diff --git a/.bazelrc b/.bazelrc index b083e61e..4471fa12 100644 --- a/.bazelrc +++ b/.bazelrc @@ -22,9 +22,20 @@ build --spawn_strategy=standalone build --strategy=SwiftCompile=standalone build --define RULES_SWIFT_BUILD_DUMMY_WORKER=1 +build:swift_profile --jobs=1 +build:swift_profile --local_cpu_resources=1 +build:swift_profile --features=-swift.enable_batch_mode +build:swift_profile --experimental_ui_max_stdouterr_bytes=104857600 +build:swift_profile --@build_bazel_rules_swift//swift:copt=-Xfrontend +build:swift_profile --@build_bazel_rules_swift//swift:copt=-warn-long-function-bodies=350 +build:swift_profile --@build_bazel_rules_swift//swift:copt=-Xfrontend +build:swift_profile --@build_bazel_rules_swift//swift:copt=-warn-long-expression-type-checking=350 + common:index_build --experimental_convenience_symlinks=ignore common:index_build --bes_backend= --bes_results_url= common:index_build --nolegacy_important_outputs common:index_build --show_result=0 common:index_build --define=buildNumber=10000 common:index_build --define=telegramVersion=12.2.2 + +common --registry=https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/ diff --git a/.gitignore b/.gitignore index 230ee773..100cdd79 100644 --- a/.gitignore +++ b/.gitignore @@ -59,8 +59,6 @@ bazel-telegram-ios bazel-telegram-ios/* bazel-testlogs bazel-testlogs/* -bazel-telegram-antidelete -bazel-telegram-antidelete/* xcodeproj.bazelrc */*.swp *.swp @@ -76,10 +74,7 @@ buildServer.json Telegram.LSP.json **/.build/** spm-files +xcode-files .bsp/** - -*.mobileprovision -build-system/fake-codesigning/profiles/* - -*.backup -*_BACKUP* \ No newline at end of file +/.claude/ +/buildbox/* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 39daf6b3..b9fde043 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -50,6 +50,11 @@ internal_testflight: - python3 -u build-system/Make/Make.py remote-deploy-testflight --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: testflight_llc + artifacts: + when: always + paths: + - build/artifacts + expire_in: 1 week appstore_development: tags: @@ -80,7 +85,7 @@ experimental_i: - export PATH=/opt/homebrew/opt/ruby/bin:$PATH - export PATH=`gem environment gemdir`/bin:$PATH - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appcenter-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=adhoc --configuration=release_arm64 - - python3 -u build-system/Make/DeployToFirebase.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/firebase-configurations/firebase-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" + - python3 -u build-system/Make/Make.py remote-deploy-testflight --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: experimental artifacts: diff --git a/.sourcekit-lsp/config.json b/.sourcekit-lsp/config.json index 7715da74..8837d093 100644 --- a/.sourcekit-lsp/config.json +++ b/.sourcekit-lsp/config.json @@ -1,6 +1,8 @@ { "backgroundIndexing": true, "backgroundPreparationMode": "build", + "buildServerWorkspaceRequestsTimeout": 999, + "buildSettingsTimeout": 999, "defaultWorkspaceType": "buildServer", "logging": { "level": "error", diff --git a/BUILD.bazel b/BUILD.bazel index 37688a7c..929cf6a2 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -2,7 +2,7 @@ load("@sourcekit_bazel_bsp//rules:setup_sourcekit_bsp.bzl", "setup_sourcekit_bsp setup_sourcekit_bsp( name = "setup_sourcekit_bsp_telegram_project", - bazel_wrapper = "./build-input/bazel-8.4.2-darwin-arm64", + bazel_wrapper = "./build-input/bazel-8.4.2-darwin-x86_64", files_to_watch = [ "**/*.swift", ], diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index ff664751..c24d0548 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,156 +1,156 @@ { "lockFileVersion": 18, "registryFileHashes": { - "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", - "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", - "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", - "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", - "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", - "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", - "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", - "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", - "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", - "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", - "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", - "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", - "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", - "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", - "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", - "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", - "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", - "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", - "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", - "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", - "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", - "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", - "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", - "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", - "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", - "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", - "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", - "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", - "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", - "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", - "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", - "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", - "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", - "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", - "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", - "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", - "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", - "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", - "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", - "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", - "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", - "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", - "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29", - "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", - "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", - "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", - "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", - "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", - "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", - "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", - "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", - "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", - "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", - "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", - "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", - "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", - "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", - "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", - "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", - "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", - "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", - "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", - "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", - "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", - "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", - "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", - "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", - "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", - "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", - "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", - "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", - "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", - "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", - "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", - "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", - "https://bcr.bazel.build/modules/rules_cc/0.1.2/source.json": "53fcb09b5816c83ca60d9d7493faf3bfaf410dfc2f15deb52d6ddd146b8d43f0", - "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", - "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", - "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", - "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", - "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", - "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", - "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", - "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", - "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", - "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", - "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", - "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", - "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", - "https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", - "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", - "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", - "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", - "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", - "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", - "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", - "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", - "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", - "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", - "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", - "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", - "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", - "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", - "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", - "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", - "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", - "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", - "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", - "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", - "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", - "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", - "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", - "https://bcr.bazel.build/modules/rules_python/0.27.1/MODULE.bazel": "65dc875cc1a06c30d5bbdba7ab021fd9e551a6579e408a3943a61303e2228a53", - "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", - "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", - "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", - "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", - "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", - "https://bcr.bazel.build/modules/rules_python/1.3.0/source.json": "25932f917cd279c7baefa6cb1d3fa8750a7a29de522024449b19af6eab51f4a0", - "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", - "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", - "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", - "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", - "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", - "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", - "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", - "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", - "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", - "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", - "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", - "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.1.2/source.json": "53fcb09b5816c83ca60d9d7493faf3bfaf410dfc2f15deb52d6ddd146b8d43f0", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.27.1/MODULE.bazel": "65dc875cc1a06c30d5bbdba7ab021fd9e551a6579e408a3943a61303e2228a53", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/1.3.0/source.json": "25932f917cd279c7baefa6cb1d3fa8750a7a29de522024449b19af6eab51f4a0", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", + "https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" }, "selectedYankedVersions": {}, "moduleExtensions": { diff --git a/README.md b/README.md index 22582050..79f325aa 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,116 @@ -# ๐Ÿ‘ป Ghostgram iOS +# Telegram iOS Source Code Compilation Guide -

- Ghostgram Logo -

+We welcome all developers to use our API and source code to create applications on our platform. +There are several things we require from **all developers** for the moment. -

- The ultimate Telegram fork for privacy, control, and freedom. -

+# Creating your Telegram Application -

- Telegram Support - Platform iOS - License GPLv2 -

+1. [**Obtain your own api_id**](https://core.telegram.org/api/obtaining_api_id) for your application. +2. Please **do not** use the name Telegram for your app โ€” or make sure your users understand that it is unofficial. +3. Kindly **do not** use our standard logo (white paper plane in a blue circle) as your app's logo. +3. Please study our [**security guidelines**](https://core.telegram.org/mtproto/security_guidelines) and take good care of your users' data and privacy. +4. Please remember to publish **your** code too in order to comply with the licences. ---- +# Quick Compilation Guide -## ๐ŸŒŸ Key Features +## Get the Code -### ๐Ÿ›ก๏ธ Anti-Delete System -*Never miss a message again. Ghostgram keeps what others try to hide.* -- **Local Archive:** Automatically saves messages deleted by the sender for everyone. -- **Media Preservation:** Photos and videos from deleted messages are archived locally. -- **Edit History:** View original content of edited messages. -- **Visual Indicators:** Deleted messages are clearly marked with a ๐Ÿ—‘๏ธ icon. +``` +git clone --recursive -j8 https://github.com/TelegramMessenger/Telegram-iOS.git +``` -### ๐Ÿ‘ค Ghost Mode -- **Invisible Reading:** Read messages without sending "Read" receipts (single checkmark). -- **Stealth Stories:** View stories anonymously without appearing in the viewer list. -- **Hidden Input:** Hide "Typing..." status from your contacts. -- **Online Privacy:** Hide your online status or use **Force Offline** to always appear as "last seen recently". +## Setup Xcode -### ๐ŸŽ™๏ธ Advanced Audio Tools -- **Free Transcription:** Get text from voice and video messages without Telegram Premium (On-device processing). -- **Voice Morpher:** Real-time voice changer for your outgoing audio messages. +Install Xcode (directly from https://developer.apple.com/download/applications or using the App Store). -### ๐Ÿ”“ Bypass Limits -- **Copy Protection Bypass:** Forward or save content from channels that have restricted it. -- **Infinite View-Once:** View-once media stays available as long as you need. -- **Secret Screenshots:** Take screenshots in secret chats without notification. +## Adjust Configuration -### ๐Ÿ› ๏ธ More Features -- **Device Spoofing:** Change how your device appears to others. -- **Ad Blocker:** Say goodbye to sponsored messages in channels. -- **Always Online:** Keep your status active even when the app is in the background. +1. Generate a random identifier: +``` +openssl rand -hex 8 +``` +2. Create a new Xcode project. Use `Telegram` as the Product Name. Use `org.{identifier from step 1}` as the Organization Identifier. +3. Open `Keychain Access` and navigate to `Certificates`. Locate `Apple Development: your@email.address (XXXXXXXXXX)` and double tap the certificate. Under `Details`, locate `Organizational Unit`. This is the Team ID. +4. Edit `build-system/template_minimal_development_configuration.json`. Use data from the previous steps. ---- +## Generate an Xcode project -## ๐Ÿ›  Installation & Building +``` +python3 build-system/Make/Make.py \ + --cacheDir="$HOME/telegram-bazel-cache" \ + generateProject \ + --configurationPath=build-system/template_minimal_development_configuration.json \ + --xcodeManagedCodesigning +``` -Ghostgram is a developer-centric fork. To build it from source, follow our detailed guide: +# Advanced Compilation Guide -๐Ÿ“– **[Build & Compilation Guide](BUILD.md)** +## Xcode ---- +1. Copy and edit `build-system/appstore-configuration.json`. +2. Copy `build-system/fake-codesigning`. Create and download provisioning profiles, using the `profiles` folder as a reference for the entitlements. +3. Generate an Xcode project: +``` +python3 build-system/Make/Make.py \ + --cacheDir="$HOME/telegram-bazel-cache" \ + generateProject \ + --configurationPath=configuration_from_step_1.json \ + --codesigningInformationPath=directory_from_step_2 +``` -## โš ๏ธ Disclaimer & Legal +## IPA -> **Educational Use Only:** This is a modified version of the Telegram client. Use it at your own risk. The developer is not responsible for any account bans or data loss. +1. Repeat the steps from the previous section. Use distribution provisioning profiles. +2. Run: +``` +python3 build-system/Make/Make.py \ + --cacheDir="$HOME/telegram-bazel-cache" \ + build \ + --configurationPath=...see previous section... \ + --codesigningInformationPath=...see previous section... \ + --buildNumber=100001 \ + --configuration=release_arm64 +``` -- **Unofficial:** This project is not affiliated with, endorsed by, or in any way officially connected with Telegram Messenger Inc. -- **Terms of Service:** Using third-party clients may violate Telegram's ToS. -- **License:** Based on [Telegram-iOS](https://github.com/TelegramMessenger/Telegram-iOS), licensed under **GPLv2**. +# FAQ ---- +## Xcode is stuck at "build-request.json not updated yet" -### ๐Ÿ‘จโ€๐Ÿ’ป Support & Community -Report issues or join the discussion: -- **Telegram:** [@ceopoco](https://t.me/ceosppw) +Occasionally, you might observe the following message in your build log: +``` +"/Users/xxx/Library/Developer/Xcode/DerivedData/Telegram-xxx/Build/Intermediates.noindex/XCBuildData/xxx.xcbuilddata/build-request.json" not updated yet, waiting... +``` -**Made with ๐Ÿ–ค for the community.** +Should this occur, simply cancel the ongoing build and initiate a new one. + +## Telegram_xcodeproj: no such package + +Following a system restart, the auto-generated Xcode project might encounter a build failure accompanied by this error: +``` +ERROR: Skipping '@rules_xcodeproj_generated//generator/Telegram/Telegram_xcodeproj:Telegram_xcodeproj': no such package '@rules_xcodeproj_generated//generator/Telegram/Telegram_xcodeproj': BUILD file not found in directory 'generator/Telegram/Telegram_xcodeproj' of external repository @rules_xcodeproj_generated. Add a BUILD file to a directory to mark it as a package. +``` + +If you encounter this issue, re-run the project generation steps in the README. + + +# Tips + +## Codesigning is not required for simulator-only builds + +Add `--disableProvisioningProfiles`: +``` +python3 build-system/Make/Make.py \ + --cacheDir="$HOME/telegram-bazel-cache" \ + generateProject \ + --configurationPath=path-to-configuration.json \ + --codesigningInformationPath=path-to-provisioning-data \ + --disableProvisioningProfiles +``` + +## Versions + +Each release is built using a specific Xcode version (see `versions.json`). The helper script checks the versions of the installed software and reports an error if they don't match the ones specified in `versions.json`. It is possible to bypass these checks: + +``` +python3 build-system/Make/Make.py --overrideXcodeVersion build ... # Don't check the version of Xcode +``` diff --git a/Telegram/BUILD b/Telegram/BUILD index 74b3ecae..4b84ab9c 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -8,8 +8,6 @@ load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_extension", "ios_framework", - "ios_unit_test", - "ios_ui_test", ) load("@build_bazel_rules_apple//apple:resources.bzl", @@ -22,7 +20,6 @@ load("@build_bazel_rules_swift//swift:swift.bzl", load( "@rules_xcodeproj//xcodeproj:defs.bzl", - "top_level_target", "top_level_targets", "xcodeproj", "xcode_provisioning_profile", @@ -338,6 +335,7 @@ filegroup( objc_library( name = "Main", + module_name = "Main", srcs = [ "Telegram-iOS/main.m" ], @@ -345,6 +343,7 @@ objc_library( swift_library( name = "Lib", + module_name = "Lib", srcs = glob([ "Telegram-iOS/Application.swift", ]), @@ -1810,7 +1809,7 @@ ios_application( #"//third-party/boringssl:ssl", #"//third-party/boringssl:crypto", #"//submodules/TelegramVoip", - #"//third-party/recaptcha:RecaptchaEnterprise", + #"//third-party/recaptcha:RecaptchaEnterpriseSDK", "//submodules/TelegramUI", ], ) diff --git a/Telegram/Telegram-iOS/Config-AppStoreLLC.xcconfig b/Telegram/Telegram-iOS/Config-AppStoreLLC.xcconfig index a220df74..76c1f1e5 100644 --- a/Telegram/Telegram-iOS/Config-AppStoreLLC.xcconfig +++ b/Telegram/Telegram-iOS/Config-AppStoreLLC.xcconfig @@ -5,4 +5,4 @@ APP_SPECIFIC_URL_SCHEME=tgapp GLOBAL_CONSTANTS = APP_CONFIG_IS_INTERNAL_BUILD=false APP_CONFIG_IS_APPSTORE_BUILD=true APP_CONFIG_APPSTORE_ID=686449807 APP_SPECIFIC_URL_SCHEME="\"$(APP_SPECIFIC_URL_SCHEME)\"" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) $(GLOBAL_CONSTANTS) -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) APP_CONFIG_API_ID=YOUR_API_ID APP_CONFIG_API_HASH="\"YOUR_API_HASH\"" APP_CONFIG_HOCKEYAPP_ID="\"ad8831329ffc8f8aff9a2b0b86558b24\"" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) APP_CONFIG_API_ID=8 APP_CONFIG_API_HASH="\"7245de8e747a0d6fbe11f7cc14fcc0bb\"" APP_CONFIG_HOCKEYAPP_ID="\"ad8831329ffc8f8aff9a2b0b86558b24\"" diff --git a/Telegram/Telegram-iOS/Config-Fork.xcconfig b/Telegram/Telegram-iOS/Config-Fork.xcconfig index 2e78f776..c3192251 100644 --- a/Telegram/Telegram-iOS/Config-Fork.xcconfig +++ b/Telegram/Telegram-iOS/Config-Fork.xcconfig @@ -5,4 +5,4 @@ APP_SPECIFIC_URL_SCHEME=tgfork GLOBAL_CONSTANTS = APP_CONFIG_IS_INTERNAL_BUILD=false APP_CONFIG_IS_APPSTORE_BUILD=true APP_CONFIG_APPSTORE_ID=0 APP_SPECIFIC_URL_SCHEME="\"$(APP_SPECIFIC_URL_SCHEME)\"" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) $(GLOBAL_CONSTANTS) -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) APP_CONFIG_API_ID=YOUR_API_ID APP_CONFIG_API_HASH="\"YOUR_API_HASH\"" APP_CONFIG_HOCKEYAPP_ID="\"\"" \ No newline at end of file +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) APP_CONFIG_API_ID=8 APP_CONFIG_API_HASH="\"7245de8e747a0d6fbe11f7cc14fcc0bb\"" APP_CONFIG_HOCKEYAPP_ID="\"\"" \ No newline at end of file diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Contents.json b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Contents.json index 4d654570..221e2b44 100644 --- a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Contents.json +++ b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Contents.json @@ -1,115 +1,116 @@ { - "images" : [ + "images": [ { - "filename" : "BlueNotificationIcon@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" + "filename": "GhostIcon@40x40.png", + "idiom": "iphone", + "scale": "2x", + "size": "20x20" }, { - "filename" : "BlueNotificationIcon@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" + "filename": "GhostIcon@60x60.png", + "idiom": "iphone", + "scale": "3x", + "size": "20x20" }, { - "filename" : "Simple@58x58.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" + "filename": "GhostIcon@58x58.png", + "idiom": "iphone", + "scale": "2x", + "size": "29x29" }, { - "filename" : "Simple@87x87.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" + "filename": "GhostIcon@87x87.png", + "idiom": "iphone", + "scale": "3x", + "size": "29x29" }, { - "filename" : "Simple@80x80.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" + "filename": "GhostIcon@80x80.png", + "idiom": "iphone", + "scale": "2x", + "size": "40x40" }, { - "filename" : "BlueIcon@2x-1.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" + "filename": "GhostIcon@120x120.png", + "idiom": "iphone", + "scale": "3x", + "size": "40x40" }, { - "filename" : "BlueIcon@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" + "filename": "GhostIcon@120x120.png", + "idiom": "iphone", + "scale": "2x", + "size": "60x60" }, { - "filename" : "BlueIcon@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" + "filename": "GhostIcon@180x180.png", + "idiom": "iphone", + "scale": "3x", + "size": "60x60" }, { - "filename" : "BlueNotificationIcon.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" + "filename": "GhostIcon@20x20.png", + "idiom": "ipad", + "scale": "1x", + "size": "20x20" }, { - "filename" : "BlueNotificationIcon@2x-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" + "filename": "GhostIcon@40x40.png", + "idiom": "ipad", + "scale": "2x", + "size": "20x20" }, { - "filename" : "Simple@29x29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" + "filename": "GhostIcon@29x29.png", + "idiom": "ipad", + "scale": "1x", + "size": "29x29" }, { - "filename" : "Simple@58x58-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" + "filename": "GhostIcon@58x58.png", + "idiom": "ipad", + "scale": "2x", + "size": "29x29" }, { - "filename" : "Simple@40x40-1.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" + "filename": "GhostIcon@40x40.png", + "idiom": "ipad", + "scale": "1x", + "size": "40x40" }, { - "filename" : "Simple@80x80-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" + "filename": "GhostIcon@80x80.png", + "idiom": "ipad", + "scale": "2x", + "size": "40x40" }, { - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" + "filename": "GhostIcon@76x76.png", + "idiom": "ipad", + "scale": "1x", + "size": "76x76" }, { - "filename" : "BlueIconIpad@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" + "filename": "GhostIcon@152x152.png", + "idiom": "ipad", + "scale": "2x", + "size": "76x76" }, { - "filename" : "BlueIconLargeIpad@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" + "filename": "GhostIcon@167x167.png", + "idiom": "ipad", + "scale": "2x", + "size": "83.5x83.5" }, { - "filename" : "Simple-iTunesArtwork.png", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" + "filename": "GhostIcon@1024x1024.png", + "idiom": "ios-marketing", + "scale": "1x", + "size": "1024x1024" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "xcode", + "version": 1 } -} +} \ No newline at end of file diff --git a/Telegram/Telegram-iOS/IconDefault-60@2x.png b/Telegram/Telegram-iOS/IconDefault-60@2x.png index 9525324b..bc3738e3 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-60@2x.png and b/Telegram/Telegram-iOS/IconDefault-60@2x.png differ diff --git a/Telegram/Telegram-iOS/IconDefault-60@3x.png b/Telegram/Telegram-iOS/IconDefault-60@3x.png index facbf49f..336e7bd2 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-60@3x.png and b/Telegram/Telegram-iOS/IconDefault-60@3x.png differ diff --git a/Telegram/Telegram-iOS/IconDefault-76.png b/Telegram/Telegram-iOS/IconDefault-76.png index 07de5603..88823054 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-76.png and b/Telegram/Telegram-iOS/IconDefault-76.png differ diff --git a/Telegram/Telegram-iOS/IconDefault-76@2x.png b/Telegram/Telegram-iOS/IconDefault-76@2x.png index d71dcd20..b7d12ca8 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-76@2x.png and b/Telegram/Telegram-iOS/IconDefault-76@2x.png differ diff --git a/Telegram/Telegram-iOS/IconDefault-83.5@2x.png b/Telegram/Telegram-iOS/IconDefault-83.5@2x.png index f51ae17d..a514afb2 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-83.5@2x.png and b/Telegram/Telegram-iOS/IconDefault-83.5@2x.png differ diff --git a/Telegram/Telegram-iOS/IconDefault-Small-40.png b/Telegram/Telegram-iOS/IconDefault-Small-40.png index e2b1ba78..7425a650 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-Small-40.png and b/Telegram/Telegram-iOS/IconDefault-Small-40.png differ diff --git a/Telegram/Telegram-iOS/IconDefault-Small-40@2x.png b/Telegram/Telegram-iOS/IconDefault-Small-40@2x.png index 8d4fe9ef..4a76bc85 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-Small-40@2x.png and b/Telegram/Telegram-iOS/IconDefault-Small-40@2x.png differ diff --git a/Telegram/Telegram-iOS/IconDefault-Small-40@3x.png b/Telegram/Telegram-iOS/IconDefault-Small-40@3x.png index 9525324b..bc3738e3 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-Small-40@3x.png and b/Telegram/Telegram-iOS/IconDefault-Small-40@3x.png differ diff --git a/Telegram/Telegram-iOS/IconDefault-Small.png b/Telegram/Telegram-iOS/IconDefault-Small.png index 4865bb8b..840c5956 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-Small.png and b/Telegram/Telegram-iOS/IconDefault-Small.png differ diff --git a/Telegram/Telegram-iOS/IconDefault-Small@2x.png b/Telegram/Telegram-iOS/IconDefault-Small@2x.png index b9f52c59..b54ab402 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-Small@2x.png and b/Telegram/Telegram-iOS/IconDefault-Small@2x.png differ diff --git a/Telegram/Telegram-iOS/IconDefault-Small@3x.png b/Telegram/Telegram-iOS/IconDefault-Small@3x.png index 95b278c2..0b4de296 100644 Binary files a/Telegram/Telegram-iOS/IconDefault-Small@3x.png and b/Telegram/Telegram-iOS/IconDefault-Small@3x.png differ diff --git a/Telegram/Telegram-iOS/InfoBazel.plist b/Telegram/Telegram-iOS/InfoBazel.plist index ae1b92de..e0172c99 100644 --- a/Telegram/Telegram-iOS/InfoBazel.plist +++ b/Telegram/Telegram-iOS/InfoBazel.plist @@ -110,17 +110,17 @@ 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. + Ghostgram 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. + When you send your location to your friends, Ghostgram 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. + When you send your location to your friends, Ghostgram 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. + When you send your location to your friends, Ghostgram needs access to show them a map. NSPhotoLibraryAddUsageDescription We need this so that you can share photos and videos from your photo library. NSPhotoLibraryUsageDescription @@ -188,7 +188,7 @@ public.data UTTypeDescription - Telegram iOS Color Theme File + Ghostgram iOS Color Theme File UTTypeIconFiles BlueIcon@3x.png diff --git a/Telegram/Telegram-iOS/Resources/Cocoon.tgs b/Telegram/Telegram-iOS/Resources/Cocoon.tgs new file mode 100644 index 00000000..9eba8213 Binary files /dev/null and b/Telegram/Telegram-iOS/Resources/Cocoon.tgs differ diff --git a/Telegram/Telegram-iOS/en.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/en.lproj/InfoPlist.strings index ca338663..0daf084e 100644 --- a/Telegram/Telegram-iOS/en.lproj/InfoPlist.strings +++ b/Telegram/Telegram-iOS/en.lproj/InfoPlist.strings @@ -1,12 +1,16 @@ /* Localized versions of Info.plist keys */ -"NSContactsUsageDescription" = "Telegram will continuously upload your contacts to its heavily encrypted cloud servers to let you connect with your friends across all your devices."; -"NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Telegram needs access to show them a map."; -"NSLocationAlwaysAndWhenInUseUsageDescription" = "When you choose to share your Live Location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing."; -"NSLocationAlwaysUsageDescription" = "When you choose to share your live location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing. You also need this to send locations from an Apple Watch."; +/* GHOSTGRAM: App display name */ +"CFBundleDisplayName" = "Ghostgram"; +"CFBundleName" = "Ghostgram"; + +"NSContactsUsageDescription" = "Ghostgram will continuously upload your contacts to its heavily encrypted cloud servers to let you connect with your friends across all your devices."; +"NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Ghostgram needs access to show them a map."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "When you choose to share your Live Location with friends in a chat, Ghostgram needs background access to your location to keep them updated for the duration of the live sharing."; +"NSLocationAlwaysUsageDescription" = "When you choose to share your live location with friends in a chat, Ghostgram needs background access to your location to keep them updated for the duration of the live sharing. You also need this to send locations from an Apple Watch."; "NSCameraUsageDescription" = "We need this so that you can take and share photos and videos, as well as make video calls."; -"NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library."; -"NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library."; +"NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library and save the ones you capture."; +"NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library and save the ones you capture."; "NSMicrophoneUsageDescription" = "We need this so that you can record and share voice messages and videos with sound."; "NSSiriUsageDescription" = "You can use Siri to send messages."; "NSFaceIDUsageDescription" = "You can use Face ID to unlock the app."; diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 597ef1ad..16b14dc7 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -4856,12 +4856,9 @@ Sorry for the inconvenience."; "Conversation.SendMessage.SetReminder" = "Set a Reminder"; -"Conversation.SelectedMessages_1" = "%@ Selected"; -"Conversation.SelectedMessages_2" = "%@ Selected"; -"Conversation.SelectedMessages_3_10" = "%@ Selected"; -"Conversation.SelectedMessages_any" = "%@ Selected"; -"Conversation.SelectedMessages_many" = "%@ Selected"; -"Conversation.SelectedMessages_0" = "%@ Selected"; +"Conversation.SelectedMessagesFormat_1" = "{} Selected"; +"Conversation.SelectedMessagesFormat_any" = "{} Selected"; + "AccentColor.Title" = "Accent Color"; @@ -15459,6 +15456,8 @@ Error: %8$@"; "Notification.StarsGiftOffer.Expired" = "%1$@ didn't respond to your offer for %2$@ within %3$@ โ€“ your %4$@ have been refunded"; "Notification.StarsGiftOffer.Expired.Stars_1" = "%@ Star"; "Notification.StarsGiftOffer.Expired.Stars_any" = "%@ Stars"; +"Notification.StarsGiftOffer.Expired.Hours_1" = "%@ hours"; +"Notification.StarsGiftOffer.Expired.Hours_any" = "%@ hours"; "Notification.StarsGiftOffer.ExpiredYou" = "The offer from %1$@ to buy your %2$@ for %3$@ has expired"; "Notification.StarsGiftOffer.ExpiredYou.Stars_1" = "%@ Star"; "Notification.StarsGiftOffer.ExpiredYou.Stars_any" = "%@ Stars"; @@ -15623,3 +15622,50 @@ Error: %8$@"; "Chat.GiftPurchaseOffer.AcceptConfirmation.BadValue" = "The value of this gift is **%@** higher than the offer."; "Chat.GiftPurchaseOffer.AcceptConfirmation.Confirm" = "Confirm Sale"; +"Notification.PremiumGift.DaysTitle_1" = "%@ Day Premium"; +"Notification.PremiumGift.DaysTitle_any" = "%@ Days Premium"; + +"MediaEditor.Audio.Title" = "Audio"; +"MediaEditor.Audio.SavedMusic" = "SAVED MUSIC"; +"MediaEditor.Audio.ShowMore" = "Show More"; + +"WebApp.AddToAttachmentTitle" = "Add to Attachment Menu"; + +"Gift.Upgrade.Wearable.Title" = "Wearable"; +"Gift.Upgrade.Wearable.Text" = "Display gifts on your page and set them as profile covers or statuses."; +"Gift.Upgrade.ViewAllVariants" = "View all variants"; + +"CocoonInfo.Title" = "COCOON"; +"CocoonInfo.Description" = "**Cocoon** (**Co**nfidential **Co**mpute **O**pen **N**etwork) handles AI tasks **safely** and **efficiently**."; +"CocoonInfo.Private.Title" = "Private"; +"CocoonInfo.Private.Text" = "No third party can access any data, such as translations, inside Cocoon."; +"CocoonInfo.Efficient.Title" = "Efficient"; +"CocoonInfo.Efficient.Text" = "Cocoon has allowed Telegram to reduce translation costs by 6x."; +"CocoonInfo.ForEveryone.Title" = "For Everyone"; +"CocoonInfo.ForEveryone.Text" = "Any developer can use Cocoon for AI features. Learn more at [@cocoon](telegram) or [cocoon.org](web)."; +"CocoonInfo.IntergrateInfo" = "Want to integrate Cocoon into your projects?\nReach out at [t.me/cocoon?direct]()"; +"CocoonInfo.Understood" = "Understood"; + +"Conversation.Translation.CocoonInfo" = "Translations are powered by\n**#Cocoon**. [How does it work?]()"; + +"Conversation.EmojiStake.Won" = "%1$@ won %2$@"; +"Conversation.EmojiStake.WonYou" = "You won %@"; +"Conversation.EmojiStake.Lost" = "%1$@ lost %2$@"; +"Conversation.EmojiStake.LostYou" = "You lost %@"; + +"Conversation.Dice.Stake" = "Stake:"; +"Conversation.Dice.Change" = "change"; + +"EmojiStake.Title" = "Emoji Stake"; +"EmojiStake.Description" = "An experimental feature for Telegram Premium users."; +"EmojiStake.ResultsTitle" = "RESULTS AND RETURNS"; +"EmojiStake.StreakInfo" = "A streak resets after 3 # or a stake change."; +"EmojiStake.StakeTitle" = "STAKE"; +"EmojiStake.StakePlaceholder" = "Amount"; +"EmojiStake.Roll" = "Save and Roll"; + +"Conversation.Summary.Title" = "AI Summary"; +"Conversation.Summary.Text" = "Tap to see original text"; + +"Conversation.Summary.Limit.Title" = "AI Summary"; +"Conversation.Summary.Limit.Text" = "Summarize large messages with AI โ€“ unlimited with Telegram Premium."; diff --git a/Telegram/Telegram-iOS/ru.lproj/InfoPlist.strings b/Telegram/Telegram-iOS/ru.lproj/InfoPlist.strings index 08c349a3..18b5cac0 100644 --- a/Telegram/Telegram-iOS/ru.lproj/InfoPlist.strings +++ b/Telegram/Telegram-iOS/ru.lproj/InfoPlist.strings @@ -1,7 +1,11 @@ /* Localized versions of Info.plist keys */ -"NSContactsUsageDescription" = "ะะบั‚ัƒะฐะปัŒะฝะฐั ะธะฝั„ะพั€ะผะฐั†ะธั ะพ ะฒะฐัˆะธั… ะบะพะฝั‚ะฐะบั‚ะฐั… ะฑัƒะดะตั‚ ั…ั€ะฐะฝะธั‚ัŒัั ะทะฐัˆะธั„ั€ะพะฒะฐะฝะฝะพะน ะฒ ะพะฑะปะฐะบะต Telegram, ั‡ั‚ะพะฑั‹ ะฒั‹ ะผะพะณะปะธ ัะฒัะทะฐั‚ัŒัั ั ะดั€ัƒะทัŒัะผะธ ั ะปัŽะฑะพะณะพ ัƒัั‚ั€ะพะนัั‚ะฒะฐ."; -"NSLocationWhenInUseUsageDescription" = "ะšะพะณะดะฐ ะฒั‹ ะพั‚ะฟั€ะฐะฒะปัะตั‚ะต ะดั€ัƒะทัŒัะผ ะณะตะพะฟะพะทะธั†ะธัŽ, Telegram ะฝัƒะถะฝะพ ั€ะฐะทั€ะตัˆะตะฝะธะต, ั‡ั‚ะพะฑั‹ ะฟะพะบะฐะทะฐั‚ัŒ ะธะผ ะบะฐั€ั‚ัƒ."; +/* GHOSTGRAM: App display name */ +"CFBundleDisplayName" = "Ghostgram"; +"CFBundleName" = "Ghostgram"; + +"NSContactsUsageDescription" = "ะะบั‚ัƒะฐะปัŒะฝะฐั ะธะฝั„ะพั€ะผะฐั†ะธั ะพ ะฒะฐัˆะธั… ะบะพะฝั‚ะฐะบั‚ะฐั… ะฑัƒะดะตั‚ ั…ั€ะฐะฝะธั‚ัŒัั ะทะฐัˆะธั„ั€ะพะฒะฐะฝะฝะพะน ะฒ ะพะฑะปะฐะบะต Ghostgram, ั‡ั‚ะพะฑั‹ ะฒั‹ ะผะพะณะปะธ ัะฒัะทะฐั‚ัŒัั ั ะดั€ัƒะทัŒัะผะธ ั ะปัŽะฑะพะณะพ ัƒัั‚ั€ะพะนัั‚ะฒะฐ."; +"NSLocationWhenInUseUsageDescription" = "ะšะพะณะดะฐ ะฒั‹ ะพั‚ะฟั€ะฐะฒะปัะตั‚ะต ะดั€ัƒะทัŒัะผ ะณะตะพะฟะพะทะธั†ะธัŽ, Ghostgram ะฝัƒะถะฝะพ ั€ะฐะทั€ะตัˆะตะฝะธะต, ั‡ั‚ะพะฑั‹ ะฟะพะบะฐะทะฐั‚ัŒ ะธะผ ะบะฐั€ั‚ัƒ."; "NSLocationAlwaysAndWhenInUseUsageDescription" = "ะคะพะฝะพะฒั‹ะน ะดะพัั‚ัƒะฟ ะบ ะณะตะพะฟะพะทะธั†ะธะธ ั‚ั€ะตะฑัƒะตั‚ัั, ั‡ั‚ะพะฑั‹ ะพะฑะฝะพะฒะปัั‚ัŒ ะฒะฐัˆัƒ ะณะตะพะฟะพะทะธั†ะธัŽ, ะบะพะณะดะฐ ะฒั‹ ั‚ั€ะฐะฝัะปะธั€ัƒะตั‚ะต ะตั‘ ะฒ ั‡ะฐั‚ ั ะดั€ัƒะทัŒัะผะธ."; "NSLocationAlwaysUsageDescription" = "ะคะพะฝะพะฒั‹ะน ะดะพัั‚ัƒะฟ ะบ ะณะตะพะฟะพะทะธั†ะธะธ ั‚ั€ะตะฑัƒะตั‚ัั, ั‡ั‚ะพะฑั‹ ะพะฑะฝะพะฒะปัั‚ัŒ ะฒะฐัˆัƒ ะณะตะพะฟะพะทะธั†ะธัŽ, ะบะพะณะดะฐ ะฒั‹ ั‚ั€ะฐะฝัะปะธั€ัƒะตั‚ะต ะตั‘ ะฒ ั‡ะฐั‚ ั ะดั€ัƒะทัŒัะผะธ. ะžะฝ ั‚ะฐะบะถะต ะฝะตะพะฑั…ะพะดะธะผ ะดะปั ะพั‚ะฟั€ะฐะฒะบะธ ะณะตะพะฟะพะทะธั†ะธะธ ั Apple Watch."; "NSCameraUsageDescription" = "ะญั‚ะพ ะฝะตะพะฑั…ะพะดะธะผะพ, ั‡ั‚ะพะฑั‹ ะฒั‹ ะผะพะณะปะธ ะดะตะปะธั‚ัŒัั ัะฝัั‚ั‹ะผะธ ั„ะพั‚ะพะณั€ะฐั„ะธัะผะธ ะธ ะฒะธะดะตะพ."; @@ -10,3 +14,4 @@ "NSMicrophoneUsageDescription" = "ะญั‚ะพ ะฝะตะพะฑั…ะพะดะธะผะพ, ั‡ั‚ะพะฑั‹ ะฒั‹ ะผะพะณะปะธ ะดะตะปะธั‚ัŒัั ะณะพะปะพัะพะฒั‹ะผะธ ัะพะพะฑั‰ะตะฝะธัะผะธ ะธ ะฒะธะดะตะพ ัะพ ะทะฒัƒะบะพะผ."; "NSSiriUsageDescription" = "ะ’ั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ Siri ะดะปั ะพั‚ะฟั€ะฐะฒะบะธ ัะพะพะฑั‰ะตะฝะธะน."; "NSFaceIDUsageDescription" = "ะ’ั‹ ะผะพะถะตั‚ะต ั€ะฐะทะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ะฟั€ะธะปะพะถะตะฝะธะต ั ะฟะพะผะพั‰ัŒัŽ Face ID."; + diff --git a/bazel-telegram-antidelete b/bazel-telegram-antidelete new file mode 120000 index 00000000..824f1c9e --- /dev/null +++ b/bazel-telegram-antidelete @@ -0,0 +1 @@ +/private/var/tmp/_bazel_ichmagmaus812/b1f80ab1863cefaee5ee828c8e6100cf/execroot/_main \ No newline at end of file diff --git a/build-system/Make/GenerateProfiles.py b/build-system/Make/GenerateProfiles.py index 0992d95a..df51eb6f 100644 --- a/build-system/Make/GenerateProfiles.py +++ b/build-system/Make/GenerateProfiles.py @@ -5,40 +5,165 @@ import shutil import tempfile import plistlib import argparse +import subprocess +import base64 from BuildEnvironment import run_executable_with_output, check_run_system -def get_certificate_base64(): - certificate_data = run_executable_with_output('security', arguments=['find-certificate', '-c', 'Apple Distribution: Telegram FZ-LLC (C67CF9S4VU)', '-p']) - certificate_data = certificate_data.replace('-----BEGIN CERTIFICATE-----', '') - certificate_data = certificate_data.replace('-----END CERTIFICATE-----', '') - certificate_data = certificate_data.replace('\n', '') - return certificate_data +def setup_temp_keychain(p12_path, p12_password=''): + """Create a temporary keychain and import the p12 certificate.""" + keychain_name = 'generate-profiles-temp.keychain' + keychain_password = 'temp123' + + # Delete if exists + run_executable_with_output('security', arguments=['delete-keychain', keychain_name], check_result=False) + + # Create keychain + run_executable_with_output('security', arguments=[ + 'create-keychain', '-p', keychain_password, keychain_name + ], check_result=True) + + # Add to search list + existing = run_executable_with_output('security', arguments=['list-keychains', '-d', 'user']) + run_executable_with_output('security', arguments=[ + 'list-keychains', '-d', 'user', '-s', keychain_name, existing.replace('"', '') + ], check_result=True) + + # Unlock and set settings + run_executable_with_output('security', arguments=['set-keychain-settings', keychain_name]) + run_executable_with_output('security', arguments=[ + 'unlock-keychain', '-p', keychain_password, keychain_name + ]) + + # Import p12 + run_executable_with_output('security', arguments=[ + 'import', p12_path, '-k', keychain_name, '-P', p12_password, + '-T', '/usr/bin/codesign', '-T', '/usr/bin/security' + ], check_result=True) + + # Set partition list for access + run_executable_with_output('security', arguments=[ + 'set-key-partition-list', '-S', 'apple-tool:,apple:', '-k', keychain_password, keychain_name + ], check_result=True) + + return keychain_name -def process_provisioning_profile(source, destination, certificate_data): +def cleanup_temp_keychain(keychain_name): + """Remove the temporary keychain.""" + run_executable_with_output('security', arguments=['delete-keychain', keychain_name], check_result=False) + + +def get_signing_identity_from_p12(p12_path, p12_password=''): + """Extract the common name (signing identity) from the p12 certificate.""" + proc = subprocess.Popen( + ['openssl', 'pkcs12', '-in', p12_path, '-passin', 'pass:' + p12_password, '-nokeys', '-legacy'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + cert_pem, _ = proc.communicate() + + proc2 = subprocess.Popen( + ['openssl', 'x509', '-noout', '-subject', '-nameopt', 'oneline,-esc_msb'], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + subject, _ = proc2.communicate(cert_pem) + subject = subject.decode('utf-8').strip() + + # Parse CN from subject line like: subject= C = AE, O = ..., CN = Some Name + if 'CN = ' in subject: + cn = subject.split('CN = ')[-1].split(',')[0].strip() + return cn + + return None + + +def get_certificate_base64_from_p12(p12_path, p12_password=''): + """Extract the certificate as base64 from p12 file.""" + # Extract certificate in PEM format + proc = subprocess.Popen( + ['openssl', 'pkcs12', '-in', p12_path, '-passin', 'pass:' + p12_password, '-nokeys', '-legacy'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + cert_pem, _ = proc.communicate() + + # Convert to DER format + proc2 = subprocess.Popen( + ['openssl', 'x509', '-outform', 'DER'], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + cert_der, _ = proc2.communicate(cert_pem) + + return base64.b64encode(cert_der).decode('utf-8') + + +def process_provisioning_profile(source, destination, certificate_data, signing_identity, keychain_name): parsed_plist = run_executable_with_output('security', arguments=['cms', '-D', '-i', source], check_result=True) parsed_plist_file = tempfile.mktemp() with open(parsed_plist_file, 'w+') as file: file.write(parsed_plist) - run_executable_with_output('plutil', arguments=['-remove', 'DeveloperCertificates.0', parsed_plist_file]) + # Remove all existing developer certificates + while True: + result = run_executable_with_output('plutil', arguments=['-remove', 'DeveloperCertificates.0', parsed_plist_file], check_result=False) + if result is None or 'Could not' in str(result) or result == '': + # Check if the removal actually failed by trying to extract + check = run_executable_with_output('plutil', arguments=['-extract', 'DeveloperCertificates.0', 'raw', parsed_plist_file], check_result=False) + if check is None or 'Could not' in str(check): + break + + # Insert the new certificate run_executable_with_output('plutil', arguments=['-insert', 'DeveloperCertificates.0', '-data', certificate_data, parsed_plist_file]) + + # Remove the DER-Encoded-Profile (signature) run_executable_with_output('plutil', arguments=['-remove', 'DER-Encoded-Profile', parsed_plist_file]) - run_executable_with_output('security', arguments=['cms', '-S', '-N', 'Apple Distribution: Telegram FZ-LLC (C67CF9S4VU)', '-i', parsed_plist_file, '-o', destination]) + # Sign with the certificate from the temporary keychain + run_executable_with_output('security', arguments=[ + 'cms', '-S', '-k', keychain_name, '-N', signing_identity, '-i', parsed_plist_file, '-o', destination + ], check_result=True) os.unlink(parsed_plist_file) -def generate_provisioning_profiles(source_path, destination_path): - certificate_data = get_certificate_base64() +def generate_provisioning_profiles(source_path, destination_path, certs_path): + p12_path = os.path.join(certs_path, 'SelfSigned.p12') - if not os.path.exists(destination_path): - print('{} does not exits'.format(destination_path)) + if not os.path.exists(p12_path): + print('{} does not exist'.format(p12_path)) sys.exit(1) - for file_name in os.listdir(source_path): - if file_name.endswith('.mobileprovision'): - process_provisioning_profile(source=source_path + '/' + file_name, destination=destination_path + '/' + file_name, certificate_data=certificate_data) + if not os.path.exists(destination_path): + print('{} does not exist'.format(destination_path)) + sys.exit(1) + + # Extract certificate info from p12 + p12_password = '' # fake-codesigning uses empty password + certificate_data = get_certificate_base64_from_p12(p12_path, p12_password) + signing_identity = get_signing_identity_from_p12(p12_path, p12_password) + + if not signing_identity: + print('Could not extract signing identity from {}'.format(p12_path)) + sys.exit(1) + + print('Using signing identity: {}'.format(signing_identity)) + + # Setup temporary keychain with the certificate + keychain_name = setup_temp_keychain(p12_path, p12_password) + + try: + for file_name in os.listdir(source_path): + if file_name.endswith('.mobileprovision'): + print('Processing {}'.format(file_name)) + process_provisioning_profile( + source=os.path.join(source_path, file_name), + destination=os.path.join(destination_path, file_name), + certificate_data=certificate_data, + signing_identity=signing_identity, + keychain_name=keychain_name + ) + print('Done. Generated {} profiles.'.format( + len([f for f in os.listdir(destination_path) if f.endswith('.mobileprovision')]) + )) + finally: + cleanup_temp_keychain(keychain_name) diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index 1898ad5c..93b6a56d 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -46,6 +46,7 @@ class BazelCommandLine: self.show_actions = False self.enable_sandbox = False self.disable_provisioning_profiles = False + self.profile_swift = False self.common_args = [ # https://docs.bazel.build/versions/master/command-line-reference.html @@ -143,6 +144,9 @@ class BazelCommandLine: def set_disable_provisioning_profiles(self): self.disable_provisioning_profiles = True + def set_profile_swift(self, value): + self.profile_swift = value + def set_configuration(self, configuration): if configuration == 'debug_arm64': self.configuration_args = [ @@ -300,6 +304,8 @@ class BazelCommandLine: ] combined_arguments += self.configuration_args + if self.profile_swift: + combined_arguments += ['--config=swift_profile'] print('TelegramBuild: running') print(subprocess.list2cmdline(combined_arguments)) @@ -369,17 +375,15 @@ class BazelCommandLine: print(subprocess.list2cmdline(combined_arguments)) call_executable(combined_arguments) - def get_spm_aspect_invocation(self): + def invoke_spm_build(self): combined_arguments = [ self.build_environment.bazel_path ] combined_arguments += self.get_startup_bazel_arguments() combined_arguments += ['build'] - if self.custom_target is not None: - combined_arguments += [self.custom_target] - else: - combined_arguments += ['Telegram/Telegram'] + # Build the generate_spm target directly to get the dependency tree JSON + combined_arguments += ['//Telegram:spm_build_root'] if self.continue_on_error: combined_arguments += ['--keep_going'] @@ -409,8 +413,6 @@ class BazelCommandLine: combined_arguments += self.configuration_args - combined_arguments += ['--aspects', '//build-system/bazel-utils:spm.bzl%spm_text_aspect'] - print(subprocess.list2cmdline(combined_arguments)) call_executable(combined_arguments) @@ -624,6 +626,7 @@ def build(bazel, arguments): bazel_command_line.set_continue_on_error(arguments.continueOnError) bazel_command_line.set_show_actions(arguments.showActions) bazel_command_line.set_enable_sandbox(arguments.sandbox) + bazel_command_line.set_profile_swift(arguments.profileSwift) bazel_command_line.set_split_swiftmodules(arguments.enableParallelSwiftmoduleGeneration) @@ -719,7 +722,7 @@ def query(bazel, arguments): bazel_command_line.invoke_query(query_args) -def get_spm_aspect_invocation(bazel, arguments): +def build_spm(bazel, arguments): bazel_command_line = BazelCommandLine( bazel=bazel, override_bazel_version=arguments.overrideBazelVersion, @@ -741,13 +744,12 @@ def get_spm_aspect_invocation(bazel, arguments): bazel_command_line.set_configuration(arguments.configuration) bazel_command_line.set_build_number(arguments.buildNumber) - bazel_command_line.set_custom_target(arguments.target) bazel_command_line.set_continue_on_error(False) bazel_command_line.set_show_actions(False) bazel_command_line.set_enable_sandbox(False) bazel_command_line.set_split_swiftmodules(False) - bazel_command_line.get_spm_aspect_invocation() + bazel_command_line.invoke_spm_build() def add_codesigning_common_arguments(current_parser: argparse.ArgumentParser): configuration_group = current_parser.add_mutually_exclusive_group(required=True) @@ -977,6 +979,12 @@ if __name__ == '__main__': help='Generate .swiftmodule files in parallel to building modules, can speed up compilation on multi-core ' 'systems. ' ) + buildParser.add_argument( + '--profileSwift', + action='store_true', + default=False, + help='Enable single-core Swift compile profiling flags.' + ) buildParser.add_argument( '--target', type=str, @@ -1068,6 +1076,13 @@ if __name__ == '__main__': type=str, help='Path to the destination directory.' ) + generate_profiles_build_parser.add_argument( + '--certsPath', + required=False, + type=str, + default='build-system/fake-codesigning/certs', + help='Path to the directory containing SelfSigned.p12 certificate.' + ) remote_upload_testflight_parser = subparsers.add_parser('remote-deploy-testflight', help='Build the app using a remote environment.') remote_upload_testflight_parser.add_argument( @@ -1188,13 +1203,7 @@ if __name__ == '__main__': metavar='query_string' ) - spm_parser = subparsers.add_parser('spm', help='Generate SPM package') - spm_parser.add_argument( - '--target', - type=str, - help='A custom bazel target name to build.', - metavar='target_name' - ) + spm_parser = subparsers.add_parser('spm', help='Generate SPM package (outputs bazel-bin/Telegram/spm_build_root_modules.json)') spm_parser.add_argument( '--buildNumber', required=False, @@ -1315,7 +1324,7 @@ if __name__ == '__main__': additional_codesigning_output_path=remote_input_path ) - GenerateProfiles.generate_provisioning_profiles(source_path=remote_input_path + '/profiles', destination_path=args.destination) + GenerateProfiles.generate_provisioning_profiles(source_path=remote_input_path + '/profiles', destination_path=args.destination, certs_path=args.certsPath) elif args.commandName == 'remote-deploy-testflight': env = os.environ if 'APPSTORE_CONNECT_USERNAME' not in env: @@ -1351,7 +1360,7 @@ if __name__ == '__main__': elif args.commandName == 'query': query(bazel=bazel_path, arguments=args) elif args.commandName == 'spm': - get_spm_aspect_invocation(bazel=bazel_path, arguments=args) + build_spm(bazel=bazel_path, arguments=args) else: raise Exception('Unknown command') except KeyboardInterrupt: diff --git a/build-system/Make/TartBuild.py b/build-system/Make/TartBuild.py index 449e9ce0..9f890ca3 100644 --- a/build-system/Make/TartBuild.py +++ b/build-system/Make/TartBuild.py @@ -604,7 +604,7 @@ def remote_build_tart(macos_version, bazel_cache_host, configuration, build_inpu else: guest_build_sh += '--cacheHost="$CACHE_HOST" \\' guest_build_sh += 'build \\' - guest_build_sh += '--lock \\' + #guest_build_sh += '--lock \\' guest_build_sh += '--buildNumber={} \\'.format(build_number) guest_build_sh += '--configuration={} \\'.format(configuration) guest_build_sh += '--configurationPath=$HOME/telegram-build-input/configuration.json \\' diff --git a/build-system/MakeProject/Package.resolved b/build-system/MakeProject/Package.resolved new file mode 100644 index 00000000..d9f1a9dc --- /dev/null +++ b/build-system/MakeProject/Package.resolved @@ -0,0 +1,50 @@ +{ + "pins" : [ + { + "identity" : "aexml", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tadija/AEXML.git", + "state" : { + "revision" : "38f7d00b23ecd891e1ee656fa6aeebd6ba04ecc3", + "version" : "4.6.1" + } + }, + { + "identity" : "pathkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kylef/PathKit.git", + "state" : { + "revision" : "3bfd2737b700b9a36565a8c94f4ad2b050a5e574", + "version" : "1.0.1" + } + }, + { + "identity" : "spectre", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kylef/Spectre.git", + "state" : { + "revision" : "26cc5e9ae0947092c7139ef7ba612e34646086c7", + "version" : "0.10.1" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "309a47b2b1d9b5e991f36961c983ecec72275be3", + "version" : "1.6.1" + } + }, + { + "identity" : "xcodeproj", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tuist/XcodeProj.git", + "state" : { + "revision" : "dc3b87a4e69f9cd06c6cb16199f5d0472e57ef6b", + "version" : "8.24.3" + } + } + ], + "version" : 2 +} diff --git a/build-system/MakeProject/Package.swift b/build-system/MakeProject/Package.swift new file mode 100644 index 00000000..8e81f4a7 --- /dev/null +++ b/build-system/MakeProject/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version:5.8 + +import PackageDescription + +let package = Package( + name: "MakeProject", + platforms: [ + .macOS(.v13) + ], + products: [ + .executable(name: "MakeProject", targets: ["MakeProject"]) + ], + dependencies: [ + .package(url: "https://github.com/tuist/XcodeProj.git", from: "8.15.0"), + .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"), + ], + targets: [ + .executableTarget( + name: "MakeProject", + dependencies: [ + .product(name: "XcodeProj", package: "XcodeProj"), + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ] + ) + ] +) diff --git a/build-system/MakeProject/Sources/MakeProject/ModuleJSON.swift b/build-system/MakeProject/Sources/MakeProject/ModuleJSON.swift new file mode 100644 index 00000000..fa47427c --- /dev/null +++ b/build-system/MakeProject/Sources/MakeProject/ModuleJSON.swift @@ -0,0 +1,54 @@ +import Foundation + +struct ModuleDefinition: Codable { + let name: String + let moduleName: String? + let type: String + let path: String + let sources: [String] + let deps: [String]? + let copts: [String]? + let cxxopts: [String]? + let defines: [String]? + let includes: [String]? + let sdkFrameworks: [String]? + let sdkDylibs: [String]? + let hdrs: [String]? + let textualHdrs: [String]? + let weakSdkFrameworks: [String]? + + enum CodingKeys: String, CodingKey { + case name + case moduleName = "module_name" + case type + case path + case sources + case deps + case copts + case cxxopts + case defines + case includes + case sdkFrameworks = "sdk_frameworks" + case sdkDylibs = "sdk_dylibs" + case hdrs + case textualHdrs = "textual_hdrs" + case weakSdkFrameworks = "weak_sdk_frameworks" + } +} + +enum ModuleType: String { + case swiftLibrary = "swift_library" + case objcLibrary = "objc_library" + case ccLibrary = "cc_library" + case xcframework = "apple_static_xcframework_import" + + init?(from definition: ModuleDefinition) { + self.init(rawValue: definition.type) + } +} + +func loadModules(from path: String) throws -> [String: ModuleDefinition] { + let url = URL(fileURLWithPath: path) + let data = try Data(contentsOf: url) + return try JSONDecoder().decode([String: ModuleDefinition].self, from: data) +} diff --git a/build-system/MakeProject/Sources/MakeProject/ProjectGenerator.swift b/build-system/MakeProject/Sources/MakeProject/ProjectGenerator.swift new file mode 100644 index 00000000..92f965af --- /dev/null +++ b/build-system/MakeProject/Sources/MakeProject/ProjectGenerator.swift @@ -0,0 +1,147 @@ +import Foundation +import PathKit +import XcodeProj + +class ProjectGenerator { + let modulesPath: Path + let outputDir: Path + let projectRoot: Path + + init(modulesPath: Path, outputDir: Path, projectRoot: Path) { + self.modulesPath = modulesPath + self.outputDir = outputDir + self.projectRoot = projectRoot + } + + func generate() throws { + print("Loading modules from \(modulesPath)...") + let modules = try loadModules(from: modulesPath.string) + print("Loaded \(modules.count) modules") + + // Filter out empty modules, but keep: + // - Modules with source files (excluding .a) + // - Static library modules (only .a files) + // - XCFramework imports + let validModules = modules.filter { name, module in + let nonStaticSources = module.sources.filter { !$0.hasSuffix(".a") } + let staticLibs = module.sources.filter { $0.hasSuffix(".a") } + return !nonStaticSources.isEmpty || + !staticLibs.isEmpty || + module.type == "apple_static_xcframework_import" + } + print("Processing \(validModules.count) non-empty modules") + + // Setup output directory + try outputDir.mkpath() + + // Create symlink manager + let symlinkManager = SymlinkManager(outputDir: outputDir, projectRoot: projectRoot) + symlinkManager.scanExistingFiles() + + // Create project + let projectPath = outputDir + "Telegram.xcodeproj" + let pbxproj = PBXProj() + + // Create main group + let mainGroup = PBXGroup(children: [], sourceTree: .group) + pbxproj.add(object: mainGroup) + + // Create project-level build configurations + let projectDebugSettings: BuildSettings = [ + "ALWAYS_SEARCH_USER_PATHS": "NO", + "CLANG_CXX_LANGUAGE_STANDARD": "c++17", + "CLANG_ENABLE_MODULES": "YES", + "CLANG_ENABLE_OBJC_ARC": "YES", + "CLANG_ENABLE_EXPLICIT_MODULES": "NO", // Disable explicit module builds for ObjC-Swift interop + "SWIFT_ENABLE_EXPLICIT_MODULES": "NO", // Disable explicit module builds for Swift + "ENABLE_STRICT_OBJC_MSGSEND": "YES", + "GCC_NO_COMMON_BLOCKS": "YES", + "IPHONEOS_DEPLOYMENT_TARGET": "13.0", + "MTL_ENABLE_DEBUG_INFO": "INCLUDE_SOURCE", + "ONLY_ACTIVE_ARCH": "YES", + "SDKROOT": "iphoneos", + "SWIFT_VERSION": "5.0", + "TARGETED_DEVICE_FAMILY": "1,2", + "DEBUG_INFORMATION_FORMAT": "dwarf", + "ENABLE_BITCODE": "NO", + ] + + var projectReleaseSettings = projectDebugSettings + projectReleaseSettings["DEBUG_INFORMATION_FORMAT"] = "dwarf-with-dsym" + projectReleaseSettings["MTL_ENABLE_DEBUG_INFO"] = "NO" + projectReleaseSettings["ONLY_ACTIVE_ARCH"] = "NO" + + let projectDebugConfig = XCBuildConfiguration(name: "Debug", buildSettings: projectDebugSettings) + let projectReleaseConfig = XCBuildConfiguration(name: "Release", buildSettings: projectReleaseSettings) + pbxproj.add(object: projectDebugConfig) + pbxproj.add(object: projectReleaseConfig) + + let projectConfigList = XCConfigurationList( + buildConfigurations: [projectDebugConfig, projectReleaseConfig], + defaultConfigurationName: "Release" + ) + pbxproj.add(object: projectConfigList) + + // Create project + let project = PBXProject( + name: "Telegram", + buildConfigurationList: projectConfigList, + compatibilityVersion: "Xcode 14.0", + preferredProjectObjectVersion: 56, + minimizedProjectReferenceProxies: 0, + mainGroup: mainGroup + ) + pbxproj.add(object: project) + + // Create target builder + let targetBuilder = TargetBuilder( + project: project, + pbxproj: pbxproj, + mainGroup: mainGroup, + outputDir: outputDir, + symlinkManager: symlinkManager + ) + + // Build targets + print("Creating targets...") + var builtCount = 0 + for (name, module) in validModules.sorted(by: { $0.key < $1.key }) { + do { + if let _ = try targetBuilder.buildTarget(for: module, allModules: validModules) { + builtCount += 1 + if builtCount % 50 == 0 { + print(" Created \(builtCount) targets...") + } + } + } catch { + print("Warning: Failed to build target \(name): \(error)") + } + } + print("Created \(builtCount) targets") + + // Wire up dependencies + print("Wiring up dependencies...") + try targetBuilder.wireUpDependencies(modules: validModules) + + // Write project + print("Writing project to \(projectPath)...") + pbxproj.rootObject = project + let xcodeproj = XcodeProj(workspace: XCWorkspace(), pbxproj: pbxproj) + try xcodeproj.write(path: projectPath) + + // Generate scheme for main target + if let telegramTarget = targetBuilder.getTarget(named: "TelegramUI") { + print("Generating scheme...") + let schemeGenerator = SchemeGenerator(projectPath: projectPath, pbxproj: pbxproj) + try schemeGenerator.generateScheme(for: telegramTarget, named: "TelegramUI") + } else { + print("Warning: Could not find TelegramUI target for scheme") + } + + // Clean up stale files + print("Cleaning up stale symlinks...") + symlinkManager.cleanupStaleFiles() + + print("Done! Project written to \(projectPath)") + } +} diff --git a/build-system/MakeProject/Sources/MakeProject/SchemeGenerator.swift b/build-system/MakeProject/Sources/MakeProject/SchemeGenerator.swift new file mode 100644 index 00000000..1c1b7632 --- /dev/null +++ b/build-system/MakeProject/Sources/MakeProject/SchemeGenerator.swift @@ -0,0 +1,74 @@ +import Foundation +import PathKit +import XcodeProj + +class SchemeGenerator { + let projectPath: Path + let pbxproj: PBXProj + + init(projectPath: Path, pbxproj: PBXProj) { + self.projectPath = projectPath + self.pbxproj = pbxproj + } + + func generateScheme(for target: PBXNativeTarget, named schemeName: String) throws { + let schemesDir = projectPath + "xcshareddata" + "xcschemes" + try schemesDir.mkpath() + + let buildableReference = XCScheme.BuildableReference( + referencedContainer: "container:Telegram.xcodeproj", + blueprint: target, + buildableName: "\(target.name).framework", + blueprintName: target.name + ) + + let buildAction = XCScheme.BuildAction( + buildActionEntries: [ + XCScheme.BuildAction.Entry( + buildableReference: buildableReference, + buildFor: [.running, .testing, .profiling, .archiving, .analyzing] + ) + ], + parallelizeBuild: true, + buildImplicitDependencies: true + ) + + let launchAction = XCScheme.LaunchAction( + runnable: nil, + buildConfiguration: "Debug" + ) + + let testAction = XCScheme.TestAction( + buildConfiguration: "Debug", + macroExpansion: buildableReference + ) + + let profileAction = XCScheme.ProfileAction( + runnable: nil, + buildConfiguration: "Release", + macroExpansion: buildableReference + ) + + let analyzeAction = XCScheme.AnalyzeAction(buildConfiguration: "Debug") + + let archiveAction = XCScheme.ArchiveAction( + buildConfiguration: "Release", + revealArchiveInOrganizer: true + ) + + let scheme = XCScheme( + name: schemeName, + lastUpgradeVersion: nil, + version: nil, + buildAction: buildAction, + testAction: testAction, + launchAction: launchAction, + profileAction: profileAction, + analyzeAction: analyzeAction, + archiveAction: archiveAction + ) + + let schemePath = schemesDir + "\(schemeName).xcscheme" + try scheme.write(path: schemePath, override: true) + } +} diff --git a/build-system/MakeProject/Sources/MakeProject/SymlinkManager.swift b/build-system/MakeProject/Sources/MakeProject/SymlinkManager.swift new file mode 100644 index 00000000..1ee9da3d --- /dev/null +++ b/build-system/MakeProject/Sources/MakeProject/SymlinkManager.swift @@ -0,0 +1,116 @@ +import Foundation +import PathKit + +class SymlinkManager { + let outputDir: Path + let projectRoot: Path + private var previousFiles: Set = [] + private var currentFiles: Set = [] + + init(outputDir: Path, projectRoot: Path) { + self.outputDir = outputDir + self.projectRoot = projectRoot + } + + func scanExistingFiles() { + previousFiles = [] + scanDirectory(outputDir) + } + + private func scanDirectory(_ path: Path) { + guard path.exists else { return } + do { + for item in try path.children() { + let name = item.lastComponent + // Skip build artifacts and xcodeproj bundles + if name == ".build" || name.hasSuffix(".xcodeproj") { continue } + previousFiles.insert(item) + if item.isDirectory && !item.isSymlink { + scanDirectory(item) + } + } + } catch { + print("Warning: Could not scan \(path): \(error)") + } + } + + func createDirectory(_ path: Path) throws { + currentFiles.insert(path) + var parent = path.parent() + while parent != outputDir && parent.string.count > outputDir.string.count { + currentFiles.insert(parent) + parent = parent.parent() + } + if !path.exists { + try path.mkpath() + } + } + + func createSymlink(from source: Path, to target: Path) throws { + currentFiles.insert(target) + var parent = target.parent() + while parent != outputDir && parent.string.count > outputDir.string.count { + currentFiles.insert(parent) + parent = parent.parent() + } + + // Calculate relative path from target back to source + let targetDir = target.parent() + let depth = targetDir.components.count - outputDir.components.count + 1 + let relativePrefix = Array(repeating: "..", count: depth).joined(separator: "/") + let relativePath = Path(relativePrefix) + source + + if target.isSymlink { + let existingTarget = try? target.symlinkDestination() + if existingTarget == relativePath { + return // Already correct + } + try target.delete() + } else if target.exists { + try target.delete() + } + + try targetDir.mkpath() + try FileManager.default.createSymbolicLink( + atPath: target.string, + withDestinationPath: relativePath.string + ) + } + + func cleanupStaleFiles() { + let staleFiles = previousFiles.subtracting(currentFiles) + let sortedStale = staleFiles.sorted { $0.components.count > $1.components.count } + + for path in sortedStale { + do { + if path.isSymlink || path.isFile { + try path.delete() + } else if path.isDirectory { + if (try? path.children().isEmpty) == true { + try path.delete() + } + } + } catch { + print("Warning: Could not remove \(path): \(error)") + } + } + } + + func markFile(_ path: Path) { + currentFiles.insert(path) + } +} + +extension Path { + var isSymlink: Bool { + var isDir: ObjCBool = false + let exists = FileManager.default.fileExists(atPath: self.string, isDirectory: &isDir) + guard exists else { return false } + do { + let attrs = try FileManager.default.attributesOfItem(atPath: self.string) + return attrs[.type] as? FileAttributeType == .typeSymbolicLink + } catch { + return false + } + } +} diff --git a/build-system/MakeProject/Sources/MakeProject/TargetBuilder.swift b/build-system/MakeProject/Sources/MakeProject/TargetBuilder.swift new file mode 100644 index 00000000..8252f47c --- /dev/null +++ b/build-system/MakeProject/Sources/MakeProject/TargetBuilder.swift @@ -0,0 +1,792 @@ +import Foundation +import PathKit +import XcodeProj + +class TargetBuilder { + let project: PBXProject + let pbxproj: PBXProj + let mainGroup: PBXGroup + let outputDir: Path + let symlinkManager: SymlinkManager + + private var targetsByName: [String: PBXNativeTarget] = [:] + private var groupsByPath: [String: PBXGroup] = [:] + + init(project: PBXProject, pbxproj: PBXProj, mainGroup: PBXGroup, outputDir: Path, symlinkManager: SymlinkManager) { + self.project = project + self.pbxproj = pbxproj + self.mainGroup = mainGroup + self.outputDir = outputDir + self.symlinkManager = symlinkManager + } + + // Track which modules are header-only (have no linkable code) + private var headerOnlyModules: Set = [] + // Track which modules are static library collections (.a files) + private var staticLibraryModules: [String: [String]] = [:] // module name -> list of .a file paths + + func isHeaderOnlyModule(_ name: String) -> Bool { + return headerOnlyModules.contains(name) + } + + func isStaticLibraryModule(_ name: String) -> Bool { + return staticLibraryModules[name] != nil + } + + func getStaticLibraries(for name: String) -> [String] { + return staticLibraryModules[name] ?? [] + } + + func buildTarget(for module: ModuleDefinition, allModules: [String: ModuleDefinition]) throws -> PBXNativeTarget? { + guard let moduleType = ModuleType(from: module) else { + print("Warning: Unknown module type \(module.type) for \(module.name)") + return nil + } + + // Check for static library modules (only .a files) + let staticLibs = module.sources.filter { $0.hasSuffix(".a") } + let nonStaticLibs = module.sources.filter { !$0.hasSuffix(".a") } + let isStaticLibOnly = !staticLibs.isEmpty && nonStaticLibs.allSatisfy { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") } + + if isStaticLibOnly && moduleType != .xcframework { + // This is a static library module - track its .a files but don't create a framework + staticLibraryModules[module.name] = staticLibs + return nil // Don't create a target, just track the static libs + } + + // Check if module is header-only (only header files, no real sources) + let sourceFiles = module.sources.filter { source in + !source.hasSuffix(".a") && !source.hasSuffix(".h") && !source.hasSuffix(".hpp") + } + let isHeaderOnly = sourceFiles.isEmpty && moduleType != .xcframework + + // Skip modules with no sources at all + if module.sources.isEmpty && moduleType != .xcframework { + return nil + } + + switch moduleType { + case .xcframework: + return try buildXCFrameworkTarget(for: module) + case .swiftLibrary, .objcLibrary, .ccLibrary: + if isHeaderOnly { + headerOnlyModules.insert(module.name) + return try buildHeaderOnlyFrameworkTarget(for: module) + } else { + return try buildFrameworkTarget(for: module, moduleType: moduleType) + } + } + } + + private func buildXCFrameworkTarget(for module: ModuleDefinition) throws -> PBXNativeTarget { + // For xcframeworks, we create a reference but no build phases + let target = PBXNativeTarget( + name: module.name, + buildConfigurationList: createConfigurationList(for: module, isXCFramework: true), + buildPhases: [], + productName: module.name, + productType: .framework + ) + pbxproj.add(object: target) + project.targets.append(target) + targetsByName[module.name] = target + return target + } + + private func buildFrameworkTarget(for module: ModuleDefinition, moduleType: ModuleType) throws -> PBXNativeTarget { + // Create group for module + let moduleGroup = try getOrCreateGroup(for: module.path) + + // Create symlinks and file references + var sourceRefs: [PBXFileReference] = [] + var publicHeaderRefs: [PBXFileReference] = [] + var seenPublicHeaderNames: Set = [] // Track header filenames to avoid duplicates in headers build phase + + // Determine public header detection: + // 1. If hdrs is provided, use those (explicit public headers) + // 2. Otherwise, headers in includes directories are public + let explicitPublicHeaders = Set(module.hdrs ?? []) + + let allSourceFiles = module.sources + (module.hdrs ?? []) + (module.textualHdrs ?? []) + + for source in allSourceFiles { + if source.hasSuffix(".a") { continue } + + let sourcePath = Path(source) + let fileName = sourcePath.lastComponent + + // Calculate relative path within module + let relativeToModule: String + if source.hasPrefix(module.path + "/") { + relativeToModule = String(source.dropFirst(module.path.count + 1)) + } else if source.hasPrefix("bazel-out/") { + // Generated file + if let range = source.range(of: module.path + "/") { + relativeToModule = String(source[range.upperBound...]) + } else { + relativeToModule = fileName + } + } else { + relativeToModule = fileName + } + + // Create symlink + let symlinkPath = outputDir + module.path + relativeToModule + try symlinkManager.createDirectory(symlinkPath.parent()) + try symlinkManager.createSymlink(from: Path(source), to: symlinkPath) + + // Create file reference + let fileRef = try getOrCreateFileReference( + path: relativeToModule, + in: moduleGroup, + modulePath: module.path, + fileName: fileName + ) + + let isHeader = source.hasSuffix(".h") || source.hasSuffix(".hpp") + + // Determine if this is a public header: + // 1. Explicitly in hdrs array + // 2. Located in an includes directory + let isPublicHeader: Bool + if !isHeader { + isPublicHeader = false + } else if !explicitPublicHeaders.isEmpty { + // If hdrs is provided, only those are public + isPublicHeader = explicitPublicHeaders.contains(source) + } else { + // Headers in include directories are public (handles bazel-out paths) + isPublicHeader = isInIncludesDirectory(source: source, modulePath: module.path, includes: module.includes) + } + + if isPublicHeader { + // Skip duplicate header filenames to avoid "multiple commands produce" errors + if !seenPublicHeaderNames.contains(fileName) { + seenPublicHeaderNames.insert(fileName) + publicHeaderRefs.append(fileRef) + } + } else if !source.hasSuffix(".inc") && !isHeader { + // Source files (not headers) + sourceRefs.append(fileRef) + } + // Private headers are not added to any build phase + } + + // Build phases + var buildPhases: [PBXBuildPhase] = [] + + // Sources build phase + let sourcesBuildPhase = PBXSourcesBuildPhase( + files: sourceRefs.map { ref in + let buildFile = PBXBuildFile(file: ref) + pbxproj.add(object: buildFile) + return buildFile + } + ) + pbxproj.add(object: sourcesBuildPhase) + buildPhases.append(sourcesBuildPhase) + + // Generate modulemap for ObjC/C++ modules (SPM-style explicit headers) + var modulemapPath: Path? = nil + if moduleType == .objcLibrary || moduleType == .ccLibrary { + modulemapPath = try generateModulemap(for: module) + + // Headers build phase with public headers - needed for ObjC #import to work + if !publicHeaderRefs.isEmpty { + let headersBuildPhase = PBXHeadersBuildPhase( + files: publicHeaderRefs.map { ref in + let buildFile = PBXBuildFile(file: ref, settings: ["ATTRIBUTES": ["Public"]]) + pbxproj.add(object: buildFile) + return buildFile + } + ) + pbxproj.add(object: headersBuildPhase) + buildPhases.append(headersBuildPhase) + } + } + + // Frameworks build phase + let frameworksBuildPhase = PBXFrameworksBuildPhase(files: []) + pbxproj.add(object: frameworksBuildPhase) + buildPhases.append(frameworksBuildPhase) + + // Create target with custom modulemap if generated + let configList = createConfigurationList(for: module, isXCFramework: false, modulemapPath: modulemapPath) + + let target = PBXNativeTarget( + name: module.name, + buildConfigurationList: configList, + buildPhases: buildPhases, + productName: module.name, + productType: .framework + ) + pbxproj.add(object: target) + project.targets.append(target) + targetsByName[module.name] = target + + return target + } + + /// Build a framework target for header-only modules (just headers + modulemap, no sources) + private func buildHeaderOnlyFrameworkTarget(for module: ModuleDefinition) throws -> PBXNativeTarget { + // Create group for module + let moduleGroup = try getOrCreateGroup(for: module.path) + + // Symlink header files and create file references + var publicHeaderRefs: [PBXFileReference] = [] + var seenPublicHeaderNames: Set = [] // Track header filenames to avoid duplicates + let allHeaders = module.sources.filter { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") } + (module.hdrs ?? []) + (module.textualHdrs ?? []) + + for source in allHeaders { + let sourcePath = Path(source) + let fileName = sourcePath.lastComponent + let relativeToModule = relativePathInModule(source: source, modulePath: module.path) + + // Create parent group + let parentPath = (Path(module.path) + Path(relativeToModule).parent()).string + let parentGroup = try getOrCreateGroup(for: parentPath) + + // Create symlink + let symlinkPath = outputDir + module.path + relativeToModule + try symlinkManager.createDirectory(symlinkPath.parent()) + try symlinkManager.createSymlink(from: Path(source), to: symlinkPath) + + // Create file reference + let fileType = lastKnownFileType(for: fileName) + let fileRef = PBXFileReference( + sourceTree: .group, + name: fileName, + lastKnownFileType: fileType, + path: fileName + ) + pbxproj.add(object: fileRef) + parentGroup.children.append(fileRef) + + // Check if it's a public header (in includes directories) + // Use helper that properly handles bazel-out paths + let isPublic = isInIncludesDirectory(source: source, modulePath: module.path, includes: module.includes) + if isPublic { + // Skip duplicate header filenames to avoid "multiple commands produce" errors + if !seenPublicHeaderNames.contains(fileName) { + seenPublicHeaderNames.insert(fileName) + publicHeaderRefs.append(fileRef) + } + } + } + + // Generate modulemap for this header-only module + let modulemapPath = try generateModulemap(for: module) + + // Create build phases + var buildPhases: [PBXBuildPhase] = [] + + // Headers build phase with public headers - needed for ObjC #import to work + if !publicHeaderRefs.isEmpty { + let headersBuildPhase = PBXHeadersBuildPhase( + files: publicHeaderRefs.map { ref in + let buildFile = PBXBuildFile(file: ref, settings: ["ATTRIBUTES": ["Public"]]) + pbxproj.add(object: buildFile) + return buildFile + } + ) + pbxproj.add(object: headersBuildPhase) + buildPhases.append(headersBuildPhase) + } + + // Empty frameworks phase (needed for Xcode, but won't link anything) + let frameworksBuildPhase = PBXFrameworksBuildPhase(files: []) + pbxproj.add(object: frameworksBuildPhase) + buildPhases.append(frameworksBuildPhase) + + // Create target with custom modulemap + let configList = createConfigurationList(for: module, isXCFramework: false, modulemapPath: modulemapPath) + + let target = PBXNativeTarget( + name: module.name, + buildConfigurationList: configList, + buildPhases: buildPhases, + productName: module.name, + productType: .framework + ) + pbxproj.add(object: target) + project.targets.append(target) + targetsByName[module.name] = target + + return target + } + + /// Collect all transitive dependencies for a module + private func collectAllDependencies(for moduleName: String, modules: [String: ModuleDefinition], visited: inout Set) -> Set { + guard !visited.contains(moduleName) else { return [] } + visited.insert(moduleName) + + guard let module = modules[moduleName], let deps = module.deps else { + return [] + } + + var allDeps = Set(deps) + for depName in deps { + allDeps.formUnion(collectAllDependencies(for: depName, modules: modules, visited: &visited)) + } + return allDeps + } + + func wireUpDependencies(modules: [String: ModuleDefinition]) throws { + for (name, module) in modules { + guard let target = targetsByName[name], + let deps = module.deps else { continue } + + // Find frameworks build phase + guard let frameworksPhase = target.buildPhases.compactMap({ $0 as? PBXFrameworksBuildPhase }).first else { + continue + } + + // Track all frameworks we've added to avoid duplicates + var linkedFrameworks: Set = [] + // Track library search paths for static libraries + var staticLibSearchPaths: Set = [] + + // Add target dependency (only for direct deps) + for depName in deps { + if let depTarget = targetsByName[depName] { + let dependency = PBXTargetDependency(target: depTarget) + pbxproj.add(object: dependency) + target.dependencies.append(dependency) + } + } + + // Collect all transitive dependencies to link + var visited = Set() + let allDeps = collectAllDependencies(for: name, modules: modules, visited: &visited) + + // Collect header paths from ALL dependencies (direct and transitive) + var depHeaderPaths: [String] = [] + for depName in allDeps { + if let depModule = modules[depName] { + depHeaderPaths.append(contentsOf: exportedHeaderPaths(for: depModule)) + } + } + + // Link all dependencies (direct and transitive) that have targets + for depName in allDeps { + // Skip if already linked + guard !linkedFrameworks.contains(depName) else { continue } + + // Check if this is a static library module - link .a files directly + if isStaticLibraryModule(depName) { + // Link static libraries directly + for libPath in getStaticLibraries(for: depName) { + let libName = Path(libPath).lastComponent + // Create an absolute path to the project root then to the static library + // SRCROOT is xcode-files, so we need to go up one level to get to telegram-ios + let projectRoot = outputDir.parent() + let absoluteLibPath = (projectRoot + libPath).string + let libRef = PBXFileReference( + sourceTree: .absolute, + name: libName, + lastKnownFileType: "archive.ar", + path: absoluteLibPath + ) + pbxproj.add(object: libRef) + let buildFile = PBXBuildFile(file: libRef) + pbxproj.add(object: buildFile) + frameworksPhase.files?.append(buildFile) + + // Add the library's directory to LIBRARY_SEARCH_PATHS + let libDir = Path(absoluteLibPath).parent().string + if !staticLibSearchPaths.contains(libDir) { + staticLibSearchPaths.insert(libDir) + } + } + linkedFrameworks.insert(depName) + continue + } + + // Skip header-only modules (no framework to link) + if isHeaderOnlyModule(depName) { continue } + + // Regular framework dependency + guard targetsByName[depName] != nil else { continue } + + linkedFrameworks.insert(depName) + let frameworkRef = PBXFileReference( + sourceTree: .buildProductsDir, + name: "\(depName).framework", + lastKnownFileType: "wrapper.framework", + path: "\(depName).framework" + ) + pbxproj.add(object: frameworkRef) + let buildFile = PBXBuildFile(file: frameworkRef) + pbxproj.add(object: buildFile) + frameworksPhase.files?.append(buildFile) + } + + // Update build configurations with dependency paths + if !depHeaderPaths.isEmpty || !deps.isEmpty || !staticLibSearchPaths.isEmpty { + for config in target.buildConfigurationList?.buildConfigurations ?? [] { + // Add header search paths + if !depHeaderPaths.isEmpty { + let existing = config.buildSettings["HEADER_SEARCH_PATHS"] as? String ?? "$(inherited)" + config.buildSettings["HEADER_SEARCH_PATHS"] = existing + " " + depHeaderPaths.joined(separator: " ") + } + // Ensure framework and module search paths include built products + config.buildSettings["FRAMEWORK_SEARCH_PATHS"] = "$(inherited) $(BUILT_PRODUCTS_DIR)" + config.buildSettings["SWIFT_INCLUDE_PATHS"] = "$(inherited) $(BUILT_PRODUCTS_DIR)" + // Add library search paths for static libraries + if !staticLibSearchPaths.isEmpty { + let existing = config.buildSettings["LIBRARY_SEARCH_PATHS"] as? String ?? "$(inherited)" + config.buildSettings["LIBRARY_SEARCH_PATHS"] = existing + " " + staticLibSearchPaths.sorted().joined(separator: " ") + } + } + } + + // Collect SDK frameworks from this module and all transitive dependencies + var allSdkFrameworks: Set = [] + if let sdkFrameworks = module.sdkFrameworks { + allSdkFrameworks.formUnion(sdkFrameworks) + } + for depName in allDeps { + if let depModule = modules[depName], let depFrameworks = depModule.sdkFrameworks { + allSdkFrameworks.formUnion(depFrameworks) + } + } + + // Add SDK frameworks + for framework in allSdkFrameworks { + let fileRef = PBXFileReference( + sourceTree: .sdkRoot, + name: "\(framework).framework", + lastKnownFileType: "wrapper.framework", + path: "System/Library/Frameworks/\(framework).framework" + ) + pbxproj.add(object: fileRef) + let buildFile = PBXBuildFile(file: fileRef) + pbxproj.add(object: buildFile) + frameworksPhase.files?.append(buildFile) + } + + // Collect SDK dylibs from this module and all transitive dependencies + var allSdkDylibs: Set = [] + if let sdkDylibs = module.sdkDylibs { + allSdkDylibs.formUnion(sdkDylibs) + } + for depName in allDeps { + if let depModule = modules[depName], let depDylibs = depModule.sdkDylibs { + allSdkDylibs.formUnion(depDylibs) + } + } + + // Add SDK dylibs (system libraries like libz, libiconv, etc.) + for dylib in allSdkDylibs { + // Clean up the library name - remove 'lib' prefix if present + let libName = dylib.hasPrefix("lib") ? String(dylib.dropFirst(3)) : dylib + let fileRef = PBXFileReference( + sourceTree: .sdkRoot, + name: "lib\(libName).tbd", + lastKnownFileType: "sourcecode.text-based-dylib-definition", + path: "usr/lib/lib\(libName).tbd" + ) + pbxproj.add(object: fileRef) + let buildFile = PBXBuildFile(file: fileRef) + pbxproj.add(object: buildFile) + frameworksPhase.files?.append(buildFile) + } + } + } + + /// Returns the header search paths that this module exports to its dependents + private func exportedHeaderPaths(for module: ModuleDefinition) -> [String] { + var paths: [String] = [] + if let includes = module.includes, !includes.isEmpty { + for inc in includes { + if inc == "." { + paths.append("$(SRCROOT)/\(module.path)") + } else { + paths.append("$(SRCROOT)/\(module.path)/\(inc)") + } + } + } else { + // No includes specified, export module's own path + paths.append("$(SRCROOT)/\(module.path)") + } + return paths + } + + func getTarget(named name: String) -> PBXNativeTarget? { + return targetsByName[name] + } + + + private func createConfigurationList(for module: ModuleDefinition, isXCFramework: Bool, modulemapPath: Path? = nil) -> XCConfigurationList { + let debugSettings = createBuildSettings(for: module, isDebug: true, isXCFramework: isXCFramework, modulemapPath: modulemapPath) + let releaseSettings = createBuildSettings(for: module, isDebug: false, isXCFramework: isXCFramework, modulemapPath: modulemapPath) + + let debugConfig = XCBuildConfiguration(name: "Debug", buildSettings: debugSettings) + let releaseConfig = XCBuildConfiguration(name: "Release", buildSettings: releaseSettings) + + pbxproj.add(object: debugConfig) + pbxproj.add(object: releaseConfig) + + let configList = XCConfigurationList( + buildConfigurations: [debugConfig, releaseConfig], + defaultConfigurationName: "Release" + ) + pbxproj.add(object: configList) + + return configList + } + + private func createBuildSettings(for module: ModuleDefinition, isDebug: Bool, isXCFramework: Bool, modulemapPath: Path? = nil) -> BuildSettings { + var settings: BuildSettings = [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "PRODUCT_BUNDLE_IDENTIFIER": "org.telegram.\(module.name)", + "INFOPLIST_FILE": "", + "SKIP_INSTALL": "YES", + "GENERATE_INFOPLIST_FILE": "YES", + ] + + let moduleType = ModuleType(from: module) + + // Swift settings + if moduleType == .swiftLibrary { + settings["SWIFT_VERSION"] = "5.0" + settings["DEFINES_MODULE"] = "YES" + + if let copts = module.copts, !copts.isEmpty { + let filtered = copts.filter { !$0.hasPrefix("-warnings") } + if !filtered.isEmpty { + settings["OTHER_SWIFT_FLAGS"] = "$(inherited) " + filtered.joined(separator: " ") + } + } + + if let defines = module.defines, !defines.isEmpty { + settings["SWIFT_ACTIVE_COMPILATION_CONDITIONS"] = "$(inherited) " + defines.joined(separator: " ") + } + } + + // C/ObjC settings + if moduleType == .objcLibrary || moduleType == .ccLibrary { + // Always suppress deprecated warnings (e.g., OSSpinLock) and don't treat warnings as errors + var cflags = ["-Wno-deprecated-declarations"] + if let copts = module.copts, !copts.isEmpty { + let filtered = copts.filter { !$0.hasPrefix("-warnings") && !$0.hasPrefix("-W") } + cflags.append(contentsOf: filtered) + } + settings["OTHER_CFLAGS"] = "$(inherited) " + cflags.joined(separator: " ") + settings["GCC_TREAT_WARNINGS_AS_ERRORS"] = "NO" + + if let cxxopts = module.cxxopts, !cxxopts.isEmpty { + let filtered = cxxopts.filter { !$0.hasPrefix("-std=") } + if !filtered.isEmpty { + settings["OTHER_CPLUSPLUSFLAGS"] = "$(inherited) " + filtered.joined(separator: " ") + } + } + + if let defines = module.defines, !defines.isEmpty { + settings["GCC_PREPROCESSOR_DEFINITIONS"] = "$(inherited) " + defines.joined(separator: " ") + } + + // Always include module's own path for header search + var headerPaths = ["$(SRCROOT)/\(module.path)"] + if let includes = module.includes { + for inc in includes where inc != "." { + headerPaths.append("$(SRCROOT)/\(module.path)/\(inc)") + } + } + settings["HEADER_SEARCH_PATHS"] = "$(inherited) " + headerPaths.joined(separator: " ") + + // Use custom modulemap if provided (SPM-style) + if let modmap = modulemapPath { + // Get relative path from SRCROOT + let relativePath = modmap.string.replacingOccurrences(of: outputDir.string + "/", with: "") + settings["MODULEMAP_FILE"] = "$(SRCROOT)/\(relativePath)" + } + settings["DEFINES_MODULE"] = "YES" + } + + return settings + } + + private func getOrCreateGroup(for path: String) throws -> PBXGroup { + if let existing = groupsByPath[path] { + return existing + } + + let components = path.split(separator: "/").map(String.init) + var currentGroup = mainGroup + var currentPath = "" + + for component in components { + currentPath = currentPath.isEmpty ? component : currentPath + "/" + component + + if let existing = groupsByPath[currentPath] { + currentGroup = existing + } else { + let newGroup = PBXGroup(children: [], sourceTree: .group, name: component, path: component) + pbxproj.add(object: newGroup) + currentGroup.children.append(newGroup) + groupsByPath[currentPath] = newGroup + currentGroup = newGroup + } + } + + return currentGroup + } + + private func getOrCreateFileReference(path: String, in group: PBXGroup, modulePath: String, fileName: String) throws -> PBXFileReference { + let pathComponents = path.split(separator: "/").map(String.init) + + var currentGroup = group + var currentPath = modulePath + + // Navigate/create intermediate groups + for component in pathComponents.dropLast() { + currentPath = currentPath + "/" + component + if let existing = groupsByPath[currentPath] { + currentGroup = existing + } else { + let newGroup = PBXGroup(children: [], sourceTree: .group, name: component, path: component) + pbxproj.add(object: newGroup) + currentGroup.children.append(newGroup) + groupsByPath[currentPath] = newGroup + currentGroup = newGroup + } + } + + // Create file reference + let fileType = lastKnownFileType(for: fileName) + let fileRef = PBXFileReference( + sourceTree: .group, + name: fileName, + lastKnownFileType: fileType, + path: fileName + ) + pbxproj.add(object: fileRef) + currentGroup.children.append(fileRef) + + return fileRef + } + + private func lastKnownFileType(for fileName: String) -> String { + let ext = (fileName as NSString).pathExtension.lowercased() + switch ext { + case "swift": return "sourcecode.swift" + case "m": return "sourcecode.c.objc" + case "mm": return "sourcecode.cpp.objcpp" + case "c": return "sourcecode.c.c" + case "cc", "cpp", "cxx": return "sourcecode.cpp.cpp" + case "h": return "sourcecode.c.h" + case "hpp": return "sourcecode.cpp.h" + case "metal": return "sourcecode.metal" + case "json": return "text.json" + case "plist": return "text.plist.xml" + default: return "text" + } + } + + /// Extract the relative path within a module from a source path + /// Handles both regular paths (submodules/Foo/...) and bazel-out paths (bazel-out/.../bin/submodules/Foo/...) + private func relativePathInModule(source: String, modulePath: String) -> String { + if source.hasPrefix(modulePath + "/") { + return String(source.dropFirst(modulePath.count + 1)) + } else if source.hasPrefix("bazel-out/") { + // Generated file - extract path after the module path portion + if let range = source.range(of: modulePath + "/") { + return String(source[range.upperBound...]) + } else { + return Path(source).lastComponent + } + } else { + return Path(source).lastComponent + } + } + + /// Check if a source file is in one of the includes directories + private func isInIncludesDirectory(source: String, modulePath: String, includes: [String]?) -> Bool { + guard let includes = includes, !includes.isEmpty else { + return false + } + let relative = relativePathInModule(source: source, modulePath: modulePath) + return includes.contains { inc in + if inc == "." { + return true // All files in module are public + } else { + return relative.hasPrefix(inc + "/") || relative == inc + } + } + } + + /// Generates an explicit module.modulemap for ObjC/C++ modules (SPM-style) + /// Returns the path to the modulemap, or nil if no public headers + func generateModulemap(for module: ModuleDefinition) throws -> Path? { + let moduleType = ModuleType(from: module) + guard moduleType == .objcLibrary || moduleType == .ccLibrary else { + return nil + } + + // Determine public header prefix from includes + let publicHeaderPrefix: String + if let includes = module.includes, !includes.isEmpty { + let firstInclude = includes[0] + publicHeaderPrefix = firstInclude == "." ? "" : firstInclude + } else { + publicHeaderPrefix = "" + } + + // Determine public headers + let explicitPublicHeaders = Set(module.hdrs ?? []) + + let allFiles = module.sources + (module.hdrs ?? []) + (module.textualHdrs ?? []) + let allHeaders = allFiles.filter { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") } + + // Determine which headers are public + let publicHeaders: [String] + if !explicitPublicHeaders.isEmpty { + publicHeaders = allHeaders.filter { explicitPublicHeaders.contains($0) } + } else if module.includes != nil && !module.includes!.isEmpty { + // Use helper that properly handles bazel-out paths + publicHeaders = allHeaders.filter { header in + isInIncludesDirectory(source: header, modulePath: module.path, includes: module.includes) + } + } else { + publicHeaders = [] + } + + guard !publicHeaders.isEmpty else { + return nil + } + + // Generate explicit modulemap content (SPM-style) + var content = "// module.modulemap for \(module.name)\n" + content += "// Auto-generated - do not edit\n\n" + content += "module \(module.name) {\n" + + for header in publicHeaders { + // Calculate the symlinked path - matches the symlink creation in buildFrameworkTarget + // The symlink is at: outputDir + module.path + relativeToModule + let relativeToModule = relativePathInModule(source: header, modulePath: module.path) + let symlinkPath = outputDir + module.path + relativeToModule + content += " header \"\(symlinkPath.string)\"\n" + } + + content += " export *\n" + content += "}\n" + + // Write modulemap to the public headers directory + let modulemapDir: Path + if !publicHeaderPrefix.isEmpty { + modulemapDir = outputDir + module.path + publicHeaderPrefix + } else { + modulemapDir = outputDir + module.path + } + let modulemapPath = modulemapDir + "module.modulemap" + + try modulemapDir.mkpath() + try modulemapPath.write(content) + + // Track this file so it doesn't get cleaned up + symlinkManager.markFile(modulemapPath) + + return modulemapPath + } +} diff --git a/build-system/MakeProject/Sources/MakeProject/main.swift b/build-system/MakeProject/Sources/MakeProject/main.swift new file mode 100644 index 00000000..31fd3656 --- /dev/null +++ b/build-system/MakeProject/Sources/MakeProject/main.swift @@ -0,0 +1,51 @@ +import Foundation +import ArgumentParser +import PathKit + +struct MakeProject: ParsableCommand { + static let configuration = CommandConfiguration( + commandName: "MakeProject", + abstract: "Generate Xcode project from Bazel module definitions" + ) + + @Option(name: .long, help: "Path to modules JSON file") + var modulesJson: String = "bazel-bin/Telegram/spm_build_root_modules.json" + + @Option(name: .long, help: "Output directory for generated project") + var output: String = "xcode-files" + + func run() throws { + // Determine project root (where we find the modules JSON) + let currentDir = Path.current + var projectRoot = currentDir + + // Walk up to find project root (contains bazel-bin or the modules file) + var searchDir = currentDir + for _ in 0..<5 { + if (searchDir + modulesJson).exists { + projectRoot = searchDir + break + } + searchDir = searchDir.parent() + } + + let modulesPath = projectRoot + modulesJson + let outputDir = projectRoot + output + + guard modulesPath.exists else { + print("Error: Modules JSON not found at \(modulesPath)") + print("Run 'bazel build //Telegram:spm_build_root' first") + throw ExitCode.failure + } + + let generator = ProjectGenerator( + modulesPath: modulesPath, + outputDir: outputDir, + projectRoot: projectRoot + ) + + try generator.generate() + } +} + +MakeProject.main() diff --git a/build-system/appcenter-configuration.json b/build-system/appcenter-configuration.json index 5623b6d4..b86e141c 100755 --- a/build-system/appcenter-configuration.json +++ b/build-system/appcenter-configuration.json @@ -1,7 +1,7 @@ { "bundle_id": "ph.telegra.Telegraph", "api_id": "8", - "api_hash": "YOUR_API_HASH", + "api_hash": "7245de8e747a0d6fbe11f7cc14fcc0bb", "team_id": "C67CF9S4VU", "app_center_id": "4c816ed0-df83-423c-846b-a0a8467dc7d2", "is_internal_build": "false", diff --git a/build-system/appstore-configuration.json b/build-system/appstore-configuration.json index 79702e8a..9dea3548 100755 --- a/build-system/appstore-configuration.json +++ b/build-system/appstore-configuration.json @@ -1,7 +1,7 @@ { "bundle_id": "ph.telegra.Telegraph", "api_id": "8", - "api_hash": "YOUR_API_HASH", + "api_hash": "7245de8e747a0d6fbe11f7cc14fcc0bb", "team_id": "C67CF9S4VU", "app_center_id": "0", "is_internal_build": "false", diff --git a/build-system/example-configuration/provisioning/BroadcastUpload.mobileprovision b/build-system/example-configuration/provisioning/BroadcastUpload.mobileprovision new file mode 100644 index 00000000..90bc0e0e Binary files /dev/null and b/build-system/example-configuration/provisioning/BroadcastUpload.mobileprovision differ diff --git a/build-system/example-configuration/provisioning/Intents.mobileprovision b/build-system/example-configuration/provisioning/Intents.mobileprovision new file mode 100644 index 00000000..3de2acb5 Binary files /dev/null and b/build-system/example-configuration/provisioning/Intents.mobileprovision differ diff --git a/build-system/example-configuration/provisioning/NotificationContent.mobileprovision b/build-system/example-configuration/provisioning/NotificationContent.mobileprovision new file mode 100644 index 00000000..2866dd88 Binary files /dev/null and b/build-system/example-configuration/provisioning/NotificationContent.mobileprovision differ diff --git a/build-system/example-configuration/provisioning/NotificationService.mobileprovision b/build-system/example-configuration/provisioning/NotificationService.mobileprovision new file mode 100644 index 00000000..a0f5e98a Binary files /dev/null and b/build-system/example-configuration/provisioning/NotificationService.mobileprovision differ diff --git a/build-system/example-configuration/provisioning/Share.mobileprovision b/build-system/example-configuration/provisioning/Share.mobileprovision new file mode 100644 index 00000000..636d4658 Binary files /dev/null and b/build-system/example-configuration/provisioning/Share.mobileprovision differ diff --git a/build-system/example-configuration/provisioning/Telegram.mobileprovision b/build-system/example-configuration/provisioning/Telegram.mobileprovision new file mode 100644 index 00000000..534e1305 Binary files /dev/null and b/build-system/example-configuration/provisioning/Telegram.mobileprovision differ diff --git a/build-system/example-configuration/provisioning/WatchApp.mobileprovision b/build-system/example-configuration/provisioning/WatchApp.mobileprovision new file mode 100644 index 00000000..112b95d6 Binary files /dev/null and b/build-system/example-configuration/provisioning/WatchApp.mobileprovision differ diff --git a/build-system/example-configuration/provisioning/WatchExtension.mobileprovision b/build-system/example-configuration/provisioning/WatchExtension.mobileprovision new file mode 100644 index 00000000..87440042 Binary files /dev/null and b/build-system/example-configuration/provisioning/WatchExtension.mobileprovision differ diff --git a/build-system/example-configuration/provisioning/Widget.mobileprovision b/build-system/example-configuration/provisioning/Widget.mobileprovision new file mode 100644 index 00000000..7f4ae880 Binary files /dev/null and b/build-system/example-configuration/provisioning/Widget.mobileprovision differ diff --git a/build-system/example-configuration/variables.bzl b/build-system/example-configuration/variables.bzl index c035abfc..ed0fce46 100644 --- a/build-system/example-configuration/variables.bzl +++ b/build-system/example-configuration/variables.bzl @@ -1,7 +1,7 @@ telegram_bundle_id = "ph.telegra.Telegraph" telegram_api_id = "8" -telegram_api_hash = "YOUR_API_HASH" +telegram_api_hash = "7245de8e747a0d6fbe11f7cc14fcc0bb" telegram_team_id = "C67CF9S4VU" telegram_app_center_id = "0" telegram_is_internal_build = "false" diff --git a/build-system/fake-codesigning/profiles/BroadcastUpload.mobileprovision b/build-system/fake-codesigning/profiles/BroadcastUpload.mobileprovision new file mode 100644 index 00000000..76d68f79 Binary files /dev/null and b/build-system/fake-codesigning/profiles/BroadcastUpload.mobileprovision differ diff --git a/build-system/fake-codesigning/profiles/Intents.mobileprovision b/build-system/fake-codesigning/profiles/Intents.mobileprovision new file mode 100644 index 00000000..5d102699 Binary files /dev/null and b/build-system/fake-codesigning/profiles/Intents.mobileprovision differ diff --git a/build-system/fake-codesigning/profiles/NotificationContent.mobileprovision b/build-system/fake-codesigning/profiles/NotificationContent.mobileprovision new file mode 100644 index 00000000..1f3b13bf Binary files /dev/null and b/build-system/fake-codesigning/profiles/NotificationContent.mobileprovision differ diff --git a/build-system/fake-codesigning/profiles/NotificationService.mobileprovision b/build-system/fake-codesigning/profiles/NotificationService.mobileprovision new file mode 100644 index 00000000..809c9c8a Binary files /dev/null and b/build-system/fake-codesigning/profiles/NotificationService.mobileprovision differ diff --git a/build-system/fake-codesigning/profiles/Share.mobileprovision b/build-system/fake-codesigning/profiles/Share.mobileprovision new file mode 100644 index 00000000..cc40fa6a Binary files /dev/null and b/build-system/fake-codesigning/profiles/Share.mobileprovision differ diff --git a/build-system/fake-codesigning/profiles/Telegram.mobileprovision b/build-system/fake-codesigning/profiles/Telegram.mobileprovision new file mode 100644 index 00000000..0db3e47c Binary files /dev/null and b/build-system/fake-codesigning/profiles/Telegram.mobileprovision differ diff --git a/build-system/fake-codesigning/profiles/WatchApp.mobileprovision b/build-system/fake-codesigning/profiles/WatchApp.mobileprovision new file mode 100644 index 00000000..b766d12e Binary files /dev/null and b/build-system/fake-codesigning/profiles/WatchApp.mobileprovision differ diff --git a/build-system/fake-codesigning/profiles/WatchExtension.mobileprovision b/build-system/fake-codesigning/profiles/WatchExtension.mobileprovision new file mode 100644 index 00000000..43d092ff Binary files /dev/null and b/build-system/fake-codesigning/profiles/WatchExtension.mobileprovision differ diff --git a/build-system/fake-codesigning/profiles/Widget.mobileprovision b/build-system/fake-codesigning/profiles/Widget.mobileprovision new file mode 100644 index 00000000..e578d141 Binary files /dev/null and b/build-system/fake-codesigning/profiles/Widget.mobileprovision differ diff --git a/build-system/verify.sh b/build-system/verify.sh index 30894933..fce7a685 100644 --- a/build-system/verify.sh +++ b/build-system/verify.sh @@ -7,7 +7,7 @@ export DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: Digital Fortress LL export DEVELOPMENT_TEAM="C67CF9S4VU" export API_ID="8" -export API_HASH="YOUR_API_HASH" +export API_HASH="7245de8e747a0d6fbe11f7cc14fcc0bb" export BUNDLE_ID="ph.telegra.Telegraph" export APP_CENTER_ID="0" diff --git a/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.NotificationContent.mobileprovision b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.NotificationContent.mobileprovision new file mode 100644 index 00000000..bd054573 Binary files /dev/null and b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.NotificationContent.mobileprovision differ diff --git a/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.NotificationService.mobileprovision b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.NotificationService.mobileprovision new file mode 100644 index 00000000..663bd68b Binary files /dev/null and b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.NotificationService.mobileprovision differ diff --git a/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.Share.mobileprovision b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.Share.mobileprovision new file mode 100644 index 00000000..e49de2d7 Binary files /dev/null and b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.Share.mobileprovision differ diff --git a/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.SiriIntents.mobileprovision b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.SiriIntents.mobileprovision new file mode 100644 index 00000000..8cb484f9 Binary files /dev/null and b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.SiriIntents.mobileprovision differ diff --git a/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.Widget.mobileprovision b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.Widget.mobileprovision new file mode 100644 index 00000000..37de9fe5 Binary files /dev/null and b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.Widget.mobileprovision differ diff --git a/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.mobileprovision b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.mobileprovision new file mode 100644 index 00000000..15926743 Binary files /dev/null and b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.mobileprovision differ diff --git a/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.watchkitapp.mobileprovision b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.watchkitapp.mobileprovision new file mode 100644 index 00000000..2b0558a2 Binary files /dev/null and b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.watchkitapp.mobileprovision differ diff --git a/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.watchkitapp.watchkitextension.mobileprovision b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.watchkitapp.watchkitextension.mobileprovision new file mode 100644 index 00000000..ba9ce392 Binary files /dev/null and b/buildbox/fake-codesigning/profiles/appstore/AppStore_ph.telegra.Telegraph.watchkitapp.watchkitextension.mobileprovision differ diff --git a/codesigning/profiles_backup/Ghostgram_BroadcastUpload_Distribution.mobileprovision b/codesigning/profiles_backup/Ghostgram_BroadcastUpload_Distribution.mobileprovision new file mode 100644 index 00000000..a0439365 Binary files /dev/null and b/codesigning/profiles_backup/Ghostgram_BroadcastUpload_Distribution.mobileprovision differ diff --git a/codesigning/profiles_backup/Ghostgram_Distribution.mobileprovision b/codesigning/profiles_backup/Ghostgram_Distribution.mobileprovision new file mode 100644 index 00000000..fac7f9c4 Binary files /dev/null and b/codesigning/profiles_backup/Ghostgram_Distribution.mobileprovision differ diff --git a/codesigning/profiles_backup/Ghostgram_NotificationContent_Distribution.mobileprovision b/codesigning/profiles_backup/Ghostgram_NotificationContent_Distribution.mobileprovision new file mode 100644 index 00000000..c7ae0b3f Binary files /dev/null and b/codesigning/profiles_backup/Ghostgram_NotificationContent_Distribution.mobileprovision differ diff --git a/codesigning/profiles_backup/Ghostgram_NotificationService_Dist.mobileprovision b/codesigning/profiles_backup/Ghostgram_NotificationService_Dist.mobileprovision new file mode 100644 index 00000000..af2aad2f Binary files /dev/null and b/codesigning/profiles_backup/Ghostgram_NotificationService_Dist.mobileprovision differ diff --git a/codesigning/profiles_backup/Ghostgram_Share_Distribution.mobileprovision b/codesigning/profiles_backup/Ghostgram_Share_Distribution.mobileprovision new file mode 100644 index 00000000..0be3546f Binary files /dev/null and b/codesigning/profiles_backup/Ghostgram_Share_Distribution.mobileprovision differ diff --git a/codesigning/profiles_backup/Ghostgram_SiriIntents_Distribution.mobileprovision b/codesigning/profiles_backup/Ghostgram_SiriIntents_Distribution.mobileprovision new file mode 100644 index 00000000..a5da2b13 Binary files /dev/null and b/codesigning/profiles_backup/Ghostgram_SiriIntents_Distribution.mobileprovision differ diff --git a/codesigning/profiles_backup/Ghostgram_WatchApp_Distribution.mobileprovision b/codesigning/profiles_backup/Ghostgram_WatchApp_Distribution.mobileprovision new file mode 100644 index 00000000..51a6911e Binary files /dev/null and b/codesigning/profiles_backup/Ghostgram_WatchApp_Distribution.mobileprovision differ diff --git a/codesigning/profiles_backup/Ghostgram_WatchExtension_Distribution.mobileprovision b/codesigning/profiles_backup/Ghostgram_WatchExtension_Distribution.mobileprovision new file mode 100644 index 00000000..7e41ac87 Binary files /dev/null and b/codesigning/profiles_backup/Ghostgram_WatchExtension_Distribution.mobileprovision differ diff --git a/codesigning/profiles_backup/Ghostgram_Widget_Distribution.mobileprovision b/codesigning/profiles_backup/Ghostgram_Widget_Distribution.mobileprovision new file mode 100644 index 00000000..f2e8498e Binary files /dev/null and b/codesigning/profiles_backup/Ghostgram_Widget_Distribution.mobileprovision differ diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 0081e2c6..608bd418 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1376,7 +1376,7 @@ public protocol SharedAccountContext: AnyObject { func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, transitionOut: @escaping () -> BotPreviewEditorTransitionOut?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController - func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController + func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?) func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController func makeStickerPickerScreen(context: AccountContext, inputData: Promise, completion: @escaping (FileMediaReference) -> Void) -> ViewController func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController @@ -1407,10 +1407,10 @@ public protocol SharedAccountContext: AnyObject { func makeGiftUpgradePreviewScreen(context: AccountContext, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController func makeGiftAuctionInfoScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: (() -> Void)?) -> ViewController func makeGiftAuctionBidScreen(context: AccountContext, toPeerId: EnginePeer.Id, text: String?, entities: [MessageTextEntity]?, hideName: Bool, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?) -> ViewController - func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController + func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, peerId: EnginePeer.Id?, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController - func makeGiftOfferScreen(context: AccountContext, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController - func makeGiftUpgradeVariantsPreviewScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute]) -> ViewController + func makeGiftOfferScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController + func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController func makeGiftDemoScreen(context: AccountContext) -> ViewController func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController @@ -1428,6 +1428,8 @@ public protocol SharedAccountContext: AnyObject { func makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController func makeSendInviteLinkScreen(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme?) -> ViewController + func makeCocoonInfoScreen(context: AccountContext) -> ViewController + func makeLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, text: String, link: String?, apply: @escaping (String?) -> Void) -> ViewController @available(iOS 13.0, *) func makePostSuggestionsSettingsScreen(context: AccountContext, peerId: EnginePeer.Id) async -> ViewController @@ -1678,7 +1680,7 @@ public struct StarsSubscriptionConfiguration { return StarsSubscriptionConfiguration( maxFee: 2500, usdWithdrawRate: 1200, - tonUsdRate: 0, + tonUsdRate: 1.0, paidMessageMaxAmount: 10000, paidMessageCommissionPermille: 850, paidMessagesAvailable: false, @@ -1698,7 +1700,7 @@ public struct StarsSubscriptionConfiguration { public let maxFee: Int64 public let usdWithdrawRate: Int64 - public let tonUsdRate: Int64 + public let tonUsdRate: Double public let paidMessageMaxAmount: Int64 public let paidMessageCommissionPermille: Int32 public let paidMessagesAvailable: Bool @@ -1717,7 +1719,7 @@ public struct StarsSubscriptionConfiguration { fileprivate init( maxFee: Int64, usdWithdrawRate: Int64, - tonUsdRate: Int64, + tonUsdRate: Double, paidMessageMaxAmount: Int64, paidMessageCommissionPermille: Int32, paidMessagesAvailable: Bool, @@ -1756,7 +1758,7 @@ public struct StarsSubscriptionConfiguration { if let data = appConfiguration.data { let maxFee = (data["stars_subscription_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.maxFee let usdWithdrawRate = (data["stars_usd_withdraw_rate_x1000"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.usdWithdrawRate - let tonUsdRate = (data["ton_usd_rate"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.tonUsdRate + let tonUsdRate = (data["ton_usd_rate"] as? Double) ?? StarsSubscriptionConfiguration.defaultValue.tonUsdRate let paidMessageMaxAmount = (data["stars_paid_message_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageMaxAmount let paidMessageCommissionPermille = (data["stars_paid_message_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageCommissionPermille let paidMessagesAvailable = (data["stars_paid_messages_available"] as? Bool) ?? StarsSubscriptionConfiguration.defaultValue.paidMessagesAvailable diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index d8860963..e499fc07 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -1216,7 +1216,7 @@ public enum ChatHistoryListDisplayHeaders { public enum ChatHistoryListMode: Equatable { case bubbles - case list(search: Bool, reversed: Bool, reverseGroups: Bool, displayHeaders: ChatHistoryListDisplayHeaders, hintLinks: Bool, isGlobalSearch: Bool) + case list(reversed: Bool, reverseGroups: Bool, displayHeaders: ChatHistoryListDisplayHeaders, hintLinks: Bool, isGlobalSearch: Bool) } public protocol ChatControllerInteractionProtocol: AnyObject { diff --git a/submodules/AccountUtils/Sources/AccountUtils.swift b/submodules/AccountUtils/Sources/AccountUtils.swift index 44d09f56..aa4f12eb 100644 --- a/submodules/AccountUtils/Sources/AccountUtils.swift +++ b/submodules/AccountUtils/Sources/AccountUtils.swift @@ -4,8 +4,9 @@ import TelegramCore import TelegramUIPreferences import AccountContext -public let maximumNumberOfAccounts = 3 -public let maximumPremiumNumberOfAccounts = 4 +// GHOSTGRAM: Unlimited accounts bypass - always allow up to 10 accounts +public let maximumNumberOfAccounts = 10 +public let maximumPremiumNumberOfAccounts = 10 public func activeAccountsAndPeers(context: AccountContext, includePrimary: Bool = false) -> Signal<((AccountContext, EnginePeer)?, [(AccountContext, EnginePeer, Int32)]), NoError> { let sharedContext = context.sharedContext diff --git a/submodules/AdUI/Sources/AdInfoScreen.swift b/submodules/AdUI/Sources/AdInfoScreen.swift index 9a3309f9..7cecf822 100644 --- a/submodules/AdUI/Sources/AdInfoScreen.swift +++ b/submodules/AdUI/Sources/AdInfoScreen.swift @@ -73,7 +73,7 @@ public final class AdInfoScreen: ViewController { self.titleNode = ImmediateTextNode() self.titleNode.maximumNumberOfLines = 1 - self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.SponsoredMessageInfoScreen_Title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.SponsoredMessageInfoScreen_Title, font: Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor) self.scrollNode = ASScrollNode() self.scrollNode.view.showsVerticalScrollIndicator = true diff --git a/submodules/AttachmentUI/BUILD b/submodules/AttachmentUI/BUILD index d29a2ee4..21040b91 100644 --- a/submodules/AttachmentUI/BUILD +++ b/submodules/AttachmentUI/BUILD @@ -44,6 +44,8 @@ swift_library( "//submodules/TelegramUI/Components/MinimizedContainer", "//submodules/TelegramUI/Components/GlassBackgroundComponent", "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/LiquidLens", + "//submodules/TelegramUI/Components/TabSelectionRecognizer", ], visibility = [ "//visibility:public", diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 66665ad2..d0f87723 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -24,6 +24,8 @@ import LegacyMessageInputPanelInputView import ReactionSelectionNode import TopMessageReactions import GlassBackgroundComponent +import LiquidLens +import TabSelectionRecognizer private let legacyButtonSize = CGSize(width: 88.0, height: 49.0) private let glassButtonSize = CGSize(width: 72.0, height: 62.0) @@ -887,7 +889,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { } } -final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { +final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate { enum Style { case glass case legacy @@ -912,12 +914,22 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { private let containerNode: ASDisplayNode private var backgroundView: GlassBackgroundView? + private var liquidLensView: LiquidLensView? private let backgroundNode: NavigationBackgroundNode private let scrollNode: ASScrollNode private let separatorNode: ASDisplayNode private let selectionNode: ASImageNode - private var buttonViews: [AnyHashable: ComponentHostView] = [:] + + private var itemsContainer = UIView() + private var itemViews: [AnyHashable: ComponentHostView] = [:] + private var selectedItemsContainer = UIView() + private var selectedItemViews: [AnyHashable: ComponentHostView] = [:] + private var itemSizes: [AnyHashable: CGSize] = [:] + + private var tabSelectionRecognizer: TabSelectionRecognizer? + private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable, isLifted: Bool)? + private var lensIsLifted = false private var textInputPanelNode: AttachmentTextInputPanelNode? private var progressNode: LoadingProgressNode? @@ -971,7 +983,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { self.makeEntityInputView = makeEntityInputView - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) self.containerNode = ASDisplayNode() self.containerNode.clipsToBounds = false @@ -987,7 +999,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { self.mainButtonNode = MainButtonNode() self.secondaryButtonNode = MainButtonNode() - + super.init() self.addSubnode(self.containerNode) @@ -995,7 +1007,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { switch style { case .glass: self.scrollNode.cornerRadius = glassButtonSize.height * 0.5 - self.scrollNode.addSubnode(self.selectionNode) case .legacy: self.containerNode.addSubnode(self.backgroundNode) self.containerNode.addSubnode(self.separatorNode) @@ -1135,7 +1146,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = chatTextLinkEditController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: (presentationData, .never()), account: strongSelf.context.account, text: text?.string ?? "", link: link, apply: { [weak self] link in + let controller = chatTextLinkEditController(context: strongSelf.context, updatedPresentationData: (presentationData, .never()), text: text?.string ?? "", link: link, apply: { [weak self] link in if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange { if let link = link { strongSelf.updateChatPresentationInterfaceState(animated: true, { state in @@ -1419,6 +1430,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { self.view.accessibilityTraits = .tabBar } + func requestLayout(transition: ContainedViewLayoutTransition) { + if let layout = self.validLayout { + let _ = self.update(layout: layout, buttons: self.buttons, isSelecting: self.isSelecting, selectionCount: self.selectionCount, elevateProgress: self.elevateProgress, transition: transition) + } + } + @objc private func mainButtonPressed() { self.onMainButtonPressed() } @@ -1473,6 +1490,105 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } } + private func item(at point: CGPoint) -> AnyHashable? { + let contentOffset = self.scrollNode.view.contentOffset.x + let point = point.offsetBy(dx: contentOffset, dy: 0.0) + var closestItem: (AnyHashable, CGFloat)? + for (id, itemView) in self.itemViews { + if itemView.frame.contains(point) { + return id + } else { + let distance = abs(point.x - itemView.center.x) + if let closestItemValue = closestItem { + if closestItemValue.1 > distance { + closestItem = (id, distance) + } + } else { + closestItem = (id, distance) + } + } + } + return closestItem?.0 + } + + @objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) { + guard let liquidLensView = self.liquidLensView else { + return + } + let location = recognizer.location(in: liquidLensView.contentView) + switch recognizer.state { + case .began: + if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] { + let startX = itemView.frame.minX + self.selectionGestureState = (startX, startX, itemId, !self.scrollNode.view.isScrollEnabled) + + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + } + case .changed: + if var selectionGestureState = self.selectionGestureState { + selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self.view).x + if let itemId = self.item(at: location) { + selectionGestureState.itemId = itemId + } + self.selectionGestureState = selectionGestureState + self.requestLayout(transition: .immediate) + } + case .ended, .cancelled: + if let selectionGestureState = self.selectionGestureState { + if !selectionGestureState.isLifted { + self.lensIsLifted = true + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + + self.liquidLensView?.clipsToBounds = false + + Queue.mainQueue().after(0.1, { + self.lensIsLifted = false + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + Queue.mainQueue().after(0.4, { + self.liquidLensView?.clipsToBounds = true + }) + }) + } + + self.selectionGestureState = nil + if case .ended = recognizer.state { + guard let index = self.buttons.firstIndex(where: { AnyHashable($0.key) == selectionGestureState.itemId }) else { + return + } + let button = self.buttons[index] + self.skipLensUpdate = true + if self.selectionChanged(button) { + self.selectedIndex = index + if self.buttons.count > 5, let button = self.itemViews[button.key] { + let transition = ComponentTransition.spring(duration: 0.4) + + let scrollView = self.scrollNode.view + let targetRect = button.frame.insetBy(dx: -35.0, dy: 0.0) + + var newBounds = scrollView.bounds + if targetRect.minX < scrollView.bounds.minX { + newBounds.origin.x = targetRect.minX + } + else if targetRect.maxX > scrollView.bounds.maxX { + newBounds.origin.x = targetRect.maxX - scrollView.bounds.width + } + let minX = 0.0 + let maxX = scrollView.contentSize.width - scrollView.bounds.width + newBounds.origin.x = max(minX, min(newBounds.origin.x, maxX)) + + transition.setBounds(view: scrollView, bounds: newBounds) + self.updateItemContainers(contentOffset: newBounds.minX, transition: transition) + } + } + self.skipLensUpdate = false + } + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + } + default: + break + } + } + func updateViews(transition: ComponentTransition) { guard let layout = self.validLayout else { return @@ -1486,18 +1602,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { case .legacy: panelSideInset = 3.0 } - - let visibleRect = self.scrollNode.bounds.insetBy(dx: -180.0, dy: 0.0) - + var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, self.buttons.count - 1))) let internalWidth = distanceBetweenNodes * CGFloat(self.buttons.count - 1) var buttonWidth = self.buttonSize.width var leftNodeOriginX: CGFloat - var maxButtonsToFit = 6 + var maxButtonsToFit = 5 switch self.panelStyle { case .glass: - leftNodeOriginX = layout.safeInsets.left + 3.0 + buttonWidth / 2.0 - if layout.size.width < 400.0 { + leftNodeOriginX = layout.safeInsets.left + buttonWidth / 2.0 + if layout.size.width < 420.0 { maxButtonsToFit = 5 } case .legacy: @@ -1513,7 +1627,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { buttonWidth = smallButtonWidth distanceBetweenNodes = 60.0 } - leftNodeOriginX = layout.safeInsets.left + 3.0 + buttonWidth / 2.0 + leftNodeOriginX = layout.safeInsets.left + buttonWidth / 2.0 } var validIds = Set() @@ -1521,25 +1635,28 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { var selectionFrame = CGRect() var mostRightX = 0.0 for i in 0 ..< self.buttons.count { - let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0) - let buttonFrame = CGRect(origin: CGPoint(x: originX, y: 0.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height)) + let originX = floorToScreenPixels(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0) + let buttonFrame = CGRect(origin: CGPoint(x: originX, y: -3.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height)) mostRightX = buttonFrame.maxX - if !visibleRect.intersects(buttonFrame) { - continue - } let type = self.buttons[i] let _ = validIds.insert(type.key) var buttonTransition = transition let buttonView: ComponentHostView - if let current = self.buttonViews[type.key] { + let selectedButtonView: ComponentHostView + if let current = self.itemViews[type.key], let currentSelected = self.selectedItemViews[type.key] { buttonView = current + selectedButtonView = currentSelected } else { buttonTransition = .immediate buttonView = ComponentHostView() - self.buttonViews[type.key] = buttonView - self.scrollNode.view.addSubview(buttonView) + self.itemViews[type.key] = buttonView + self.itemsContainer.addSubview(buttonView) + + selectedButtonView = ComponentHostView() + self.selectedItemViews[type.key] = selectedButtonView + self.selectedItemsContainer.addSubview(selectedButtonView) } if case let .app(bot) = type { @@ -1578,7 +1695,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { style: self.panelStyle == .glass ? .glass : .legacy, type: type, isFirstOrLast: i == 0 || i == self.buttons.count - 1, - isSelected: i == self.selectedIndex, + isSelected: false, strings: self.presentationData.strings, theme: self.presentationData.theme, action: { [weak self] in @@ -1587,7 +1704,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { strongSelf.selectedIndex = i strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) - if strongSelf.buttons.count > 5, let button = strongSelf.buttonViews[type.key] { + if strongSelf.buttons.count > 5, let button = strongSelf.itemViews[type.key] { strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true) } } @@ -1601,11 +1718,32 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { environment: {}, containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height) ) + self.itemSizes[type.key] = actualButtonSize + + let _ = selectedButtonView.update( + transition: buttonTransition, + component: AnyComponent(AttachButtonComponent( + context: self.context, + style: self.panelStyle == .glass ? .glass : .legacy, + type: type, + isFirstOrLast: i == 0 || i == self.buttons.count - 1, + isSelected: true, + strings: self.presentationData.strings, + theme: self.presentationData.theme, + action: { + }, + longPressAction: { + }) + ), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height) + ) if i == self.selectedIndex { selectionFrame = CGRect(origin: CGPoint(x: buttonFrame.midX - actualButtonSize.width * 0.5, y: buttonFrame.minY), size: actualButtonSize) } buttonTransition.setFrame(view: buttonView, frame: buttonFrame) + buttonTransition.setFrame(view: selectedButtonView, frame: buttonFrame) var accessibilityTitle = "" switch type { case .gallery: @@ -1634,14 +1772,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { buttonView.accessibilityTraits = [.button] } var removeIds: [AnyHashable] = [] - for (id, itemView) in self.buttonViews { + for (id, itemView) in self.itemViews { if !validIds.contains(id) { removeIds.append(id) itemView.removeFromSuperview() } } for id in removeIds { - self.buttonViews.removeValue(forKey: id) + self.itemViews.removeValue(forKey: id) } selectionFrame = selectionFrame.insetBy(dx: 1.0, dy: 4.0) @@ -1651,9 +1789,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { mostRightX += layout.safeInsets.right + 3.0 let contentSize = CGSize(width: mostRightX, height: self.buttonSize.height) - if contentSize != self.scrollNode.view.contentSize { + if contentSize != self.scrollNode.view.contentSize && self.scrollNode.view.bounds.width > 0.0 { self.scrollNode.view.contentSize = contentSize - self.scrollNode.view.isScrollEnabled = abs(contentSize.width - self.scrollNode.view.bounds.width) > 1.0 + self.scrollNode.view.isScrollEnabled = contentSize.width - self.scrollNode.view.bounds.width > 1.0 + self.liquidLensView?.clipsToBounds = self.scrollNode.view.isScrollEnabled } } @@ -1916,31 +2055,65 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { textPanelWidth = layout.size.width - panelSideInset * 2.0 } + self.updateViews(transition: .immediate) + let glassPanelHeight: CGFloat = 62.0 let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom)) if case .glass = self.panelStyle { let backgroundView: GlassBackgroundView - if let current = self.backgroundView { - backgroundView = current + let liquidLensView: LiquidLensView + if let currentBackground = self.backgroundView, let currentLens = self.liquidLensView { + backgroundView = currentBackground + liquidLensView = currentLens } else { backgroundView = GlassBackgroundView() - self.containerNode.view.addSubview(backgroundView) - self.containerNode.view.addSubview(self.scrollNode.view) self.backgroundView = backgroundView + + liquidLensView = LiquidLensView(kind: .noContainer) + liquidLensView.clipsToBounds = true + liquidLensView.layer.cornerRadius = 28.0 + + self.containerNode.view.addSubview(backgroundView) + self.containerNode.view.addSubview(liquidLensView) + self.containerNode.view.addSubview(self.scrollNode.view) + self.liquidLensView = liquidLensView + + liquidLensView.contentView.addSubview(self.itemsContainer) + liquidLensView.selectedContentView.addSubview(self.selectedItemsContainer) + + self.itemsContainer.clipsToBounds = true + self.itemsContainer.layer.cornerRadius = 28.0 + self.selectedItemsContainer.clipsToBounds = true + self.selectedItemsContainer.layer.cornerRadius = 28.0 + + let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:))) + tabSelectionRecognizer.delegate = self.wrappedGestureRecognizerDelegate + self.tabSelectionRecognizer = tabSelectionRecognizer + self.scrollNode.view.addGestureRecognizer(tabSelectionRecognizer) } let panelSize = CGSize(width: isSelecting ? textPanelWidth : layout.size.width - layout.safeInsets.left - layout.safeInsets.right - panelSideInset * 2.0, height: isSelecting ? textPanelHeight - 11.0 : glassPanelHeight) - let backgroundViewColor: UIColor - if self.presentationData.theme.overallDarkAppearance { - backgroundViewColor = self.presentationData.theme.list.modalBlocksBackgroundColor.withAlphaComponent(0.55) - } else { - backgroundViewColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.75) - } - + let cornerRadius: CGFloat = isSelecting ? min(20.0, panelSize.height * 0.5) : panelSize.height * 0.5 let backgroundOriginX: CGFloat = isSelecting ? panelSideInset : floorToScreenPixels((layout.size.width - panelSize.width) / 2.0) - backgroundView.update(size: panelSize, cornerRadius: isSelecting ? 20.0 : glassPanelHeight * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: backgroundViewColor), transition: ComponentTransition(transition)) + + backgroundView.update(size: panelSize, cornerRadius: cornerRadius, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: ComponentTransition(transition)) + + let lensSideInset: CGFloat = defaultPanelSideInset + layout.safeInsets.left + let lensPanelSize = CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - lensSideInset * 2.0, height: glassPanelHeight) + self.lensParams = (lensPanelSize, cornerRadius) + self.updateLiquidLens(transition: ComponentTransition(transition)) + + transition.updatePosition(layer: liquidLensView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5)) + transition.updateBounds(layer: liquidLensView.layer, bounds: CGRect(origin: .zero, size: CGSize(width: lensPanelSize.width - 3.0 * 2.0, height: lensPanelSize.height - 3.0 * 2.0))) + transition.updatePosition(layer: backgroundView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5)) transition.updateBounds(layer: backgroundView.layer, bounds: CGRect(origin: .zero, size: panelSize)) + + let itemsContainerFrame = CGRect(origin: .zero, size: CGSize(width: lensPanelSize.width - 3.0 * 2.0, height: lensPanelSize.height - 3.0 * 2.0)) + transition.updateBounds(layer: self.itemsContainer.layer, bounds: CGRect(origin: .zero, size: itemsContainerFrame.size)) + transition.updateBounds(layer: self.selectedItemsContainer.layer, bounds: CGRect(origin: .zero, size: itemsContainerFrame.size)) + transition.updatePosition(layer: self.itemsContainer.layer, position: itemsContainerFrame.center) + transition.updatePosition(layer: self.selectedItemsContainer.layer, position: itemsContainerFrame.center) } var containerTransition: ContainedViewLayoutTransition @@ -2002,8 +2175,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { let alphaTransition = ContainedViewLayoutTransition.animated(duration: isSelecting ? 0.1 : 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0) containerTransition.updateTransformScale(node: self.scrollNode, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0) - if let backgroundView = self.backgroundView { - containerTransition.updateTransformScale(layer: backgroundView.layer, scale: isAnyButtonVisible ? 0.85 : 1.0) + + if let liquidLensView = self.liquidLensView { + alphaTransition.updateAlpha(layer: liquidLensView.layer, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0) + containerTransition.updateTransformScale(layer: liquidLensView.layer, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0) } if isSelectingUpdated { @@ -2022,7 +2197,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 1.0) let componentTransition = ComponentTransition.easeInOut(duration: 0.25) - componentTransition.animateBlur(layer: self.scrollNode.layer, fromRadius: 0.0, toRadius: 10.0) + if let liquidLensView = self.liquidLensView { + componentTransition.animateBlur(layer: liquidLensView.layer, fromRadius: 0.0, toRadius: 10.0) + transition.animatePositionAdditive(layer: liquidLensView.layer, offset: .zero, to: CGPoint(x: -27.0, y: 0.0)) + } let blurTransition = ComponentTransition.easeInOut(duration: 0.18) transition.animateTransformScale(node: textInputPanelNode.opaqueActionButtons, from: 0.01) @@ -2030,9 +2208,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { textInputPanelNode.opaqueActionButtons.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) transition.animatePosition(node: textInputPanelNode.opaqueActionButtons, from: CGPoint(x: textInputPanelNode.opaqueActionButtons.position.x, y: textInputPanelNode.opaqueActionButtons.position.y + heightDelta)) + blurTransition.animateBlur(layer: textInputPanelNode.inputModeView.layer, fromRadius: 4.0, toRadius: 0.0) + componentTransition.animateAlpha(view: textInputPanelNode.inputModeView, from: 0.0, to: 1.0) + transition.animatePositionAdditive(layer: textInputPanelNode.textPlaceholderNode.layer, offset: CGPoint(x: 6.0, y: heightDelta)) transition.animatePositionAdditive(layer: textInputPanelNode.inputModeView.layer, offset: CGPoint(x: 64.0, y: heightDelta)) - + textInputPanelNode.animateIn(transition: transition) } else { textInputPanelNode.alpha = 1.0 @@ -2054,7 +2235,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 0.0) let componentTransition = ComponentTransition.easeInOut(duration: 0.25) - componentTransition.animateBlur(layer: self.scrollNode.layer, fromRadius: 10.0, toRadius: 0.0) + if let liquidLensView = self.liquidLensView { + componentTransition.animateBlur(layer: liquidLensView.layer, fromRadius: 10.0, toRadius: 0.0) + transition.animatePositionAdditive(layer: liquidLensView.layer, offset: CGPoint(x: -27.0, y: 0.0)) + } let blurTransition = ComponentTransition.easeInOut(duration: 0.18) transition.animateTransformScale(layer: textInputPanelNode.opaqueActionButtons.layer, from: CGPoint(x: 1.0, y: 1.0), to: CGPoint(x: 0.01, y: 0.01)) @@ -2062,6 +2246,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { textInputPanelNode.opaqueActionButtons.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) transition.animatePosition(node: textInputPanelNode.opaqueActionButtons, to: CGPoint(x: textInputPanelNode.opaqueActionButtons.position.x, y: textInputPanelNode.opaqueActionButtons.position.y + heightDelta)) + blurTransition.animateBlur(layer: textInputPanelNode.inputModeView.layer, fromRadius: 0.0, toRadius: 4.0) + componentTransition.animateAlpha(view: textInputPanelNode.inputModeView, from: 1.0, to: 0.0) + transition.animatePositionAdditive(layer: textInputPanelNode.textPlaceholderNode.layer, offset: .zero, to: CGPoint(x: 6.0, y: heightDelta)) transition.animatePositionAdditive(layer: textInputPanelNode.inputModeView.layer, offset: .zero, to: CGPoint(x: 64.0, y: heightDelta)) } else { @@ -2086,8 +2273,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: self.isSelecting ? -11.0 : 0.0), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height))) } - self.updateViews(transition: .immediate) - if let progress = self.loadingProgress { let loadingProgressNode: LoadingProgressNode if let current = self.progressNode { @@ -2183,8 +2368,70 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { return containerFrame.height } + func updateItemContainers(contentOffset: CGFloat, transition: ComponentTransition) { + let transform = CATransform3DMakeTranslation(-contentOffset, 0.0, 0.0) + transition.setSublayerTransform(view: self.itemsContainer, transform: transform) + transition.setSublayerTransform(view: self.selectedItemsContainer, transform: transform) + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + if self.selectionGestureState != nil { + self.selectionGestureState = nil + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + } + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.updateItemContainers(contentOffset: scrollView.contentOffset.x, transition: .immediate) self.updateViews(transition: .immediate) + self.updateLiquidLens(transition: .immediate) + } + + private var skipLensUpdate = false + private var lensParams: (panelSize: CGSize, cornerRadius: CGFloat)? + func updateLiquidLens(transition: ComponentTransition) { + guard !self.skipLensUpdate, let liquidLensView = self.liquidLensView, let (panelSize, _) = self.lensParams else { + return + } + + var selectionFrame = CGRect() + if self.selectedIndex >= 0 && self.selectedIndex < self.buttons.count, let itemView = self.itemViews[self.buttons[self.selectedIndex].key], let itemSize = self.itemSizes[self.buttons[self.selectedIndex].key] { + let contentOffset = self.scrollNode.view.contentOffset.x + selectionFrame = CGRect(origin: CGPoint(x: itemView.center.x - itemSize.width / 2.0 - contentOffset, y: itemView.frame.minY), size: itemSize) + } + + var lensSelection: (x: CGFloat, width: CGFloat) + if let selectionGestureState = self.selectionGestureState, selectionGestureState.isLifted { + lensSelection = (selectionGestureState.currentX, selectionFrame.width) + } else { + lensSelection = (selectionFrame.minX, selectionFrame.width) + } + + if !self.scrollNode.view.isScrollEnabled { + lensSelection.x = max(0.0, min(lensSelection.x, panelSize.width - lensSelection.width)) + } + + var isLifted = self.selectionGestureState?.isLifted == true || self.lensIsLifted + if let widthClass = self.validLayout?.metrics.widthClass, case .regular = widthClass { + isLifted = false + } + + let inset: CGFloat = 3.0 + liquidLensView.update(size: CGSize(width: panelSize.width - inset * 2.0, height: panelSize.height - inset * 2.0), cornerRadius: 28.0, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: panelSize.height - inset * 2.0), inset: 0.0, isDark: self.presentationData.theme.overallDarkAppearance, isLifted: isLifted, isCollapsed: self.isSelecting || self.buttons.count < 2, transition: transition) + } + + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer == self.tabSelectionRecognizer { + return true + } + return super.gestureRecognizerShouldBegin(gestureRecognizer) + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + return true + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true } } - diff --git a/submodules/AuthorizationUI/BUILD b/submodules/AuthorizationUI/BUILD index 5a48a9b3..cc280f15 100644 --- a/submodules/AuthorizationUI/BUILD +++ b/submodules/AuthorizationUI/BUILD @@ -47,6 +47,7 @@ swift_library( "//submodules/TelegramUI/Components/Premium/PremiumCoinComponent", "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/Utils/DeviceModel", + "//submodules/PresentationDataUtils", ], visibility = [ "//visibility:public", diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift index 6cd8ce98..72f1db42 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift @@ -5,7 +5,9 @@ import AsyncDisplayKit import SwiftSignalKit import TelegramCore import TelegramPresentationData +import PresentationDataUtils import ProgressNavigationButtonNode +import AccountContext public final class AuthorizationSequenceCodeEntryController: ViewController { private var controllerNode: AuthorizationSequenceCodeEntryControllerNode { @@ -14,6 +16,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { private var validLayout: ContainerViewLayout? + private let sharedContext: SharedAccountContext private let strings: PresentationStrings private let theme: PresentationTheme @@ -45,7 +48,8 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { return self.data?.6 ?? false } - public init(presentationData: PresentationData, back: @escaping () -> Void) { + public init(sharedContext: SharedAccountContext, presentationData: PresentationData, back: @escaping () -> Void) { + self.sharedContext = sharedContext self.strings = presentationData.strings self.theme = presentationData.theme @@ -61,11 +65,14 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { return false } self.navigationBar?.backPressed = { [weak self] in + guard let self else { + return + } let text: String let proceed: String let stop: String - if let (_, _, type, _, _, _, _) = self?.data, case .email = type { + if let (_, _, type, _, _, _, _) = self.data, case .email = type { text = presentationData.strings.Login_CancelEmailVerification proceed = presentationData.strings.Login_CancelEmailVerificationContinue stop = presentationData.strings.Login_CancelEmailVerificationStop @@ -75,7 +82,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController { stop = presentationData.strings.Login_CancelPhoneVerificationStop } - self?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: proceed, action: { + self.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: proceed, action: { }), TextAlertAction(type: .defaultAction, title: stop, action: { back() })]), in: .window(.root)) diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index b6d861d6..bc640f69 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -9,6 +9,7 @@ import MtProtoKit import MessageUI import CoreTelephony import TelegramPresentationData +import PresentationDataUtils import TextFormat import AccountContext import CountrySelectionUI @@ -33,7 +34,7 @@ private enum InnerState: Equatable { public final class AuthorizationSequenceController: NavigationController, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { static func navigationBarTheme(_ theme: PresentationTheme) -> NavigationBarTheme { - return NavigationBarTheme(buttonColor: theme.intro.accentTextColor, disabledButtonColor: theme.intro.disabledTextColor, primaryTextColor: theme.intro.primaryTextColor, backgroundColor: .clear, opaqueBackgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: theme.rootController.navigationBar.badgeBackgroundColor, badgeStrokeColor: theme.rootController.navigationBar.badgeStrokeColor, badgeTextColor: theme.rootController.navigationBar.badgeTextColor) + return NavigationBarTheme(overallDarkAppearance: theme.overallDarkAppearance, buttonColor: theme.chat.inputPanel.panelControlColor, disabledButtonColor: theme.intro.disabledTextColor, primaryTextColor: theme.intro.primaryTextColor, backgroundColor: .clear, opaqueBackgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: theme.rootController.navigationBar.badgeBackgroundColor, badgeStrokeColor: theme.rootController.navigationBar.badgeStrokeColor, badgeTextColor: theme.rootController.navigationBar.badgeTextColor, edgeEffectColor: .clear, style: .glass) } private let sharedContext: SharedAccountContext @@ -247,7 +248,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth let carrier = CTCarrier() let mnc = carrier.mobileNetworkCode ?? "none" - AuthorizationSequenceController.presentEmailComposeController(address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_InvalidPhoneEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_InvalidPhoneEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData) + AuthorizationSequenceController.presentEmailComposeController(sharedContext: strongSelf.sharedContext, address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_InvalidPhoneEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_InvalidPhoneEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData) })) case .phoneLimitExceeded: text = strongSelf.presentationData.strings.Login_PhoneFloodError @@ -273,7 +274,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth let carrier = CTCarrier() let mnc = carrier.mobileNetworkCode ?? "none" - AuthorizationSequenceController.presentEmailComposeController(address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_PhoneBannedEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_PhoneBannedEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData) + AuthorizationSequenceController.presentEmailComposeController(sharedContext: strongSelf.sharedContext, address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_PhoneBannedEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_PhoneBannedEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData) })) case let .generic(info): text = strongSelf.presentationData.strings.Login_UnknownError @@ -295,7 +296,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth errorString = "unknown" } - AuthorizationSequenceController.presentEmailComposeController(address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_PhoneGenericEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_PhoneGenericEmailBody(formattedNumber, errorString, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData) + AuthorizationSequenceController.presentEmailComposeController(sharedContext: strongSelf.sharedContext, address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_PhoneGenericEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_PhoneGenericEmailBody(formattedNumber, errorString, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData) })) case .timeout: text = strongSelf.presentationData.strings.Login_NetworkError @@ -307,7 +308,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth controller.present(strongSelf.sharedContext.makeProxySettingsController(sharedContext: strongSelf.sharedContext, account: strongSelf.account), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) })) } - (controller.navigationController as? NavigationController)?.presentOverlay(controller: standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: actions), inGlobal: true, blockInteraction: true) + (controller.navigationController as? NavigationController)?.presentOverlay(controller: textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: actions), inGlobal: true, blockInteraction: true) controller.dismissConfirmation() } @@ -352,7 +353,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth text = strongSelf.presentationData.strings.Login_UnknownError } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } })) @@ -376,7 +377,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth if let currentController = currentController { controller = currentController } else { - controller = AuthorizationSequenceCodeEntryController(presentationData: self.presentationData, back: { [weak self] in + controller = AuthorizationSequenceCodeEntryController(sharedContext: self.sharedContext, presentationData: self.presentationData, back: { [weak self] in guard let strongSelf = self else { return } @@ -451,7 +452,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth let _ = self.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)).startStandalone() } - controller.presentInGlobalOverlay(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})])) + controller.presentInGlobalOverlay(textAlertController(sharedContext: self.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})])) } }) ) @@ -510,7 +511,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth controller.resetCode() } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } } @@ -623,7 +624,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth controller.resetCode() } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } } @@ -645,7 +646,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth let mnc = carrier.mobileNetworkCode ?? "none" let _ = strongSelf.engine.auth.reportMissingCode(phoneNumber: number, phoneCodeHash: phoneCodeHash, mnc: mnc).start() - AuthorizationSequenceController.presentDidNotGetCodeUI(controller: controller, presentationData: strongSelf.presentationData, phoneNumber: number, mnc: mnc) + AuthorizationSequenceController.presentDidNotGetCodeUI(sharedContext: strongSelf.sharedContext, controller: controller, presentationData: strongSelf.presentationData, phoneNumber: number, mnc: mnc) } } else { controller?.inProgress = true @@ -681,7 +682,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth text = strongSelf.presentationData.strings.Login_NetworkError } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: actions), in: .window(.root)) + controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: actions), in: .window(.root)) } })) } @@ -780,7 +781,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth text = strongSelf.presentationData.strings.Login_EmailNotAllowedError } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } }, completed: { controller?.inProgress = false @@ -827,7 +828,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth switch authorization.credential { case let appleIdCredential as ASAuthorizationAppleIDCredential: guard let tokenData = appleIdCredential.identityToken, let token = String(data: tokenData, encoding: .utf8) else { - lastController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + lastController?.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return } @@ -850,7 +851,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth case .emailNotAllowed: text = strongSelf.presentationData.strings.Login_EmailNotAllowedError } - lastController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + lastController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } })) } else { @@ -891,7 +892,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth text = strongSelf.presentationData.strings.Login_InvalidEmailAddressError } - lastController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + lastController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } }) @@ -907,7 +908,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth guard let lastController = self.viewControllers.last as? ViewController else { return } - lastController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: error.localizedDescription, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + lastController.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: error.localizedDescription, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } @available(iOS 13.0, *) @@ -927,7 +928,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth if let currentController = currentController { controller = currentController } else { - controller = AuthorizationSequencePasswordEntryController(presentationData: self.presentationData, back: { [weak self] in + controller = AuthorizationSequencePasswordEntryController(sharedContext: self.sharedContext, presentationData: self.presentationData, back: { [weak self] in guard let strongSelf = self else { return } @@ -954,7 +955,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth text = strongSelf.presentationData.strings.Login_UnknownError } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) controller.passwordIsInvalid() } } @@ -988,14 +989,14 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth strongController.inProgress = false - strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) strongController.didForgotWithNoRecovery = true })) } } controller.reset = { [weak self, weak controller] in if let strongSelf = self, let strongController = controller { - strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: suggestReset ? strongSelf.presentationData.strings.TwoStepAuth_RecoveryFailed : strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [ + strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: suggestReset ? strongSelf.presentationData.strings.TwoStepAuth_RecoveryFailed : strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [ TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Login_ResetAccountProtected_Reset, action: { if let strongSelf = self, let strongController = controller { @@ -1015,7 +1016,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth case .limitExceeded: text = strongSelf.presentationData.strings.Login_ResetAccountProtected_LimitExceeded } - strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } })) } @@ -1082,7 +1083,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth }) controller.reset = { [weak self, weak controller] in if let strongSelf = self, let strongController = controller { - strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_ResetAccountConfirmation, actions: [ + strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_ResetAccountConfirmation, actions: [ TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Login_ResetAccountProtected_Reset, action: { if let strongSelf = self, let strongController = controller { @@ -1102,7 +1103,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth case .limitExceeded: text = strongSelf.presentationData.strings.Login_ResetAccountProtected_LimitExceeded } - strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } })) } @@ -1132,7 +1133,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth if let currentController = currentController { controller = currentController } else { - controller = AuthorizationSequenceSignUpController(presentationData: self.presentationData, back: { [weak self] in + controller = AuthorizationSequenceSignUpController(sharedContext: self.sharedContext, presentationData: self.presentationData, back: { [weak self] in guard let strongSelf = self else { return } @@ -1231,7 +1232,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth text = strongSelf.presentationData.strings.Login_UnknownError } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } })) @@ -1379,7 +1380,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } } - static func presentEmailComposeController(address: String, subject: String, body: String, from controller: ViewController, presentationData: PresentationData) { + static func presentEmailComposeController(sharedContext: SharedAccountContext, address: String, subject: String, body: String, from controller: ViewController, presentationData: PresentationData) { if MFMailComposeViewController.canSendMail() { final class ComposeDelegate: NSObject, MFMailComposeViewControllerDelegate { @objc func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { @@ -1398,7 +1399,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth controller.view.window?.rootViewController?.present(composeController, animated: true, completion: nil) } else { - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_EmailNotConfiguredError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + controller.present(textAlertController(sharedContext: sharedContext, title: nil, text: presentationData.strings.Login_EmailNotConfiguredError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } @@ -1451,6 +1452,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } public static func presentDidNotGetCodeUI( + sharedContext: SharedAccountContext, controller: ViewController, presentationData: PresentationData, phoneNumber: String, @@ -1470,6 +1472,6 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth emailBody.append("Locale: \(locale)\n") emailBody.append("MNC: \(mnc)") - AuthorizationSequenceController.presentEmailComposeController(address: "sms@telegram.org", subject: presentationData.strings.Login_EmailCodeSubject(formattedNumber).string, body: emailBody, from: controller, presentationData: presentationData) + AuthorizationSequenceController.presentEmailComposeController(sharedContext: sharedContext, address: "sms@telegram.org", subject: presentationData.strings.Login_EmailCodeSubject(formattedNumber).string, body: emailBody, from: controller, presentationData: presentationData) } } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryController.swift index 000a0a49..913678b1 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryController.swift @@ -3,7 +3,9 @@ import UIKit import Display import AsyncDisplayKit import TelegramPresentationData +import PresentationDataUtils import ProgressNavigationButtonNode +import AccountContext final class AuthorizationSequencePasswordEntryController: ViewController { private var controllerNode: AuthorizationSequencePasswordEntryControllerNode { @@ -12,6 +14,7 @@ final class AuthorizationSequencePasswordEntryController: ViewController { private var validLayout: ContainerViewLayout? + private let sharedContext: SharedAccountContext private let presentationData: PresentationData var loginWithPassword: ((String) -> Void)? @@ -40,7 +43,8 @@ final class AuthorizationSequencePasswordEntryController: ViewController { } } - init(presentationData: PresentationData, back: @escaping () -> Void) { + init(sharedContext: SharedAccountContext, presentationData: PresentationData, back: @escaping () -> Void) { + self.sharedContext = sharedContext self.presentationData = presentationData super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) @@ -153,10 +157,8 @@ final class AuthorizationSequencePasswordEntryController: ViewController { } func forgotPressed() { - /*if self.suggestReset { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.TwoStepAuth_RecoveryFailed, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - } else*/ if self.didForgotWithNoRecovery { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + if self.didForgotWithNoRecovery { + self.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } else { self.forgot?() } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift index 2a7c258b..e94a8f98 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift @@ -107,7 +107,7 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT self.codeField.textField.tintColor = self.theme.list.itemAccentColor self.codeField.textField.accessibilityHint = self.strings.Login_VoiceOver_Password - self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0) + self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), glass: true, height: 50.0, cornerRadius: 50.0 * 0.5) self.proceedNode.progressType = .embedded self.proceedNode.isEnabled = false diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift index f261be57..61715e95 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift @@ -215,7 +215,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { ).string let presentationData = component.presentationData - AuthorizationSequenceController.presentEmailComposeController(address: component.supportEmailAddress, subject: environment.strings.Login_PhonePaidEmailSubject, body: body, from: controller, presentationData: presentationData) + AuthorizationSequenceController.presentEmailComposeController(sharedContext: component.sharedContext, address: component.supportEmailAddress, subject: environment.strings.Login_PhonePaidEmailSubject, body: body, from: controller, presentationData: presentationData) } func update(component: AuthorizationSequencePaymentScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift index 54855476..c28585b3 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift @@ -6,6 +6,7 @@ import SwiftSignalKit import TelegramCore import Postbox import TelegramPresentationData +import PresentationDataUtils import ProgressNavigationButtonNode import AccountContext import CountrySelectionUI @@ -91,7 +92,7 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF } if !otherAccountPhoneNumbers.1.isEmpty { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed)) } if let countriesConfiguration { @@ -173,7 +174,7 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF self.controllerNode.selectCountryCode = { [weak self] in if let strongSelf = self { - let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme) + let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, glass: true) controller.completeWithCountryCode = { code, name in if let strongSelf = self, let currentData = strongSelf.currentData { strongSelf.updateData(countryCode: Int32(code), countryName: name, number: currentData.2) @@ -404,7 +405,7 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF })) } actions.append(TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})) - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_PhoneNumberAlreadyAuthorized, actions: actions), in: .window(.root)) + self.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.Login_PhoneNumberAlreadyAuthorized, actions: actions), in: .window(.root)) } else { if let validLayout = self.validLayout, validLayout.size.width > 320.0 { let (code, formattedNumber) = self.controllerNode.formattedCodeAndNumber @@ -425,7 +426,7 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF strongSelf.loginWithNumber?(strongSelf.controllerNode.currentNumber, strongSelf.controllerNode.syncContacts) } })) - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: logInNumber, text: self.presentationData.strings.Login_PhoneNumberConfirmation, actions: actions), in: .window(.root)) + self.present(textAlertController(sharedContext: self.sharedContext, title: logInNumber, text: self.presentationData.strings.Login_PhoneNumberConfirmation, actions: actions), in: .window(.root)) } } } else { diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift index 69ca7fc7..4b440530 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift @@ -423,7 +423,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.phoneAndCountryNode = PhoneAndCountryNode(strings: strings, theme: theme) - self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0) + self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), glass: true, height: 50.0, cornerRadius: 50 * 0.5) self.proceedNode.progressType = .embedded self.proceedNode.isEnabled = false @@ -857,7 +857,7 @@ final class PhoneConfirmationController: ViewController { self.cancelButton.accessibilityTraits = [.button] self.cancelButton.accessibilityLabel = strings.Login_Edit - self.proceedNode = SolidRoundedButtonNode(title: strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 11.0) + self.proceedNode = SolidRoundedButtonNode(title: strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: theme), glass: true, height: 50.0, cornerRadius: 50.0 * 0.5) self.proceedNode.progressType = .embedded let font = Font.with(size: 20.0, design: .regular, traits: [.monospacedNumbers]) diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift index 8c8fdd8d..2ec0d705 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift @@ -5,6 +5,7 @@ import AsyncDisplayKit import SwiftSignalKit import TelegramCore import TelegramPresentationData +import PresentationDataUtils import LegacyComponents import ProgressNavigationButtonNode import ImageCompression @@ -13,6 +14,7 @@ import Postbox import TextFormat import MoreButtonNode import ContextUI +import AccountContext final class AuthorizationSequenceSignUpController: ViewController { private var controllerNode: AuthorizationSequenceSignUpControllerNode { @@ -23,6 +25,7 @@ final class AuthorizationSequenceSignUpController: ViewController { private let moreButtonNode: MoreButtonNode + private let sharedContext: SharedAccountContext private let presentationData: PresentationData private let back: () -> Void @@ -46,7 +49,8 @@ final class AuthorizationSequenceSignUpController: ViewController { } } - init(presentationData: PresentationData, back: @escaping () -> Void, displayCancel: Bool) { + init(sharedContext: SharedAccountContext, presentationData: PresentationData, back: @escaping () -> Void, displayCancel: Bool) { + self.sharedContext = sharedContext self.presentationData = presentationData self.back = back @@ -68,7 +72,7 @@ final class AuthorizationSequenceSignUpController: ViewController { guard let strongSelf = self else { return } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_CancelSignUpConfirmation, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Login_CancelPhoneVerificationContinue, action: { + strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: presentationData.strings.Login_CancelSignUpConfirmation, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Login_CancelPhoneVerificationContinue, action: { }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Login_CancelPhoneVerificationStop, action: { back() })]), in: .window(.root)) @@ -92,7 +96,7 @@ final class AuthorizationSequenceSignUpController: ViewController { } @objc private func cancelPressed() { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_CancelSignUpConfirmation, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Login_CancelPhoneVerificationContinue, action: { + self.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.Login_CancelSignUpConfirmation, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Login_CancelPhoneVerificationContinue, action: { }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Login_CancelPhoneVerificationStop, action: { [weak self] in self?.back() })]), in: .window(.root)) diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift index 044c5c70..5f517176 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift @@ -73,10 +73,12 @@ public final class AuthorizationSequenceSplashController: ViewController { self.controller = RMIntroViewController(backgroundColor: theme.list.plainBackgroundColor, primaryColor: theme.list.itemPrimaryTextColor, buttonColor: theme.intro.startButtonColor, accentColor: theme.list.itemAccentColor, regularDotColor: theme.intro.dotColor, highlightedDotColor: theme.list.itemAccentColor, suggestedLocalizationSignal: localizationSignal) - self.startButton = SolidRoundedButtonNode(title: "Start Messaging", theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 13.0, isShimmering: true) + self.startButton = SolidRoundedButtonNode(title: "Start Messaging", theme: SolidRoundedButtonTheme(theme: theme), glass: true, height: 50.0, cornerRadius: 50.0 * 0.5, isShimmering: true) super.init(navigationBarPresentationData: nil) + self._hasGlassStyle = true + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift index c2c4a9c9..84ff2287 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift @@ -114,7 +114,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.imageNode) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPriceItem.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPriceItem.swift index a88ecb09..43c32de5 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutPriceItem.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutPriceItem.swift @@ -113,7 +113,7 @@ class BotCheckoutPriceItemNode: ListViewItemNode { self.maskNode = ASImageNode() self.maskNode.isUserInteractionEnabled = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.titleNode) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutTipItem.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutTipItem.swift index 7989f744..7b177db9 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutTipItem.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutTipItem.swift @@ -509,7 +509,7 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate { self.maskNode = ASImageNode() self.maskNode.isUserInteractionEnabled = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.backgroundNode) diff --git a/submodules/BrowserUI/BUILD b/submodules/BrowserUI/BUILD index e031d767..472c7550 100644 --- a/submodules/BrowserUI/BUILD +++ b/submodules/BrowserUI/BUILD @@ -50,6 +50,13 @@ swift_library( "//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/Utils/DeviceModel", "//submodules/LegacyMediaPickerUI", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/SearchInputPanelComponent", + "//submodules/TelegramUI/Components/GlassControls", ], visibility = [ "//visibility:public", diff --git a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift index da83dddb..00915444 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressBarComponent.swift @@ -9,6 +9,9 @@ import AccountContext import BundleIconComponent import MultilineTextComponent import UrlEscaping +import GlassBackgroundComponent +import GlassBarButtonComponent +import EdgeEffect final class AddressBarContentComponent: Component { public typealias EnvironmentType = BrowserNavigationBarEnvironment @@ -19,6 +22,8 @@ final class AddressBarContentComponent: Component { let url: String let isSecure: Bool let isExpanded: Bool + let readingProgress: CGFloat + let loadingProgress: Double? let performAction: ActionSlot init( @@ -28,6 +33,8 @@ final class AddressBarContentComponent: Component { url: String, isSecure: Bool, isExpanded: Bool, + readingProgress: CGFloat, + loadingProgress: Double?, performAction: ActionSlot ) { self.theme = theme @@ -36,6 +43,8 @@ final class AddressBarContentComponent: Component { self.url = url self.isSecure = isSecure self.isExpanded = isExpanded + self.readingProgress = readingProgress + self.loadingProgress = loadingProgress self.performAction = performAction } @@ -58,6 +67,12 @@ final class AddressBarContentComponent: Component { if lhs.isExpanded != rhs.isExpanded { return false } + if lhs.readingProgress != rhs.readingProgress { + return false + } + if lhs.loadingProgress != rhs.loadingProgress { + return false + } return true } @@ -85,6 +100,8 @@ final class AddressBarContentComponent: Component { var isSecure: Bool var collapseFraction: CGFloat var isTablet: Bool + var readingProgress: CGFloat + var loadingProgress: Double? static func ==(lhs: Params, rhs: Params) -> Bool { if lhs.theme !== rhs.theme { @@ -111,26 +128,33 @@ final class AddressBarContentComponent: Component { if lhs.isTablet != rhs.isTablet { return false } + if lhs.readingProgress != rhs.readingProgress { + return false + } + if lhs.loadingProgress != rhs.loadingProgress { + return false + } return true } } private let activated: (Bool) -> Void = { _ in } private let deactivated: (Bool) -> Void = { _ in } - - private let backgroundLayer: SimpleLayer - - private let iconView: UIImageView + + private let backgroundView: GlassBackgroundView private let clearIconView: UIImageView private let clearIconButton: HighlightTrackingButton - private let cancelButtonTitle: ComponentView - private let cancelButton: HighlightTrackingButton + private let cancelButton = ComponentView() private var placeholderContent = ComponentView() private var titleContent = ComponentView() + private let clippingView = UIView() + private var loadingProgress = ComponentView() + private var readingProgressView = UIView() + private var textFrame: CGRect? private var textField: TextField? @@ -144,50 +168,30 @@ final class AddressBarContentComponent: Component { } init() { - self.backgroundLayer = SimpleLayer() - - self.iconView = UIImageView() + self.backgroundView = GlassBackgroundView() self.clearIconView = UIImageView() self.clearIconButton = HighlightableButton() self.clearIconView.isHidden = false self.clearIconButton.isHidden = false - - self.cancelButtonTitle = ComponentView() - self.cancelButton = HighlightTrackingButton() - + super.init(frame: CGRect()) - self.layer.addSublayer(self.backgroundLayer) + self.clippingView.clipsToBounds = true - self.addSubview(self.iconView) - self.addSubview(self.clearIconView) - self.addSubview(self.clearIconButton) + self.addSubview(self.backgroundView) + self.backgroundView.contentView.addSubview(self.clippingView) + self.clippingView.addSubview(self.readingProgressView) + + self.backgroundView.contentView.addSubview(self.clearIconView) + self.backgroundView.contentView.addSubview(self.clearIconButton) - self.addSubview(self.cancelButton) self.clipsToBounds = true let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) self.tapRecognizer = tapRecognizer self.addGestureRecognizer(tapRecognizer) - - self.cancelButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.layer.removeAnimation(forKey: "opacity") - cancelButtonTitleView.alpha = 0.4 - } - } else { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.alpha = 1.0 - cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } - self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside) - + self.clearIconButton.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { @@ -263,11 +267,16 @@ final class AddressBarContentComponent: Component { self.placeholderContent.view?.isHidden = !text.isEmpty if let params = self.params { - self.update(theme: params.theme, strings: params.strings, size: params.size, isActive: params.isActive, title: params.title, isSecure: params.isSecure, collapseFraction: params.collapseFraction, isTablet: params.isTablet, transition: .immediate) + self.update(theme: params.theme, strings: params.strings, size: params.size, isActive: params.isActive, title: params.title, isSecure: params.isSecure, collapseFraction: params.collapseFraction, isTablet: params.isTablet, readingProgress: params.readingProgress, loadingProgress: params.loadingProgress, transition: .immediate) } } - func update(component: AddressBarContentComponent, availableSize: CGSize, environment: Environment, transition: ComponentTransition) -> CGSize { + func update( + component: AddressBarContentComponent, + availableSize: CGSize, + environment: Environment, + transition: ComponentTransition + ) -> CGSize { let collapseFraction = environment[BrowserNavigationBarEnvironment.self].fraction let wasExpanded = self.component?.isExpanded ?? false @@ -282,12 +291,36 @@ final class AddressBarContentComponent: Component { let isActive = self.textField?.isFirstResponder ?? false let title = getDisplayUrl(component.url, hostOnly: true) - self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, isTablet: component.metrics.isTablet, transition: transition) + self.update( + theme: component.theme, + strings: component.strings, + size: availableSize, + isActive: isActive, + title: title.lowercased(), + isSecure: component.isSecure, + collapseFraction: collapseFraction, + isTablet: component.metrics.isTablet, + readingProgress: component.readingProgress, + loadingProgress: component.loadingProgress, + transition: transition + ) return availableSize } - public func update(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, isActive: Bool, title: String, isSecure: Bool, collapseFraction: CGFloat, isTablet: Bool, transition: ComponentTransition) { + public func update( + theme: PresentationTheme, + strings: PresentationStrings, + size: CGSize, + isActive: Bool, + title: String, + isSecure: Bool, + collapseFraction: CGFloat, + isTablet: Bool, + readingProgress: CGFloat, + loadingProgress: Double?, + transition: ComponentTransition + ) { let params = Params( theme: theme, strings: strings, @@ -296,7 +329,9 @@ final class AddressBarContentComponent: Component { title: title, isSecure: isSecure, collapseFraction: collapseFraction, - isTablet: isTablet + isTablet: isTablet, + readingProgress: readingProgress, + loadingProgress: loadingProgress ) if self.params == params { @@ -306,8 +341,6 @@ final class AddressBarContentComponent: Component { let isActiveWithText = self.component?.isExpanded ?? false if self.params?.theme !== theme { - self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Lock"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.iconView.tintColor = theme.rootController.navigationSearchBar.inputIconColor self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate) self.clearIconView.tintColor = theme.rootController.navigationSearchBar.inputClearButtonColor } @@ -315,57 +348,9 @@ final class AddressBarContentComponent: Component { self.params = params let sideInset: CGFloat = 10.0 - let inputHeight: CGFloat = 36.0 + let inputHeight: CGFloat = 44.0 let topInset: CGFloat = (size.height - inputHeight) / 2.0 - self.backgroundLayer.backgroundColor = theme.rootController.navigationSearchBar.inputFillColor.cgColor - self.backgroundLayer.cornerRadius = 10.5 - transition.setAlpha(layer: self.backgroundLayer, alpha: max(0.0, min(1.0, 1.0 - collapseFraction * 1.5))) - - let cancelTextSize = self.cancelButtonTitle.update( - transition: .immediate, - component: AnyComponent(Text( - text: strings.Common_Cancel, - font: Font.regular(17.0), - color: theme.rootController.navigationBar.accentTextColor - )), - environment: {}, - containerSize: CGSize(width: size.width - 32.0, height: 100.0) - ) - - let cancelButtonSpacing: CGFloat = 8.0 - - var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight)) - if isActiveWithText && !isTablet { - backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing - } - transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame) - - transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height))) - self.cancelButton.isUserInteractionEnabled = isActiveWithText && !isTablet - - let textX: CGFloat = backgroundFrame.minX + sideInset - let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) - - let placeholderSize = self.placeholderContent.update( - transition: transition, - component: AnyComponent( - Text(text: strings.WebBrowser_AddressPlaceholder, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) - ), - environment: {}, - containerSize: size - ) - if let placeholderContentView = self.placeholderContent.view { - if placeholderContentView.superview == nil { - placeholderContentView.alpha = 0.0 - placeholderContentView.isHidden = true - self.addSubview(placeholderContentView) - } - let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.midY - placeholderSize.height / 2.0), size: placeholderSize) - transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame) - transition.setAlpha(view: placeholderContentView, alpha: isActiveWithText ? 1.0 : 0.0) - } - let titleSize = self.titleContent.update( transition: transition, component: AnyComponent( @@ -379,51 +364,96 @@ final class AddressBarContentComponent: Component { environment: {}, containerSize: CGSize(width: size.width - 36.0, height: size.height) ) - var titleContentFrame = CGRect(origin: CGPoint(x: isActiveWithText ? textFrame.minX : backgroundFrame.midX - titleSize.width / 2.0, y: backgroundFrame.midY - titleSize.height / 2.0), size: titleSize) - if isSecure && !isActiveWithText { - titleContentFrame.origin.x += 7.0 + + let expandedBackgroundWidth = size.width - 14.0 * 2.0 + let collapsedBackgroundWidth = titleSize.width + 32.0 + var backgroundSize = CGSize(width: expandedBackgroundWidth * (1.0 - collapseFraction) + collapsedBackgroundWidth * collapseFraction, height: 44.0) + + let cancelButtonSpacing: CGFloat = 8.0 + let cancelSize = self.cancelButton.update( + transition: transition, + component: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent(name: "Navigation/Close", tintColor: theme.chat.inputPanel.panelControlColor) + )), + action: { [weak self] _ in + self?.cancelPressed() + } + ) + ), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + + if isActiveWithText && !isTablet { + backgroundSize.width -= cancelSize.width + cancelButtonSpacing + 4.0 } - var titleSizeChanged = false + self.backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + + var backgroundFrame = CGRect(origin: CGPoint(x: floor((size.width - backgroundSize.width) / 2.0), y: topInset), size: backgroundSize) + if isActiveWithText && !isTablet { + backgroundFrame.origin.x = 16.0 + } + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + + transition.setFrame(view: self.clippingView, frame: CGRect(origin: .zero, size: backgroundFrame.size)) + transition.setCornerRadius(layer: self.clippingView.layer, cornerRadius: backgroundFrame.size.height * 0.5) + + let textX: CGFloat = sideInset + let textFrame = CGRect(origin: CGPoint(x: textX, y: 0.0), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) + + let placeholderSize = self.placeholderContent.update( + transition: transition, + component: AnyComponent( + Text(text: strings.WebBrowser_AddressPlaceholder, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) + ), + environment: {}, + containerSize: size + ) + if let placeholderContentView = self.placeholderContent.view { + if placeholderContentView.superview == nil { + placeholderContentView.alpha = 0.0 + placeholderContentView.isHidden = true + self.backgroundView.contentView.addSubview(placeholderContentView) + } + let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.size.height / 2.0 - placeholderSize.height / 2.0), size: placeholderSize) + transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame) + transition.setAlpha(view: placeholderContentView, alpha: isActiveWithText ? 1.0 : 0.0) + } + + let titleContentFrame = CGRect(origin: CGPoint(x: isActiveWithText ? textFrame.minX : backgroundFrame.width / 2.0 - titleSize.width / 2.0, y: backgroundFrame.height / 2.0 - titleSize.height / 2.0), size: titleSize) if let titleContentView = self.titleContent.view { if titleContentView.superview == nil { - self.addSubview(titleContentView) - } - if titleContentView.frame.width != titleContentFrame.size.width { - titleSizeChanged = true + self.backgroundView.contentView.addSubview(titleContentView) } transition.setPosition(view: titleContentView, position: titleContentFrame.center) titleContentView.bounds = CGRect(origin: .zero, size: titleContentFrame.size) transition.setAlpha(view: titleContentView, alpha: isActiveWithText ? 0.0 : 1.0) } - - if let image = self.iconView.image { - let iconFrame = CGRect(origin: CGPoint(x: titleContentFrame.minX - image.size.width - 3.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - var iconTransition = transition - if titleSizeChanged { - iconTransition = .immediate - } - iconTransition.setFrame(view: self.iconView, frame: iconFrame) - transition.setAlpha(view: self.iconView, alpha: isActiveWithText || !isSecure ? 0.0 : 1.0) - } - + if let image = self.clearIconView.image { - let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) + let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.width - image.size.width - 4.0, y: floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) transition.setFrame(view: self.clearIconView, frame: iconFrame) transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0)) transition.setAlpha(view: self.clearIconView, alpha: isActiveWithText ? 1.0 : 0.0) self.clearIconButton.isUserInteractionEnabled = isActiveWithText } - if let cancelButtonTitleComponentView = self.cancelButtonTitle.view { - if cancelButtonTitleComponentView.superview == nil { - self.addSubview(cancelButtonTitleComponentView) - cancelButtonTitleComponentView.isUserInteractionEnabled = false + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) } - transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) - transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText && !isTablet ? 1.0 : 0.0) + transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelSize.height) / 2.0)), size: cancelSize)) + transition.setAlpha(view: cancelButtonView, alpha: isActiveWithText && !isTablet ? 1.0 : 0.0) } - let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height)) + let textFieldFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: CGSize(width: backgroundFrame.width - sideInset, height: backgroundFrame.height)) let textField: TextField if let current = self.textField { @@ -434,7 +464,7 @@ final class AddressBarContentComponent: Component { textField.autocorrectionType = .no textField.keyboardType = .URL textField.returnKeyType = .go - self.insertSubview(textField, belowSubview: self.clearIconView) + self.backgroundView.contentView.insertSubview(textField, belowSubview: self.clearIconView) self.textField = textField textField.delegate = self @@ -450,9 +480,34 @@ final class AddressBarContentComponent: Component { } textField.textColor = theme.rootController.navigationSearchBar.inputTextColor - transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideInset - 32.0, height: backgroundFrame.height))) + transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: sideInset, y: -UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideInset - 32.0, height: backgroundFrame.height))) transition.setAlpha(view: textField, alpha: isActiveWithText ? 1.0 : 0.0) textField.isUserInteractionEnabled = isActiveWithText + + + let loadingProgressInset: CGFloat = 12.0 + let loadingProgressSize = CGSize(width: backgroundSize.width - loadingProgressInset * 2.0, height: 2.0) + let _ = self.loadingProgress.update( + transition: transition, + component: AnyComponent(LoadingProgressComponent( + color: theme.rootController.navigationBar.accentTextColor, + height: loadingProgressSize.height, + value: params.loadingProgress ?? 0.0 + )), + environment: {}, + containerSize: loadingProgressSize + ) + if let loadingProgressView = self.loadingProgress.view { + if loadingProgressView.superview == nil { + self.clippingView.addSubview(loadingProgressView) + } + transition.setFrame(view: loadingProgressView, frame: CGRect(origin: CGPoint(x: loadingProgressInset, y: backgroundSize.height - loadingProgressSize.height), size: loadingProgressSize)) + transition.setAlpha(view: loadingProgressView, alpha: isActiveWithText ? 0.0 : 1.0) + } + + self.readingProgressView.backgroundColor = theme.rootController.navigationBar.primaryTextColor.withMultipliedAlpha(0.07) + self.readingProgressView.frame = CGRect(origin: .zero, size: CGSize(width: backgroundSize.width * params.readingProgress, height: backgroundSize.height)) + transition.setAlpha(view: self.readingProgressView, alpha: isActiveWithText ? 0.0 : 1.0) } } diff --git a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift index 1551fcf4..cd308ad4 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift @@ -19,6 +19,7 @@ final class BrowserAddressListComponent: Component { let insets: UIEdgeInsets let metrics: LayoutMetrics let addressBarFrame: CGRect + let navigationBarHeight: CGFloat let performAction: ActionSlot let presentInGlobalOverlay: (ViewController) -> Void @@ -29,6 +30,7 @@ final class BrowserAddressListComponent: Component { insets: UIEdgeInsets, metrics: LayoutMetrics, addressBarFrame: CGRect, + navigationBarHeight: CGFloat, performAction: ActionSlot, presentInGlobalOverlay: @escaping (ViewController) -> Void ) { @@ -38,6 +40,7 @@ final class BrowserAddressListComponent: Component { self.insets = insets self.metrics = metrics self.addressBarFrame = addressBarFrame + self.navigationBarHeight = navigationBarHeight self.performAction = performAction self.presentInGlobalOverlay = presentInGlobalOverlay } @@ -61,6 +64,9 @@ final class BrowserAddressListComponent: Component { if lhs.addressBarFrame != rhs.addressBarFrame { return false } + if lhs.navigationBarHeight != rhs.navigationBarHeight { + return false + } return true } @@ -214,15 +220,16 @@ final class BrowserAddressListComponent: Component { var validIds: [AnyHashable] = [] var validSectionHeaders: [AnyHashable] = [] var sectionOffset: CGFloat = 0.0 + let headerOffset: CGFloat = self.scrollView.frame.minY let sideInset: CGFloat = 0.0 - let containerInset: CGFloat = 0.0 + let containerInset: CGFloat = self.scrollView.frame.minY for sectionIndex in 0 ..< itemLayout.sections.count { let section = itemLayout.sections[sectionIndex] do { - var sectionHeaderFrame = CGRect(origin: CGPoint(x: sideInset, y: sectionOffset - self.scrollView.bounds.minY), size: CGSize(width: itemLayout.containerSize.width, height: section.insets.top)) + var sectionHeaderFrame = CGRect(origin: CGPoint(x: sideInset, y: headerOffset + sectionOffset - self.scrollView.bounds.minY), size: CGSize(width: itemLayout.containerSize.width, height: section.insets.top)) let sectionHeaderMinY = topOffset + containerInset let sectionHeaderMaxY = containerInset + sectionOffset - self.scrollView.bounds.minY + section.totalHeight - 28.0 @@ -540,7 +547,7 @@ final class BrowserAddressListComponent: Component { let containerFrame: CGRect if component.metrics.isTablet { let containerSize = CGSize(width: component.addressBarFrame.width + 32.0, height: 540.0) - containerFrame = CGRect(origin: CGPoint(x: floor(component.addressBarFrame.center.x - containerSize.width / 2.0), y: 72.0), size: containerSize) + containerFrame = CGRect(origin: CGPoint(x: floor(component.addressBarFrame.center.x - containerSize.width / 2.0), y: 100.0), size: containerSize) self.backgroundView.layer.cornerRadius = 10.0 } else { @@ -602,23 +609,28 @@ final class BrowserAddressListComponent: Component { let itemLayout = ItemLayout(containerSize: containerFrame.size, insets: .zero, sections: sections) self.itemLayout = itemLayout - let containerWidth = containerFrame.size.width let scrollContentHeight = max(itemLayout.contentHeight, containerFrame.size.height) self.ignoreScrolling = true - transition.setFrame(view: self.scrollView, frame: CGRect(origin: .zero, size: containerFrame.size)) - let contentSize = CGSize(width: containerWidth, height: scrollContentHeight) + let scrollFrame: CGRect + if component.metrics.isTablet { + scrollFrame = CGRect(origin: .zero, size: containerFrame.size) + } else { + scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: component.navigationBarHeight), size: CGSize(width: containerFrame.size.width, height: containerFrame.size.height - component.navigationBarHeight)) + } + transition.setFrame(view: self.scrollView, frame: scrollFrame) + let contentSize = CGSize(width: scrollFrame.width, height: scrollContentHeight) if contentSize != self.scrollView.contentSize { self.scrollView.contentSize = contentSize } if resetScrolling { - self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: containerWidth, height: containerFrame.size.height)) + self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollFrame.size) } self.ignoreScrolling = false self.updateScrolling(transition: transition) transition.setFrame(view: self.backgroundView, frame: containerFrame) - transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: .zero, size: CGSize(width: containerWidth, height: scrollContentHeight))) + transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: .zero, size: CGSize(width: scrollFrame.width, height: scrollContentHeight))) if component.metrics.isTablet { transition.setFrame(view: self.shadowView, frame: containerFrame.insetBy(dx: -60.0, dy: -60.0)) diff --git a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift index f41a5395..49c9ece4 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift @@ -12,8 +12,15 @@ import AccountContext import ContextUI import UrlEscaping -private let iconFont = Font.with(size: 30.0, design: .round, weight: .bold) -private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radius: 6.0, color: UIColor(rgb: 0xFF9500)) +private let iconFont = Font.with(size: 28.0, design: .round, weight: .bold) +private let iconTextBackgroundImage = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor(rgb: 0xFF9500).cgColor) + + let path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 12.0) + context.addPath(path.cgPath) + context.fillPath() +}) final class BrowserAddressListItemComponent: Component { let context: AccountContext @@ -261,7 +268,7 @@ final class BrowserAddressListItemComponent: Component { let iconImageLayout = self.icon.asyncLayout() var iconImageApply: (() -> Void)? if let iconImageReferenceAndRepresentation = iconImageReferenceAndRepresentation { - let imageCorners = ImageCorners(radius: 6.0) + let imageCorners = ImageCorners(radius: 12.0, curve: .continuous) let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconImageReferenceAndRepresentation.1.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) } diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index 274ce3b5..d5fa9632 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -18,6 +18,10 @@ import SearchBarNode import ChatHistorySearchContainerNode import ContextUI import UndoUI +import ComponentFlow +import EdgeEffect +import ButtonComponent +import BundleIconComponent public final class BrowserBookmarksScreen: ViewController { final class Node: ViewControllerTracingNode, ASScrollViewDelegate { @@ -196,7 +200,6 @@ public final class BrowserBookmarksScreen: ViewController { controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list( - search: false, reversed: false, reverseGroups: false, displayHeaders: .none, @@ -287,9 +290,9 @@ public final class BrowserBookmarksScreen: ViewController { } let tagMask: MessageTags = .webPage - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, hasBackground: true, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.context.account.peerId, threadId: nil, tagMask: tagMask, interfaceInteraction: self.controllerInteraction), cancel: { [weak self] in + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .navigation, placeholder: self.presentationData.strings.Common_Search, hasBackground: true, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.context.account.peerId, threadId: nil, tagMask: tagMask, interfaceInteraction: self.controllerInteraction), cancel: { [weak self] in self?.controller?.deactivateSearch() - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in @@ -372,7 +375,9 @@ public final class BrowserBookmarksScreen: ViewController { self.openUrl = openUrl self.addBookmark = addBookmark - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + + self._hasGlassStyle = true self.navigationPresentation = .modal self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) @@ -429,19 +434,13 @@ public final class BrowserBookmarksScreen: ViewController { searchContentNode.updateListVisibleContentOffset(offset) } } -// -// self.node.historyNode.didEndScrolling = { [weak self] _ in -// if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { -// let _ = fixNavigationSearchableListNodeScrolling(strongSelf.node.historyNode, searchNode: searchContentNode) -// } -// } self.displayNodeDidLoad() } private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) } @@ -484,10 +483,8 @@ private class BottomPanelNode: ASDisplayNode { private let strings: PresentationStrings private let action: () -> Void - private let separatorNode: ASDisplayNode - private let button: HighlightTrackingButtonNode - private let iconNode: ASImageNode - private let textNode: ImmediateTextNode + private let edgeEffectView = EdgeEffectView() + private let button = ComponentView() private var validLayout: (CGFloat, CGFloat, CGFloat)? @@ -496,49 +493,13 @@ private class BottomPanelNode: ASDisplayNode { self.strings = strings self.action = action - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor - - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddIcon"), color: theme.rootController.navigationBar.accentTextColor) - self.iconNode.isUserInteractionEnabled = false - - self.textNode = ImmediateTextNode() - self.textNode.displaysAsynchronously = false - self.textNode.attributedText = NSAttributedString(string: strings.WebBrowser_Bookmarks_BookmarkCurrent, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor) - self.textNode.isUserInteractionEnabled = false - - self.button = HighlightTrackingButtonNode() - super.init() + } + + override func didLoad() { + super.didLoad() - self.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor - - self.addSubnode(self.button) - self.addSubnode(self.separatorNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.textNode) - self.addSubnode(self.button) - - self.button.highligthedChanged = { [weak self] highlighted in - if let self { - if highlighted { - self.iconNode.layer.removeAnimation(forKey: "opacity") - self.iconNode.alpha = 0.4 - - self.textNode.layer.removeAnimation(forKey: "opacity") - self.textNode.alpha = 0.4 - } else { - self.iconNode.alpha = 1.0 - self.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - - self.textNode.alpha = 1.0 - self.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.view.addSubview(self.edgeEffectView) } @objc private func buttonPressed() { @@ -551,26 +512,76 @@ private class BottomPanelNode: ASDisplayNode { var bottomInset = bottomInset bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0) - let buttonHeight: CGFloat = 40.0 - let textSize = self.textNode.updateLayout(CGSize(width: width, height: 44.0)) +// let buttonHeight: CGFloat = 40.0 +// let textSize = self.textNode.updateLayout(CGSize(width: width, height: 44.0)) +// +// let spacing: CGFloat = 8.0 +// var contentWidth = textSize.width +// var contentOriginX = floorToScreenPixels((width - contentWidth) / 2.0) +// if let icon = self.iconNode.image { +// contentWidth += icon.size.width + spacing +// contentOriginX = floorToScreenPixels((width - contentWidth) / 2.0) +// transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: contentOriginX, y: 12.0 + UIScreenPixel), size: icon.size)) +// contentOriginX += icon.size.width + spacing +// } +// let textFrame = CGRect(origin: CGPoint(x: contentOriginX, y: 17.0), size: textSize) +// transition.updateFrame(node: self.textNode, frame: textFrame) +// +// transition.updateFrame(node: self.button, frame: textFrame.insetBy(dx: -10.0, dy: -10.0)) +// +// transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) +// + + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: bottomInset, innerDiameter: 52.0, sideInset: 30.0) + let height: CGFloat = 52.0 + buttonInsets.bottom - let spacing: CGFloat = 8.0 - var contentWidth = textSize.width - var contentOriginX = floorToScreenPixels((width - contentWidth) / 2.0) - if let icon = self.iconNode.image { - contentWidth += icon.size.width + spacing - contentOriginX = floorToScreenPixels((width - contentWidth) / 2.0) - transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: contentOriginX, y: 12.0 + UIScreenPixel), size: icon.size)) - contentOriginX += icon.size.width + spacing + let edgeEffectHeight: CGFloat = height + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: height - edgeEffectHeight), size: CGSize(width: width, height: edgeEffectHeight)) + transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update( + content: self.theme.list.plainBackgroundColor, + blur: true, + rect: edgeEffectFrame, + edge: .bottom, + edgeSize: edgeEffectFrame.height, + transition: ComponentTransition(transition) + ) + + var buttonItems: [AnyComponentWithIdentity] = [] + buttonItems.append(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Fave", tintColor: self.theme.list.itemCheckColors.foregroundColor)))) + buttonItems.append(AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: self.strings.WebBrowser_Bookmarks_BookmarkCurrent, font: Font.semibold(17.0), color: self.theme.list.itemCheckColors.foregroundColor)))) + + let buttonSize = self.button.update( + transition: .immediate, + component: AnyComponent( + ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: self.theme.list.itemCheckColors.fillColor, + foreground: self.theme.list.itemCheckColors.foregroundColor, + pressedColor: self.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(HStack(buttonItems, spacing: 7.0)) + ), + action: { [weak self] in + self?.action() + } + ) + ), + environment: {}, + containerSize: CGSize(width: width - sideInset * 2.0 - buttonInsets.left - buttonInsets.right, height: 52.0) + ) + let buttonFrame = CGRect(origin: CGPoint(x: sideInset + buttonInsets.left, y: height - buttonInsets.bottom - buttonSize.height), size: buttonSize) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.view.addSubview(buttonView) + } + transition.updateFrame(view: buttonView, frame: buttonFrame) } - let textFrame = CGRect(origin: CGPoint(x: contentOriginX, y: 17.0), size: textSize) - transition.updateFrame(node: self.textNode, frame: textFrame) - transition.updateFrame(node: self.button, frame: textFrame.insetBy(dx: -10.0, dy: -10.0)) - - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) - - return topInset + buttonHeight + bottomInset + return height } } diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift index b09b444e..cb03ff6e 100644 --- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift +++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift @@ -90,12 +90,21 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg private let updateLayoutDisposable = MetaDisposable() private let loadProgress = ValuePromise(1.0, ignoreRepeated: true) - private let readingProgress = ValuePromise(1.0, ignoreRepeated: true) + private let readingProgress = ValuePromise(0.0, ignoreRepeated: true) private var containerLayout: (size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets)? private var setupScrollOffsetOnLayout = false - init(context: AccountContext, presentationData: PresentationData, webPage: TelegramMediaWebpage, anchor: String?, url: String, sourceLocation: InstantPageSourceLocation, preloadedResouces: [Any]?, originalContent: BrowserContent? = nil) { + init( + context: AccountContext, + presentationData: PresentationData, + webPage: TelegramMediaWebpage, + anchor: String?, + url: String, + sourceLocation: InstantPageSourceLocation, + preloadedResouces: [Any]?, + originalContent: BrowserContent? = nil + ) { self.context = context var instantPage: InstantPage? if case let .Loaded(content) = webPage.content { @@ -132,6 +141,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg super.init(frame: .zero) + self.backgroundColor = self.theme.pageBackgroundColor + self.statePromise.set(.single(self._state) |> then( combineLatest( @@ -193,6 +204,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg self.presentationData = presentationData self.theme = instantPageThemeForType(presentationData.theme.overallDarkAppearance ? .dark : .light, settings: self.settings) + self.backgroundColor = self.theme.pageBackgroundColor + self.updatePageLayout() self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) } @@ -415,8 +428,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg var updateVisibleItems = false let resetContentOffset = self.scrollNode.bounds.size.width.isZero || self.setupScrollOffsetOnLayout || !(self.initialAnchor ?? "").isEmpty - var scrollInsets = insets - scrollInsets.top = 0.0 + let scrollInsets = fullInsets if self.scrollNode.view.contentInset != scrollInsets { self.scrollNode.view.contentInset = scrollInsets self.scrollNode.view.scrollIndicatorInsets = scrollInsets @@ -424,7 +436,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg self.wrapperNode.frame = CGRect(origin: .zero, size: size) - let scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top)) + let scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) let scrollFrameUpdated = self.scrollNode.bounds.size != scrollFrame.size if scrollFrameUpdated { let widthUpdated = self.scrollNode.bounds.size.width != scrollFrame.width diff --git a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift index 89e0387e..26cc5ae1 100644 --- a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift @@ -2,8 +2,10 @@ import Foundation import UIKit import Display import ComponentFlow -import BlurredBackgroundComponent +import TelegramPresentationData import ContextUI +import GlassBackgroundComponent +import EdgeEffect final class BrowserNavigationBarEnvironment: Equatable { public let fraction: CGFloat @@ -20,7 +22,7 @@ final class BrowserNavigationBarEnvironment: Equatable { } } -final class BrowserNavigationBarComponent: CombinedComponent { +final class BrowserNavigationBarComponent: Component { public class ExternalState { public fileprivate(set) var centerItemFrame: CGRect @@ -29,11 +31,7 @@ final class BrowserNavigationBarComponent: CombinedComponent { } } - let backgroundColor: UIColor - let separatorColor: UIColor - let textColor: UIColor - let progressColor: UIColor - let accentColor: UIColor + let theme: PresentationTheme let topInset: CGFloat let height: CGFloat let sideInset: CGFloat @@ -42,17 +40,11 @@ final class BrowserNavigationBarComponent: CombinedComponent { let leftItems: [AnyComponentWithIdentity] let rightItems: [AnyComponentWithIdentity] let centerItem: AnyComponentWithIdentity? - let readingProgress: CGFloat - let loadingProgress: Double? let collapseFraction: CGFloat let activate: () -> Void init( - backgroundColor: UIColor, - separatorColor: UIColor, - textColor: UIColor, - progressColor: UIColor, - accentColor: UIColor, + theme: PresentationTheme, topInset: CGFloat, height: CGFloat, sideInset: CGFloat, @@ -61,16 +53,10 @@ final class BrowserNavigationBarComponent: CombinedComponent { leftItems: [AnyComponentWithIdentity], rightItems: [AnyComponentWithIdentity], centerItem: AnyComponentWithIdentity?, - readingProgress: CGFloat, - loadingProgress: Double?, collapseFraction: CGFloat, activate: @escaping () -> Void ) { - self.backgroundColor = backgroundColor - self.separatorColor = separatorColor - self.textColor = textColor - self.progressColor = progressColor - self.accentColor = accentColor + self.theme = theme self.topInset = topInset self.height = height self.sideInset = sideInset @@ -79,26 +65,12 @@ final class BrowserNavigationBarComponent: CombinedComponent { self.leftItems = leftItems self.rightItems = rightItems self.centerItem = centerItem - self.readingProgress = readingProgress - self.loadingProgress = loadingProgress self.collapseFraction = collapseFraction self.activate = activate } static func ==(lhs: BrowserNavigationBarComponent, rhs: BrowserNavigationBarComponent) -> Bool { - if lhs.backgroundColor != rhs.backgroundColor { - return false - } - if lhs.separatorColor != rhs.separatorColor { - return false - } - if lhs.textColor != rhs.textColor { - return false - } - if lhs.progressColor != rhs.progressColor { - return false - } - if lhs.accentColor != rhs.accentColor { + if lhs.theme !== rhs.theme { return false } if lhs.topInset != rhs.topInset { @@ -122,203 +94,353 @@ final class BrowserNavigationBarComponent: CombinedComponent { if lhs.centerItem != rhs.centerItem { return false } - if lhs.readingProgress != rhs.readingProgress { - return false - } - if lhs.loadingProgress != rhs.loadingProgress { - return false - } if lhs.collapseFraction != rhs.collapseFraction { return false } return true } - static var body: Body { - let background = Child(Rectangle.self) - let readingProgress = Child(Rectangle.self) - let separator = Child(Rectangle.self) - let loadingProgress = Child(LoadingProgressComponent.self) - let leftItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) - let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) - let centerItems = ChildMap(environment: BrowserNavigationBarEnvironment.self, keyedBy: AnyHashable.self) - let activate = Child(Button.self) + final class View: UIView { + private var edgeEffectView = EdgeEffectView() + private let containerView = GlassBackgroundContainerView() + + private var leftItemsBackground: GlassBackgroundView? + private var leftItems: [AnyHashable: ComponentView] = [:] - return { context in - var availableWidth = context.availableSize.width - let sideInset: CGFloat = (context.component.metrics.isTablet ? 20.0 : 16.0) + context.component.sideInset + private var rightItemsBackground: GlassBackgroundView? + private var rightItems: [AnyHashable: ComponentView] = [:] + + private var centerItems: [AnyHashable: ComponentView] = [:] + + private let activateButton = HighlightTrackingButton() + + private var component: BrowserNavigationBarComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) - let collapsedHeight: CGFloat = 24.0 - let expandedHeight = context.component.height - let contentHeight: CGFloat = expandedHeight * (1.0 - context.component.collapseFraction) + collapsedHeight * context.component.collapseFraction - let size = CGSize(width: context.availableSize.width, height: context.component.topInset + contentHeight) - let verticalOffset: CGFloat = context.component.metrics.isTablet ? -2.0 : 0.0 - let itemSpacing: CGFloat = context.component.metrics.isTablet ? 26.0 : 8.0 + self.addSubview(self.edgeEffectView) - let background = background.update( - component: Rectangle(color: context.component.backgroundColor.withAlphaComponent(1.0)), - availableSize: CGSize(width: size.width, height: size.height), - transition: context.transition - ) - - let readingProgress = readingProgress.update( - component: Rectangle(color: context.component.progressColor), - availableSize: CGSize(width: size.width * context.component.readingProgress, height: size.height), - transition: context.transition - ) - - let separator = separator.update( - component: Rectangle(color: context.component.separatorColor, height: UIScreenPixel), - availableSize: CGSize(width: size.width, height: size.height), - transition: context.transition - ) - - let loadingProgressHeight: CGFloat = 2.0 - let loadingProgress = loadingProgress.update( - component: LoadingProgressComponent( - color: context.component.accentColor, - height: loadingProgressHeight, - value: context.component.loadingProgress ?? 0.0 - ), - availableSize: CGSize(width: size.width, height: size.height), - transition: context.transition - ) - - var leftItemList: [_UpdatedChildComponent] = [] - for item in context.component.leftItems { - let item = leftItems[item.id].update( - component: item.component, - availableSize: CGSize(width: availableWidth, height: expandedHeight), - transition: context.transition - ) - leftItemList.append(item) - availableWidth -= item.size.width - } - - var rightItemList: [_UpdatedChildComponent] = [] - for item in context.component.rightItems { - let item = rightItems[item.id].update( - component: item.component, - availableSize: CGSize(width: availableWidth, height: expandedHeight), - transition: context.transition - ) - rightItemList.append(item) - availableWidth -= item.size.width + self.addSubview(self.containerView) + self.activateButton.addTarget(self, action: #selector(self.activatePressed), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + @objc private func activatePressed() { + guard let component = self.component else { + return } + component.activate() + } + + func update(component: BrowserNavigationBarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + var availableWidth = availableSize.width + let sideInset: CGFloat = (component.metrics.isTablet ? 20.0 : 16.0) + component.sideInset + + let collapsedHeight: CGFloat = 54.0 + let expandedHeight = component.height + let contentHeight: CGFloat = expandedHeight * (1.0 - component.collapseFraction) + collapsedHeight * component.collapseFraction + let size = CGSize(width: availableSize.width, height: component.topInset + contentHeight) + let verticalOffset: CGFloat = component.metrics.isTablet ? -2.0 : 0.0 + let itemSpacing: CGFloat = 0.0 //component.metrics.isTablet ? 26.0 : 8.0 + let panelHeight: CGFloat = 44.0 + + var leftItemsBackground: GlassBackgroundView? + var leftItemsBackgroundTransition = transition + if !component.leftItems.isEmpty { + if let current = self.leftItemsBackground { + leftItemsBackground = current + } else { + leftItemsBackgroundTransition = .immediate + leftItemsBackground = GlassBackgroundView() + self.containerView.contentView.addSubview(leftItemsBackground!) + self.leftItemsBackground = leftItemsBackground - context.add(background - .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) - ) - - var readingProgressAlpha = context.component.collapseFraction - if leftItemList.isEmpty && rightItemList.isEmpty { - readingProgressAlpha = 0.0 + transition.animateScale(view: leftItemsBackground!, from: 0.1, to: 1.0) + transition.animateAlpha(view: leftItemsBackground!, from: 0.0, to: 1.0) + } } - context.add(readingProgress - .position(CGPoint(x: readingProgress.size.width / 2.0, y: size.height / 2.0)) - .opacity(readingProgressAlpha) - ) - context.add(separator - .position(CGPoint(x: size.width / 2.0, y: size.height)) - ) - - context.add(loadingProgress - .position(CGPoint(x: size.width / 2.0, y: size.height - loadingProgressHeight / 2.0)) - ) + var rightItemsBackground: GlassBackgroundView? + var rightItemsBackgroundTransition = transition + if !component.rightItems.isEmpty { + if let current = self.rightItemsBackground { + rightItemsBackground = current + } else { + rightItemsBackgroundTransition = .immediate + rightItemsBackground = GlassBackgroundView() + self.containerView.contentView.addSubview(rightItemsBackground!) + self.rightItemsBackground = rightItemsBackground + + transition.animateScale(view: rightItemsBackground!, from: 0.1, to: 1.0) + transition.animateAlpha(view: rightItemsBackground!, from: 0.0, to: 1.0) + } + } + + var validLeftItemIds: Set = Set() + var leftItemTransitions: [AnyHashable: (CGSize, ComponentTransition)] = [:] + var leftItemsWidth: CGFloat = 0.0 + for item in component.leftItems { + validLeftItemIds.insert(item.id) + var itemTransition = transition + let itemView: ComponentView + if let current = self.leftItems[item.id] { + itemView = current + } else { + itemTransition = .immediate + itemView = ComponentView() + self.leftItems[item.id] = itemView + } + + let itemSize = itemView.update( + transition: itemTransition, + component: item.component, + environment: {}, + containerSize: CGSize(width: availableWidth, height: expandedHeight) + ) + leftItemTransitions[item.id] = (itemSize, itemTransition) + availableWidth -= itemSize.width + leftItemsWidth += itemSize.width + } + + var validRightItemIds: Set = Set() + var rightItemTransitions: [AnyHashable: (CGSize, ComponentTransition)] = [:] + var rightItemsWidth: CGFloat = 0.0 + for item in component.rightItems { + validRightItemIds.insert(item.id) + var itemTransition = transition + let itemView: ComponentView + if let current = self.rightItems[item.id] { + itemView = current + } else { + itemTransition = .immediate + itemView = ComponentView() + self.rightItems[item.id] = itemView + } + + let itemSize = itemView.update( + transition: itemTransition, + component: item.component, + environment: {}, + containerSize: CGSize(width: availableWidth, height: expandedHeight) + ) + rightItemTransitions[item.id] = (itemSize, itemTransition) + availableWidth -= itemSize.width + rightItemsWidth += itemSize.width + } var centerLeftInset = sideInset - var leftItemX = sideInset - for item in leftItemList { - context.add(item - .position(CGPoint(x: leftItemX + item.size.width / 2.0 - (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0 + verticalOffset)) - .scale(1.0 - 0.35 * context.component.collapseFraction) - .opacity(1.0 - context.component.collapseFraction) - .appear(.default(scale: true, alpha: true)) - .disappear(.default(scale: true, alpha: true)) - ) - leftItemX += item.size.width + itemSpacing - centerLeftInset += item.size.width + itemSpacing + var leftItemX = 0.0 + for item in component.leftItems { + guard let (itemSize, itemTransition) = leftItemTransitions[item.id], let itemView = self.leftItems[item.id]?.view else { + continue + } + let itemPosition = CGPoint(x: leftItemX + itemSize.width / 2.0, y: panelHeight * 0.5) + let itemFrame = CGRect(origin: CGPoint(x: itemPosition.x - itemSize.width * 0.5, y: itemPosition.y - itemSize.height * 0.5), size: itemSize) + if itemView.superview == nil { + leftItemsBackground?.contentView.addSubview(itemView) + transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) + transition.animateScale(view: itemView, from: 0.01, to: 1.0) + } + itemTransition.setBounds(view: itemView, bounds: CGRect(origin: .zero, size: itemFrame.size)) + itemTransition.setPosition(view: itemView, position: itemFrame.center) + + leftItemX += itemSize.width + itemSpacing + centerLeftInset += itemSize.width + itemSpacing } - - var centerRightInset = sideInset - 5.0 - var rightItemX = context.availableSize.width - (sideInset - 5.0) - for item in rightItemList.reversed() { - context.add(item - .position(CGPoint(x: rightItemX - item.size.width / 2.0 + (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0 + verticalOffset)) - .scale(1.0 - 0.35 * context.component.collapseFraction) - .opacity(1.0 - context.component.collapseFraction) - .appear(.default(scale: true, alpha: true)) - .disappear(.default(scale: true, alpha: true)) - ) - rightItemX -= item.size.width + itemSpacing - centerRightInset += item.size.width + itemSpacing + + var centerRightInset = sideInset + var rightItemX = rightItemsWidth + for item in component.rightItems.reversed() { + guard let (itemSize, itemTransition) = rightItemTransitions[item.id], let itemView = self.rightItems[item.id]?.view else { + continue + } + let itemPosition = CGPoint(x: rightItemX - itemSize.width / 2.0, y: panelHeight * 0.5) + let itemFrame = CGRect(origin: CGPoint(x: itemPosition.x - itemSize.width * 0.5, y: itemPosition.y - itemSize.height * 0.5), size: itemSize) + if itemView.superview == nil { + rightItemsBackground?.contentView.addSubview(itemView) + transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) + transition.animateScale(view: itemView, from: 0.01, to: 1.0) + } + itemTransition.setBounds(view: itemView, bounds: CGRect(origin: .zero, size: itemFrame.size)) + itemTransition.setPosition(view: itemView, position: itemFrame.center) + itemTransition.setScale(view: itemView, scale: 1.0 - 0.35 * component.collapseFraction) + itemTransition.setAlpha(view: itemView, alpha: 1.0 - component.collapseFraction) + + rightItemX -= itemSize.width + itemSpacing + centerRightInset += itemSize.width + itemSpacing + } + + if let leftItemsBackground { + let leftItemsFrame = CGRect(origin: CGPoint(x: sideInset - (leftItemsWidth / 2.0 * 0.35 * component.collapseFraction), y: component.topInset + contentHeight / 2.0 + verticalOffset - panelHeight / 2.0), size: CGSize(width: leftItemsWidth, height: panelHeight)) + leftItemsBackgroundTransition.setFrame(view: leftItemsBackground, frame: leftItemsFrame) + leftItemsBackground.update(size: leftItemsFrame.size, shape: .roundedRect(cornerRadius: leftItemsFrame.height * 0.5), isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: leftItemsBackgroundTransition) + + leftItemsBackgroundTransition.setScale(view: leftItemsBackground, scale: 1.0 - 0.999 * component.collapseFraction) + leftItemsBackgroundTransition.setAlpha(view: leftItemsBackground.contentView, alpha: 1.0 - component.collapseFraction) + } else if let leftItemsBackground = self.leftItemsBackground { + self.leftItemsBackground = nil + leftItemsBackground.removeFromSuperview() + } + + if let rightItemsBackground { + let rightItemsFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - rightItemsWidth * (1.0 - component.collapseFraction) + (rightItemsWidth / 2.0 * 0.35 * component.collapseFraction), y: component.topInset + contentHeight / 2.0 + verticalOffset - panelHeight / 2.0), size: CGSize(width: rightItemsWidth, height: panelHeight)) + rightItemsBackgroundTransition.setFrame(view: rightItemsBackground, frame: rightItemsFrame) + rightItemsBackground.update(size: rightItemsFrame.size, shape: .roundedRect(cornerRadius: rightItemsFrame.height * 0.5), isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: rightItemsBackgroundTransition) + + rightItemsBackgroundTransition.setScale(view: rightItemsBackground, scale: 1.0 - 0.999 * component.collapseFraction) + rightItemsBackgroundTransition.setAlpha(view: rightItemsBackground.contentView, alpha: 1.0 - component.collapseFraction) + } else if let rightItemsBackground = self.rightItemsBackground { + self.rightItemsBackground = nil + rightItemsBackground.removeFromSuperview() + } + + var removeLeftItemIds: [AnyHashable] = [] + for (id, item) in self.leftItems { + if !validLeftItemIds.contains(id) { + removeLeftItemIds.append(id) + if let itemView = item.view { + transition.setScale(view: itemView, scale: 0.01) + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + } + } + } + for id in removeLeftItemIds { + self.leftItems.removeValue(forKey: id) + } + + var removeRightItemIds: [AnyHashable] = [] + for (id, item) in self.rightItems { + if !validRightItemIds.contains(id) { + removeRightItemIds.append(id) + if let itemView = item.view { + transition.setScale(view: itemView, scale: 0.01) + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + } + } + } + for id in removeRightItemIds { + self.rightItems.removeValue(forKey: id) } let maxCenterInset = max(centerLeftInset, centerRightInset) - if !leftItemList.isEmpty || !rightItemList.isEmpty { - availableWidth -= itemSpacing * CGFloat(max(0, leftItemList.count - 1)) + itemSpacing * CGFloat(max(0, rightItemList.count - 1)) + 30.0 + if !component.leftItems.isEmpty || !component.rightItems.isEmpty { + availableWidth -= itemSpacing * CGFloat(max(0, component.leftItems.count - 1)) + itemSpacing * CGFloat(max(0, component.rightItems.count - 1)) + 30.0 } - availableWidth -= context.component.sideInset * 2.0 + availableWidth -= component.sideInset * 2.0 - let canCenter = availableWidth > 660.0 - availableWidth = min(660.0, availableWidth) + let canCenter = availableWidth > 390.0 + availableWidth = min(390.0, availableWidth) - let environment = BrowserNavigationBarEnvironment(fraction: context.component.collapseFraction) + let environment = BrowserNavigationBarEnvironment(fraction: component.collapseFraction) - let centerItem = context.component.centerItem.flatMap { item in - centerItems[item.id].update( + var centerX = maxCenterInset + (availableSize.width - maxCenterInset * 2.0) / 2.0 + if canCenter { + centerX = availableSize.width / 2.0 + } else { + centerX = centerLeftInset + (availableSize.width - centerLeftInset - centerRightInset) / 2.0 + } + + var validCenterItemIds: Set = Set() + if let item = component.centerItem { + validCenterItemIds.insert(item.id) + + var itemTransition = transition + let itemView: ComponentView + if let current = self.centerItems[item.id] { + itemView = current + } else { + itemTransition = .immediate + itemView = ComponentView() + self.centerItems[item.id] = itemView + } + + let itemSize = itemView.update( + transition: itemTransition, component: item.component, environment: { environment }, - availableSize: CGSize(width: availableWidth, height: expandedHeight), - transition: context.transition - ) - } - - var centerX = maxCenterInset + (context.availableSize.width - maxCenterInset * 2.0) / 2.0 - if "".isEmpty { - if canCenter { - centerX = context.availableSize.width / 2.0 - } else { - centerX = centerLeftInset + (context.availableSize.width - centerLeftInset - centerRightInset) / 2.0 - } - } - if let centerItem = centerItem { - let centerItemPosition = CGPoint(x: centerX, y: context.component.topInset + contentHeight / 2.0 + verticalOffset) - context.add(centerItem - .position(centerItemPosition) - .scale(1.0 - 0.35 * context.component.collapseFraction) - .appear(.default(scale: false, alpha: true)) - .disappear(.default(scale: false, alpha: true)) + containerSize: CGSize(width: availableWidth, height: expandedHeight) ) - context.component.externalState?.centerItemFrame = centerItem.size.centered(around: centerItemPosition) + let itemPosition = CGPoint(x: centerX, y: component.topInset + contentHeight / 2.0 + verticalOffset) + let itemFrame = CGRect(origin: CGPoint(x: itemPosition.x - itemSize.width * 0.5, y: itemPosition.y - itemSize.height * 0.5), size: itemSize) + if let itemView = itemView.view { + if itemView.superview == nil { + self.containerView.contentView.addSubview(itemView) + transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) + } + itemTransition.setBounds(view: itemView, bounds: CGRect(origin: .zero, size: itemFrame.size)) + itemTransition.setPosition(view: itemView, position: itemFrame.center) + itemTransition.setScale(view: itemView, scale: 1.0 - 0.25 * component.collapseFraction) + } + component.externalState?.centerItemFrame = itemFrame } - if context.component.collapseFraction == 1.0 { - let activateAction = context.component.activate - let activate = activate.update( - component: Button( - content: AnyComponent(Rectangle(color: UIColor(rgb: 0x000000, alpha: 0.001))), - action: { - activateAction() - } - ), - availableSize: size, - transition: .immediate - ) - context.add(activate - .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0)) - ) + var removeCenterItemIds: [AnyHashable] = [] + for (id, item) in self.centerItems { + if !validCenterItemIds.contains(id) { + removeCenterItemIds.append(id) + if let itemView = item.view { + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + } + } } + for id in removeCenterItemIds { + self.centerItems.removeValue(forKey: id) + } + + if component.collapseFraction == 1.0 { + if self.activateButton.superview == nil { + self.addSubview(self.activateButton) + } + self.activateButton.frame = CGRect(origin: .zero, size: size) + } else { + self.activateButton.removeFromSuperview() + } + + self.containerView.update(size: size, isDark: component.theme.overallDarkAppearance, transition: transition) + transition.setFrame(view: self.containerView, frame: CGRect(origin: .zero, size: size)) + + let edgeEffectHeight: CGFloat = 80.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update( + content: .clear, + blur: true, + rect: edgeEffectFrame, + edge: .top, + edgeSize: edgeEffectFrame.height, + transition: transition + ) return size } } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } } -private final class LoadingProgressComponent: Component { +final class LoadingProgressComponent: Component { let color: UIColor let height: CGFloat let value: CGFloat @@ -414,111 +536,3 @@ private final class LoadingProgressComponent: Component { return view.update(component: self, availableSize: availableSize, transition: transition) } } - -final class ReferenceButtonComponent: Component { - let content: AnyComponent - let tag: AnyObject? - let action: () -> Void - - init( - content: AnyComponent, - tag: AnyObject? = nil, - action: @escaping () -> Void - ) { - self.content = content - self.tag = tag - self.action = action - } - - static func ==(lhs: ReferenceButtonComponent, rhs: ReferenceButtonComponent) -> Bool { - if lhs.content != rhs.content { - return false - } - if lhs.tag !== rhs.tag { - return false - } - return true - } - - final class View: HighlightTrackingButton, ComponentTaggedView { - private let sourceView: ContextControllerSourceView - let referenceNode: ContextReferenceContentNode - let componentView: ComponentView - - private var component: ReferenceButtonComponent? - - public func matches(tag: Any) -> Bool { - if let component = self.component, let componentTag = component.tag { - let tag = tag as AnyObject - if componentTag === tag { - return true - } - } - return false - } - - init() { - self.componentView = ComponentView() - self.sourceView = ContextControllerSourceView() - self.sourceView.animateScale = false - self.referenceNode = ContextReferenceContentNode() - - super.init(frame: CGRect()) - - self.sourceView.isUserInteractionEnabled = false - self.addSubview(self.sourceView) - self.sourceView.addSubnode(self.referenceNode) - - self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self, let contentView = strongSelf.componentView.view { - if highlighted { - contentView.layer.removeAnimation(forKey: "opacity") - contentView.alpha = 0.4 - } else { - contentView.alpha = 1.0 - contentView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - } - - required init?(coder aDecoder: NSCoder) { - preconditionFailure() - } - - @objc private func pressed() { - self.component?.action() - } - - func update(component: ReferenceButtonComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { - self.component = component - - let componentSize = self.componentView.update( - transition: transition, - component: component.content, - environment: {}, - containerSize: availableSize - ) - if let componentView = self.componentView.view { - if componentView.superview == nil { - self.referenceNode.view.addSubview(componentView) - } - transition.setFrame(view: componentView, frame: CGRect(origin: .zero, size: componentSize)) - } - - transition.setFrame(view: self.sourceView, frame: CGRect(origin: .zero, size: componentSize)) - self.referenceNode.frame = CGRect(origin: .zero, size: componentSize) - - return componentSize - } - } - - func makeView() -> View { - return View() - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) - } -} diff --git a/submodules/BrowserUI/Sources/BrowserPdfContent.swift b/submodules/BrowserUI/Sources/BrowserPdfContent.swift index 8cad6151..be32b96e 100644 --- a/submodules/BrowserUI/Sources/BrowserPdfContent.swift +++ b/submodules/BrowserUI/Sources/BrowserPdfContent.swift @@ -16,6 +16,7 @@ import ShareController import UndoUI import UrlEscaping import PDFKit +import GlassBackgroundComponent final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDFDocumentDelegate { private let context: AccountContext @@ -25,7 +26,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF private let pdfView: PDFView private let scrollView: UIScrollView! - private let pageIndicatorBackgorund: UIVisualEffectView + private let pageIndicatorBackground = GlassBackgroundView() private let pageIndicator = ComponentView() private var pageNumber: (Int, Int)? private var pageTimer: SwiftSignalKit.Timer? @@ -61,11 +62,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF self.pdfView = PDFView() self.pdfView.clipsToBounds = false - - self.pageIndicatorBackgorund = UIVisualEffectView(effect: UIBlurEffect(style: .light)) - self.pageIndicatorBackgorund.clipsToBounds = true - self.pageIndicatorBackgorund.layer.cornerRadius = 10.0 - + var scrollView: UIScrollView? for view in self.pdfView.subviews { if let view = view as? UIScrollView { @@ -170,7 +167,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF return } let transition = ComponentTransition.easeInOut(duration: 0.25) - transition.setAlpha(view: self.pageIndicatorBackgorund, alpha: 0.0) + transition.setAlpha(view: self.pageIndicatorBackground, alpha: 0.0) }, queue: Queue.mainQueue()) self.pageTimer?.start() } @@ -354,7 +351,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF self.validLayout = (size, insets, fullInsets) self.previousScrollingOffset = ScrollingOffsetState(value: self.scrollView.contentOffset.y, isDraggingOrDecelerating: self.scrollView.isDragging || self.scrollView.isDecelerating) - + let currentBounds = self.scrollView.bounds let offsetToBottomEdge = max(0.0, self.scrollView.contentSize.height - currentBounds.maxY) var bottomInset = insets.bottom @@ -368,23 +365,24 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF let pageIndicatorSize = self.pageIndicator.update( transition: .immediate, component: AnyComponent( - Text(text: "\(self.pageNumber?.0 ?? 1) of \(self.pageNumber?.1 ?? 1)", font: Font.with(size: 15.0, weight: .semibold, traits: .monospacedNumbers), color: self.presentationData.theme.list.itemSecondaryTextColor) + Text(text: "\(self.pageNumber?.0 ?? 1) of \(self.pageNumber?.1 ?? 1)", font: Font.with(size: 15.0, weight: .regular, traits: .monospacedNumbers), color: self.presentationData.theme.list.itemPrimaryTextColor) ), environment: {}, containerSize: size ) if let view = self.pageIndicator.view { if view.superview == nil { - self.addSubview(self.pageIndicatorBackgorund) - self.pageIndicatorBackgorund.contentView.addSubview(view) + self.addSubview(self.pageIndicatorBackground) + self.pageIndicatorBackground.contentView.addSubview(view) } - + let horizontalPadding: CGFloat = 10.0 let verticalPadding: CGFloat = 8.0 - let pageBackgroundFrame = CGRect(origin: CGPoint(x: insets.left + 20.0, y: insets.top + 16.0), size: CGSize(width: horizontalPadding * 2.0 + pageIndicatorSize.width, height: verticalPadding * 2.0 + pageIndicatorSize.height)) + let pageBackgroundFrame = CGRect(origin: CGPoint(x: insets.left + 16.0, y: insets.top + 16.0), size: CGSize(width: horizontalPadding * 2.0 + pageIndicatorSize.width, height: verticalPadding * 2.0 + pageIndicatorSize.height)) - self.pageIndicatorBackgorund.bounds = CGRect(origin: .zero, size: pageBackgroundFrame.size) - transition.setPosition(view: self.pageIndicatorBackgorund, position: pageBackgroundFrame.center) + self.pageIndicatorBackground.update(size: pageBackgroundFrame.size, cornerRadius: pageBackgroundFrame.size.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: transition) + self.pageIndicatorBackground.bounds = CGRect(origin: .zero, size: pageBackgroundFrame.size) + transition.setPosition(view: self.pageIndicatorBackground, position: pageBackgroundFrame.center) view.frame = CGRect(origin: CGPoint(x: horizontalPadding, y: verticalPadding), size: pageIndicatorSize) } @@ -459,7 +457,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF } let transition = ComponentTransition.easeInOut(duration: 0.1) - transition.setAlpha(view: self.pageIndicatorBackgorund, alpha: 1.0) + transition.setAlpha(view: self.pageIndicatorBackground, alpha: 1.0) self.pageTimer?.invalidate() self.pageTimer = nil diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index aeb2e445..9eac12ee 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -20,6 +20,7 @@ import InstantPageUI import NavigationStackComponent import LottieComponent import WebKit +import GlassBarButtonComponent private let settingsTag = GenericComponentViewTag() @@ -85,6 +86,8 @@ private final class BrowserScreenComponent: CombinedComponent { let navigationBarExternalState = BrowserNavigationBarComponent.ExternalState() + let moreButtonPlayOnce = ActionSlot() + return { context in let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let performAction = context.component.performAction @@ -123,6 +126,8 @@ private final class BrowserScreenComponent: CombinedComponent { url: context.component.contentState?.url ?? "", isSecure: context.component.contentState?.isSecure ?? false, isExpanded: context.component.presentationState.addressFocused, + readingProgress: context.component.contentState?.readingProgress ?? 0.0, + loadingProgress: context.component.contentState?.estimatedProgress, performAction: performAction ) ) @@ -134,7 +139,9 @@ private final class BrowserScreenComponent: CombinedComponent { component: AnyComponent( TitleBarContentComponent( theme: environment.theme, - title: title + title: title, + readingProgress: context.component.contentState?.readingProgress ?? 0.0, + loadingProgress: context.component.contentState?.estimatedProgress ) ) ) @@ -150,37 +157,40 @@ private final class BrowserScreenComponent: CombinedComponent { component: AnyComponent( Button( content: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.WebBrowser_Done, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.accentTextColor, paragraphAlignment: .center)), horizontalAlignment: .left, maximumNumberOfLines: 1) + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) ), action: { performAction.invoke(.close) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ) ] if isTablet { - #if DEBUG - navigationLeftItems.append( - AnyComponentWithIdentity( - id: "minimize", - component: AnyComponent( - Button( - content: AnyComponent( - BundleIconComponent( - name: "Media Gallery/PictureInPictureButton", - tintColor: environment.theme.rootController.navigationBar.accentTextColor - ) - ), - action: { - performAction.invoke(.close) - } - ) - ) - ) - ) - #endif +// #if DEBUG +// navigationLeftItems.append( +// AnyComponentWithIdentity( +// id: "minimize", +// component: AnyComponent( +// Button( +// content: AnyComponent( +// BundleIconComponent( +// name: "Media Gallery/PictureInPictureButton", +// tintColor: environment.theme.rootController.navigationBar.accentTextColor +// ) +// ), +// action: { +// performAction.invoke(.close) +// } +// ) +// ) +// ) +// ) +// #endif let canGoBack = context.component.contentState?.canGoBack ?? false let canGoForward = context.component.contentState?.canGoForward ?? false @@ -193,13 +203,13 @@ private final class BrowserScreenComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Instant View/Back", - tintColor: environment.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(canGoBack ? 1.0 : 0.4) + tintColor: environment.theme.chat.inputPanel.panelControlColor.withAlphaComponent(canGoBack ? 1.0 : 0.4) ) ), action: { performAction.invoke(.navigateBack) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ) ) @@ -212,13 +222,13 @@ private final class BrowserScreenComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Instant View/Forward", - tintColor: environment.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(canGoForward ? 1.0 : 0.4) + tintColor: environment.theme.chat.inputPanel.panelControlColor.withAlphaComponent(canGoForward ? 1.0 : 0.4) ) ), action: { performAction.invoke(.navigateForward) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ) ) @@ -228,21 +238,22 @@ private final class BrowserScreenComponent: CombinedComponent { AnyComponentWithIdentity( id: "settings", component: AnyComponent( - ReferenceButtonComponent( + Button( content: AnyComponent( LottieComponent( content: LottieComponent.AppBundleContent( - name: "anim_moredots" + name: "anim_morewide" ), - color: environment.theme.rootController.navigationBar.accentTextColor, - size: CGSize(width: 30.0, height: 30.0) + color: environment.theme.chat.inputPanel.panelControlColor, + size: CGSize(width: 34.0, height: 34.0), + playOnce: moreButtonPlayOnce ) ), - tag: settingsTag, action: { performAction.invoke(.openSettings) + moreButtonPlayOnce.invoke(Void()) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(settingsTag) ) ) ] @@ -256,13 +267,13 @@ private final class BrowserScreenComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Instant View/Bookmark", - tintColor: environment.theme.rootController.navigationBar.accentTextColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) ), action: { performAction.invoke(.openBookmarks) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ), at: 0 @@ -275,14 +286,14 @@ private final class BrowserScreenComponent: CombinedComponent { Button( content: AnyComponent( BundleIconComponent( - name: "Chat List/NavigationShare", - tintColor: environment.theme.rootController.navigationBar.accentTextColor + name: "Instant View/Share", + tintColor: environment.theme.chat.inputPanel.panelControlColor ) ), action: { performAction.invoke(.share) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ), at: 0 @@ -297,13 +308,13 @@ private final class BrowserScreenComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Instant View/Browser", - tintColor: environment.theme.rootController.navigationBar.accentTextColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) ), action: { performAction.invoke(.openIn) } - ) + ).minSize(CGSize(width: 44.0, height: 44.0)) ) ) ) @@ -316,21 +327,15 @@ private final class BrowserScreenComponent: CombinedComponent { let navigationBar = navigationBar.update( component: BrowserNavigationBarComponent( - backgroundColor: environment.theme.rootController.navigationBar.blurredBackgroundColor, - separatorColor: environment.theme.rootController.navigationBar.separatorColor, - textColor: environment.theme.rootController.navigationBar.primaryTextColor, - progressColor: environment.theme.rootController.navigationBar.segmentedBackgroundColor, - accentColor: environment.theme.rootController.navigationBar.accentTextColor, + theme: environment.theme, topInset: environment.statusBarHeight, - height: environment.navigationHeight - environment.statusBarHeight, + height: environment.navigationHeight - environment.statusBarHeight + 8.0, sideInset: environment.safeInsets.left, metrics: environment.metrics, externalState: navigationBarExternalState, leftItems: navigationLeftItems, rightItems: navigationRightItems, centerItem: navigationContent, - readingProgress: context.component.contentState?.readingProgress ?? 0.0, - loadingProgress: context.component.contentState?.estimatedProgress, collapseFraction: collapseFraction, activate: { performAction.invoke(.expand) @@ -339,9 +344,6 @@ private final class BrowserScreenComponent: CombinedComponent { availableSize: context.availableSize, transition: context.transition ) - context.add(navigationBar - .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height / 2.0)) - ) let toolbarContent: AnyComponentWithIdentity? if context.component.presentationState.isSearching { @@ -349,8 +351,8 @@ private final class BrowserScreenComponent: CombinedComponent { id: "search", component: AnyComponent( SearchToolbarContentComponent( + theme: environment.theme, strings: environment.strings, - textColor: environment.theme.rootController.navigationBar.primaryTextColor, index: context.component.presentationState.searchResultIndex, count: context.component.presentationState.searchResultCount, isEmpty: context.component.presentationState.searchQueryIsEmpty, @@ -363,8 +365,7 @@ private final class BrowserScreenComponent: CombinedComponent { id: "navigation", component: AnyComponent( NavigationToolbarContentComponent( - accentColor: environment.theme.rootController.navigationBar.accentTextColor, - textColor: environment.theme.rootController.navigationBar.primaryTextColor, + theme: environment.theme, canGoBack: context.component.contentState?.canGoBack ?? false, canGoForward: context.component.contentState?.canGoForward ?? false, canOpenIn: canOpenIn, @@ -384,15 +385,12 @@ private final class BrowserScreenComponent: CombinedComponent { toolbarBottomInset = environment.safeInsets.bottom } - var toolbarSize: CGFloat = 0.0 if isTablet && !context.component.presentationState.isSearching { } else { let toolbar = toolbar.update( component: BrowserToolbarComponent( - backgroundColor: environment.theme.rootController.navigationBar.blurredBackgroundColor, - separatorColor: environment.theme.rootController.navigationBar.separatorColor, - textColor: environment.theme.rootController.navigationBar.primaryTextColor, + theme: environment.theme, bottomInset: toolbarBottomInset, sideInset: environment.safeInsets.left, item: toolbarContent, @@ -412,16 +410,9 @@ private final class BrowserScreenComponent: CombinedComponent { }) }) ) - toolbarSize = toolbar.size.height } if context.component.presentationState.addressFocused { - let addressListSize: CGSize - if isTablet { - addressListSize = context.availableSize - } else { - addressListSize = CGSize(width: context.availableSize.width, height: context.availableSize.height - navigationBar.size.height - toolbarSize) - } let controller = environment.controller let addressList = addressList.update( component: BrowserAddressListComponent( @@ -431,12 +422,13 @@ private final class BrowserScreenComponent: CombinedComponent { insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right), metrics: environment.metrics, addressBarFrame: navigationBarExternalState.centerItemFrame, + navigationBarHeight: navigationBar.size.height, performAction: performAction, presentInGlobalOverlay: { c in controller()?.presentInGlobalOverlay(c) } ), - availableSize: addressListSize, + availableSize: context.availableSize, transition: context.transition ) @@ -448,7 +440,7 @@ private final class BrowserScreenComponent: CombinedComponent { ) } else { context.add(addressList - .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: addressList.size.height / 2.0)) .clipsToBounds(true) .appear(.default(alpha: true)) .disappear(.default(alpha: true)) @@ -456,6 +448,10 @@ private final class BrowserScreenComponent: CombinedComponent { } } + context.add(navigationBar + .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height / 2.0)) + ) + return context.availableSize } } @@ -1075,7 +1071,7 @@ public class BrowserScreen: ViewController, MinimizableController { } func openSettings() { - guard let referenceView = self.componentHost.findTaggedView(tag: settingsTag) as? ReferenceButtonComponent.View else { + guard let referenceView = self.componentHost.findTaggedView(tag: settingsTag) else { return } @@ -1083,10 +1079,6 @@ public class BrowserScreen: ViewController, MinimizableController { return } - if let animationComponentView = referenceView.componentView.view as? LottieComponent.View { - animationComponentView.playOnce() - } - if let webContent = content as? BrowserWebContent { webContent.requestInstantView() } @@ -1103,7 +1095,7 @@ public class BrowserScreen: ViewController, MinimizableController { } } - let source: ContextContentSource = .reference(BrowserReferenceContentSource(controller: controller, sourceView: referenceView.referenceNode.view)) + let source: ContextContentSource = .reference(BrowserReferenceContentSource(controller: controller, sourceView: referenceView)) let items: Signal = combineLatest( queue: Queue.mainQueue(), @@ -1549,6 +1541,8 @@ public class BrowserScreen: ViewController, MinimizableController { super.init(navigationBarPresentationData: nil) + self._hasGlassStyle = true + self.navigationPresentation = .modalInCompactLayout self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) @@ -1731,7 +1725,7 @@ private final class BrowserContentComponent: Component { self.addSubview(component.content) } - let collapsedHeight: CGFloat = 24.0 + let collapsedHeight: CGFloat = 54.0 let topInset: CGFloat = component.navigationBarHeight * (1.0 - component.scrollingPanelOffsetFraction) + (component.insets.top + collapsedHeight) * component.scrollingPanelOffsetFraction let bottomInset = component.hasBottomPanel ? (49.0 + component.insets.bottom) * (1.0 - component.scrollingPanelOffsetFraction) : 0.0 let insets = UIEdgeInsets(top: topInset, left: component.insets.left, bottom: bottomInset, right: component.insets.right) diff --git a/submodules/BrowserUI/Sources/BrowserSearchBarComponent.swift b/submodules/BrowserUI/Sources/BrowserSearchBarComponent.swift index 9678ab3b..104ecaaa 100644 --- a/submodules/BrowserUI/Sources/BrowserSearchBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserSearchBarComponent.swift @@ -7,6 +7,7 @@ import ComponentFlow import TelegramPresentationData import AccountContext import BundleIconComponent +import SearchInputPanelComponent final class SearchBarContentComponent: Component { public typealias EnvironmentType = BrowserNavigationBarEnvironment @@ -35,112 +36,16 @@ final class SearchBarContentComponent: Component { return true } - final class View: UIView, UITextFieldDelegate { - private final class SearchTextField: UITextField { - override func textRect(forBounds bounds: CGRect) -> CGRect { - return bounds.integral - } - } - - private struct Params: Equatable { - var theme: PresentationTheme - var strings: PresentationStrings - var size: CGSize - - static func ==(lhs: Params, rhs: Params) -> Bool { - if lhs.theme !== rhs.theme { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.size != rhs.size { - return false - } - return true - } - } - + final class View: UIView { private let queryPromise = ValuePromise() private var queryDisposable: Disposable? - private let backgroundLayer: SimpleLayer + private let searchInput = ComponentView() - private let iconView: UIImageView - - private let clearIconView: UIImageView - private let clearIconButton: HighlightTrackingButton - - private let cancelButtonTitle: ComponentView - private let cancelButton: HighlightTrackingButton - - private var placeholderContent = ComponentView() - - private var textFrame: CGRect? - private var textField: SearchTextField? - - private var tapRecognizer: UITapGestureRecognizer? - - private var params: Params? private var component: SearchBarContentComponent? - init() { - self.backgroundLayer = SimpleLayer() - - self.iconView = UIImageView() - - self.clearIconView = UIImageView() - self.clearIconButton = HighlightableButton() - self.clearIconView.isHidden = true - self.clearIconButton.isHidden = true - - self.cancelButtonTitle = ComponentView() - self.cancelButton = HighlightTrackingButton() - - super.init(frame: CGRect()) - - self.layer.addSublayer(self.backgroundLayer) - - self.addSubview(self.iconView) - self.addSubview(self.clearIconView) - self.addSubview(self.clearIconButton) - - self.addSubview(self.cancelButton) - self.clipsToBounds = true - - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) - self.tapRecognizer = tapRecognizer - self.addGestureRecognizer(tapRecognizer) - - self.cancelButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.layer.removeAnimation(forKey: "opacity") - cancelButtonTitleView.alpha = 0.4 - } - } else { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.alpha = 1.0 - cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } - self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside) - - self.clearIconButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.clearIconView.layer.removeAnimation(forKey: "opacity") - strongSelf.clearIconView.alpha = 0.4 - } else { - strongSelf.clearIconView.alpha = 1.0 - strongSelf.clearIconView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - self.clearIconButton.addTarget(self, action: #selector(self.clearPressed), for: .touchUpInside) + override init(frame: CGRect) { + super.init(frame: frame) let throttledSearchQuery = self.queryPromise.get() |> mapToSignal { query -> Signal in @@ -164,194 +69,46 @@ final class SearchBarContentComponent: Component { fatalError("init(coder:) has not been implemented") } - @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.activateTextInput() - } - } - - private func activateTextInput() { - if self.textField == nil, let textFrame = self.textFrame { - let backgroundFrame = self.backgroundLayer.frame - let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX - 32.0, height: backgroundFrame.height)) - - let textField = SearchTextField(frame: textFieldFrame) - textField.clipsToBounds = true - textField.autocorrectionType = .no - textField.returnKeyType = .search - self.textField = textField - self.insertSubview(textField, belowSubview: self.clearIconView) - textField.delegate = self - textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) - } - - guard !(self.textField?.isFirstResponder ?? false) else { - return - } - - self.textField?.becomeFirstResponder() - } - - @objc private func cancelPressed() { - self.clearIconView.isHidden = true - self.clearIconButton.isHidden = true - - let textField = self.textField - self.textField = nil - - self.component?.performAction.invoke(.updateSearchActive(false)) - - if let textField { - textField.resignFirstResponder() - textField.removeFromSuperview() - } - } - - @objc private func clearPressed() { - guard let textField = self.textField else { - return - } - textField.text = "" - self.textFieldChanged(textField) - } - - func deactivate() { - if let text = self.textField?.text, !text.isEmpty { - self.textField?.endEditing(true) - } else { - self.cancelPressed() - } - } - - public func textFieldDidBeginEditing(_ textField: UITextField) { - } - - public func textFieldDidEndEditing(_ textField: UITextField) { - } - - public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.endEditing(true) - return false - } - - @objc private func textFieldChanged(_ textField: UITextField) { - let text = textField.text ?? "" - - self.clearIconView.isHidden = text.isEmpty - self.clearIconButton.isHidden = text.isEmpty - self.placeholderContent.view?.isHidden = !text.isEmpty - - self.queryPromise.set(text) - - if let params = self.params { - self.update(theme: params.theme, strings: params.strings, size: params.size, transition: .immediate) - } - } - func update(component: SearchBarContentComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { self.component = component - self.update(theme: component.theme, strings: component.strings, size: availableSize, transition: transition) - self.activateTextInput() - - return availableSize - } - - public func update(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, transition: ComponentTransition) { - let params = Params( - theme: theme, - strings: strings, - size: size - ) - - if self.params == params { - return - } - - let isActiveWithText = true - - if self.params?.theme !== theme { - self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.iconView.tintColor = theme.rootController.navigationSearchBar.inputIconColor - self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.clearIconView.tintColor = theme.rootController.navigationSearchBar.inputClearButtonColor - } - - self.params = params - - let sideInset: CGFloat = 10.0 - let inputHeight: CGFloat = 36.0 - let topInset: CGFloat = (size.height - inputHeight) / 2.0 - - let sideTextInset: CGFloat = sideInset + 4.0 + 17.0 - - self.backgroundLayer.backgroundColor = theme.rootController.navigationSearchBar.inputFillColor.cgColor - self.backgroundLayer.cornerRadius = 10.5 - - let cancelTextSize = self.cancelButtonTitle.update( - transition: .immediate, - component: AnyComponent(Text( - text: strings.Common_Cancel, - font: Font.regular(17.0), - color: theme.rootController.navigationBar.accentTextColor - )), - environment: {}, - containerSize: CGSize(width: size.width - 32.0, height: 100.0) - ) - - let cancelButtonSpacing: CGFloat = 8.0 - - var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight)) - if isActiveWithText { - backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing - } - transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame) - - transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height))) - - let textX: CGFloat = backgroundFrame.minX + sideTextInset - let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) - self.textFrame = textFrame - - if let image = self.iconView.image { - let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + 5.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - transition.setFrame(view: self.iconView, frame: iconFrame) - } - - let placeholderSize = self.placeholderContent.update( + let searchInputSize = self.searchInput.update( transition: transition, component: AnyComponent( - Text(text: strings.Common_Search, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) + SearchInputPanelComponent( + theme: component.theme, + strings: component.strings, + metrics: .init(widthClass: .compact, heightClass: .compact, orientation: nil), + safeInsets: UIEdgeInsets(), + placeholder: component.strings.Common_Search, + hasEdgeEffect: false, + updated: { [weak self] query in + guard let self else { + return + } + self.queryPromise.set(query) + }, + cancel: { [weak self] in + guard let self else { + return + } + self.component?.performAction.invoke(.updateSearchActive(false)) + } + ) ), environment: {}, - containerSize: size + containerSize: availableSize ) - if let placeholderContentView = self.placeholderContent.view { - if placeholderContentView.superview == nil { - self.addSubview(placeholderContentView) + if let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View { + if searchInputView.superview == nil { + self.addSubview(searchInputView) + + searchInputView.activateInput() } - let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.midY - placeholderSize.height / 2.0), size: placeholderSize) - transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame) - } - - if let image = self.clearIconView.image { - let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - transition.setFrame(view: self.clearIconView, frame: iconFrame) - transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0)) - } - - if let cancelButtonTitleComponentView = self.cancelButtonTitle.view { - if cancelButtonTitleComponentView.superview == nil { - self.addSubview(cancelButtonTitleComponentView) - cancelButtonTitleComponentView.isUserInteractionEnabled = false - } - transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) - } - - if let textField = self.textField { - textField.textColor = theme.rootController.navigationSearchBar.inputTextColor - transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideTextInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideTextInset - 32.0, height: backgroundFrame.height))) + transition.setFrame(view: searchInputView, frame: CGRect(origin: .zero, size: searchInputSize)) } + + return availableSize } } diff --git a/submodules/BrowserUI/Sources/BrowserTitleBarComponent.swift b/submodules/BrowserUI/Sources/BrowserTitleBarComponent.swift index a362da7d..d21be0fa 100644 --- a/submodules/BrowserUI/Sources/BrowserTitleBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserTitleBarComponent.swift @@ -9,19 +9,26 @@ import AccountContext import BundleIconComponent import MultilineTextComponent import UrlEscaping +import GlassBackgroundComponent final class TitleBarContentComponent: Component { public typealias EnvironmentType = BrowserNavigationBarEnvironment let theme: PresentationTheme let title: String + let readingProgress: CGFloat + let loadingProgress: Double? init( theme: PresentationTheme, - title: String + title: String, + readingProgress: CGFloat, + loadingProgress: Double? ) { self.theme = theme self.title = title + self.readingProgress = readingProgress + self.loadingProgress = loadingProgress } static func ==(lhs: TitleBarContentComponent, rhs: TitleBarContentComponent) -> Bool { @@ -31,15 +38,30 @@ final class TitleBarContentComponent: Component { if lhs.title != rhs.title { return false } + if lhs.readingProgress != rhs.readingProgress { + return false + } + if lhs.loadingProgress != rhs.loadingProgress { + return false + } return true } final class View: UIView { + private let backgroundView = GlassBackgroundView() + private let clippingView = UIView() + private let readingProgressView = UIView() private var titleContent = ComponentView() private var component: TitleBarContentComponent? init() { super.init(frame: CGRect()) + + self.clippingView.clipsToBounds = true + + self.addSubview(self.backgroundView) + self.backgroundView.contentView.addSubview(self.clippingView) + self.clippingView.addSubview(self.readingProgressView) } required public init?(coder: NSCoder) { @@ -48,7 +70,9 @@ final class TitleBarContentComponent: Component { func update(component: TitleBarContentComponent, availableSize: CGSize, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component - + + let collapseFraction = environment[BrowserNavigationBarEnvironment.self].fraction + let titleSize = self.titleContent.update( transition: transition, component: AnyComponent( @@ -60,7 +84,7 @@ final class TitleBarContentComponent: Component { ) ), environment: {}, - containerSize: CGSize(width: availableSize.width - 36.0, height: availableSize.height) + containerSize: CGSize(width: availableSize.width - 42.0, height: availableSize.height) ) let titleContentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - titleSize.height) / 2.0)), size: titleSize) if let titleContentView = self.titleContent.view { @@ -71,6 +95,18 @@ final class TitleBarContentComponent: Component { titleContentView.bounds = CGRect(origin: .zero, size: titleContentFrame.size) } + let expandedBackgroundWidth = availableSize.width - 14.0 * 2.0 + let collapsedBackgroundWidth = titleSize.width + 32.0 + let backgroundSize = CGSize(width: expandedBackgroundWidth * (1.0 - collapseFraction) + collapsedBackgroundWidth * collapseFraction, height: 44.0) + self.backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: transition) + let backgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - backgroundSize.width) / 2.0), y: floor((availableSize.height - backgroundSize.height) / 2.0)), size: backgroundSize) + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + transition.setFrame(view: self.clippingView, frame: CGRect(origin: .zero, size: backgroundFrame.size)) + transition.setCornerRadius(layer: self.clippingView.layer, cornerRadius: backgroundFrame.size.height * 0.5) + + self.readingProgressView.backgroundColor = component.theme.rootController.navigationBar.primaryTextColor.withMultipliedAlpha(0.07) + self.readingProgressView.frame = CGRect(origin: .zero, size: CGSize(width: backgroundSize.width * component.readingProgress, height: backgroundSize.height)) + return availableSize } } diff --git a/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift b/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift index 10834cdf..8f695ffc 100644 --- a/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift @@ -6,28 +6,24 @@ import BlurredBackgroundComponent import BundleIconComponent import TelegramPresentationData import ContextReferenceButtonComponent +import GlassBackgroundComponent +import EdgeEffect final class BrowserToolbarComponent: CombinedComponent { - let backgroundColor: UIColor - let separatorColor: UIColor - let textColor: UIColor + let theme: PresentationTheme let bottomInset: CGFloat let sideInset: CGFloat let item: AnyComponentWithIdentity? let collapseFraction: CGFloat init( - backgroundColor: UIColor, - separatorColor: UIColor, - textColor: UIColor, + theme: PresentationTheme, bottomInset: CGFloat, sideInset: CGFloat, item: AnyComponentWithIdentity?, collapseFraction: CGFloat ) { - self.backgroundColor = backgroundColor - self.separatorColor = separatorColor - self.textColor = textColor + self.theme = theme self.bottomInset = bottomInset self.sideInset = sideInset self.item = item @@ -35,13 +31,7 @@ final class BrowserToolbarComponent: CombinedComponent { } static func ==(lhs: BrowserToolbarComponent, rhs: BrowserToolbarComponent) -> Bool { - if lhs.backgroundColor != rhs.backgroundColor { - return false - } - if lhs.separatorColor != rhs.separatorColor { - return false - } - if lhs.textColor != rhs.textColor { + if lhs.theme !== rhs.theme { return false } if lhs.bottomInset != rhs.bottomInset { @@ -60,54 +50,70 @@ final class BrowserToolbarComponent: CombinedComponent { } static var body: Body { - let background = Child(BlurredBackgroundComponent.self) - let separator = Child(Rectangle.self) + let edgeEffect = Child(EdgeEffectComponent.self) + let background = Child(GlassBackgroundComponent.self) let centerItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) return { context in - let contentHeight: CGFloat = 49.0 + let contentHeight: CGFloat = 56.0 let totalHeight = contentHeight + context.component.bottomInset let offset = context.component.collapseFraction * totalHeight let size = CGSize(width: context.availableSize.width, height: totalHeight) - let background = background.update( - component: BlurredBackgroundComponent(color: context.component.backgroundColor), - availableSize: CGSize(width: size.width, height: size.height), + let backgroundHeight: CGFloat = 48.0 + let edgeEffectHeight = totalHeight + let edgeEffect = edgeEffect.update( + component: EdgeEffectComponent( + color: .clear, + blur: true, + alpha: 1.0, + size: CGSize(width: size.width, height: edgeEffectHeight), + edge: .bottom, + edgeSize: edgeEffectHeight + ), + availableSize: CGSize(width: size.width, height: edgeEffectHeight), transition: context.transition ) - - let separator = separator.update( - component: Rectangle(color: context.component.separatorColor, height: UIScreenPixel), - availableSize: CGSize(width: size.width, height: size.height), - transition: context.transition + context.add(edgeEffect + .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0 + offset)) ) - + let item = context.component.item.flatMap { item in return centerItems[item.id].update( component: item.component, - availableSize: CGSize(width: context.availableSize.width - context.component.sideInset * 2.0, height: contentHeight), + availableSize: CGSize(width: context.availableSize.width - context.component.sideInset * 2.0, height: backgroundHeight), transition: context.transition ) } + let contentWidth = item?.size.width ?? 0.0 + + let backgroundSize = CGSize(width: contentWidth, height: backgroundHeight) + let background = background.update( + component: GlassBackgroundComponent( + size: backgroundSize, + cornerRadius: backgroundHeight * 0.5, + isDark: context.component.theme.overallDarkAppearance, + tintColor: .init(kind: .panel, color: UIColor(white: context.component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), + isInteractive: true + ), + availableSize: backgroundSize, + transition: context.transition + ) context.add(background - .position(CGPoint(x: size.width / 2.0, y: size.height / 2.0 + offset)) + .position(CGPoint(x: size.width / 2.0, y: backgroundSize.height / 2.0 + offset)) ) - context.add(separator - .position(CGPoint(x: size.width / 2.0, y: 0.0 + offset)) - ) - if let centerItem = item { context.add(centerItem - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight / 2.0 + offset)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: backgroundSize.height / 2.0 + offset)) .appear(ComponentTransition.Appear({ _, view, transition in - transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: size.height), to: .zero, additive: true) + transition.animateBlur(layer: view.layer, fromRadius: 10.0, toRadius: 0.0) + transition.animateAlpha(view: view, from: 0.0, to: 1.0) })) .disappear(ComponentTransition.Disappear({ view, transition, completion in - let from = view.center - view.center = from.offsetBy(dx: 0.0, dy: size.height) - transition.animatePosition(view: view, from: from, to: view.center, completion: { _ in + transition.animateBlur(layer: view.layer, fromRadius: 0.0, toRadius: 10.0) + transition.setAlpha(view: view, alpha: 0.0, completion: { _ in completion() }) })) @@ -120,8 +126,7 @@ final class BrowserToolbarComponent: CombinedComponent { } final class NavigationToolbarContentComponent: CombinedComponent { - let accentColor: UIColor - let textColor: UIColor + let theme: PresentationTheme let canGoBack: Bool let canGoForward: Bool let canOpenIn: Bool @@ -131,8 +136,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { let performHoldAction: (UIView, ContextGesture?, BrowserScreen.Action) -> Void init( - accentColor: UIColor, - textColor: UIColor, + theme: PresentationTheme, canGoBack: Bool, canGoForward: Bool, canOpenIn: Bool, @@ -141,8 +145,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { performAction: ActionSlot, performHoldAction: @escaping (UIView, ContextGesture?, BrowserScreen.Action) -> Void ) { - self.accentColor = accentColor - self.textColor = textColor + self.theme = theme self.canGoBack = canGoBack self.canGoForward = canGoForward self.canOpenIn = canOpenIn @@ -153,10 +156,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { } static func ==(lhs: NavigationToolbarContentComponent, rhs: NavigationToolbarContentComponent) -> Bool { - if lhs.accentColor != rhs.accentColor { - return false - } - if lhs.textColor != rhs.textColor { + if lhs.theme !== rhs.theme { return false } if lhs.canGoBack != rhs.canGoBack { @@ -191,26 +191,20 @@ final class NavigationToolbarContentComponent: CombinedComponent { let performAction = context.component.performAction let performHoldAction = context.component.performHoldAction - let sideInset: CGFloat = 5.0 - let buttonSize = CGSize(width: 50.0, height: availableSize.height) + var size = CGSize(width: 0.0, height: 48.0) + let buttonSize = CGSize(width: 50.0, height: size.height) - var buttonCount = 3 - if context.component.canShare { - buttonCount += 1 - } - if context.component.canOpenIn { - buttonCount += 1 - } - - let spacing = (availableSize.width - buttonSize.width * CGFloat(buttonCount) - sideInset * 2.0) / CGFloat(buttonCount - 1) + let sideInset: CGFloat = 34.0 + let spacing: CGFloat = 66.0 + let textColor = context.component.theme.rootController.navigationBar.primaryTextColor let canShare = context.component.canShare let share = share.update( component: Button( content: AnyComponent( BundleIconComponent( - name: "Chat List/NavigationShare", - tintColor: context.component.accentColor + name: "Instant View/Share", + tintColor: textColor ) ), action: { @@ -224,22 +218,14 @@ final class NavigationToolbarContentComponent: CombinedComponent { ) if context.component.isDocument { - if !context.component.canShare { - context.add(share - .position(CGPoint(x: availableSize.width / 2.0, y: 10000.0)) - ) - } else { - context.add(share - .position(CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) - ) - } - + var originX: CGFloat = sideInset + let search = search.update( component: Button( content: AnyComponent( BundleIconComponent( - name: "Chat List/SearchIcon", - tintColor: context.component.accentColor + name: "Instant View/Search", + tintColor: textColor ) ), action: { @@ -250,15 +236,27 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(search - .position(CGPoint(x: sideInset + search.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) + originX += spacing + + if !context.component.canShare { + context.add(share + .position(CGPoint(x: availableSize.width / 2.0, y: 10000.0)) + ) + } else { + context.add(share + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) + ) + originX += spacing + } let quickLook = quickLook.update( component: Button( content: AnyComponent( BundleIconComponent( name: "Instant View/OpenDocument", - tintColor: context.component.accentColor + tintColor: textColor ) ), action: { @@ -269,16 +267,19 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(quickLook - .position(CGPoint(x: context.availableSize.width - sideInset - quickLook.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) + size.width = originX + sideInset } else { + var originX: CGFloat = sideInset + let canGoBack = context.component.canGoBack let back = back.update( component: ContextReferenceButtonComponent( content: AnyComponent( BundleIconComponent( name: "Instant View/Back", - tintColor: canGoBack ? context.component.accentColor : context.component.accentColor.withAlphaComponent(0.4) + tintColor: canGoBack ? textColor : textColor.withAlphaComponent(0.4) ) ), minSize: buttonSize, @@ -297,8 +298,9 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(back - .position(CGPoint(x: sideInset + back.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: sideInset, y: availableSize.height / 2.0)) ) + originX += spacing let canGoForward = context.component.canGoForward let forward = forward.update( @@ -306,7 +308,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Instant View/Forward", - tintColor: canGoForward ? context.component.accentColor : context.component.accentColor.withAlphaComponent(0.4) + tintColor: canGoForward ? textColor : textColor.withAlphaComponent(0.4) ) ), minSize: buttonSize, @@ -325,19 +327,21 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(forward - .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) + originX += spacing context.add(share - .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) + originX += spacing let bookmark = bookmark.update( component: Button( content: AnyComponent( BundleIconComponent( name: "Instant View/Bookmark", - tintColor: context.component.accentColor + tintColor: textColor ) ), action: { @@ -348,16 +352,18 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(bookmark - .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + bookmark.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) if context.component.canOpenIn { + originX += spacing + let openIn = openIn.update( component: Button( content: AnyComponent( BundleIconComponent( name: "Instant View/Browser", - tintColor: context.component.accentColor + tintColor: textColor ) ), action: { @@ -368,34 +374,36 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) context.add(openIn - .position(CGPoint(x: sideInset + back.size.width + spacing + forward.size.width + spacing + share.size.width + spacing + bookmark.size.width + spacing + openIn.size.width / 2.0, y: availableSize.height / 2.0)) + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) } + + size.width = originX + sideInset } - return availableSize + return size } } } final class SearchToolbarContentComponent: CombinedComponent { + let theme: PresentationTheme let strings: PresentationStrings - let textColor: UIColor let index: Int let count: Int let isEmpty: Bool let performAction: ActionSlot init( + theme: PresentationTheme, strings: PresentationStrings, - textColor: UIColor, index: Int, count: Int, isEmpty: Bool, performAction: ActionSlot ) { + self.theme = theme self.strings = strings - self.textColor = textColor self.index = index self.count = count self.isEmpty = isEmpty @@ -403,10 +411,10 @@ final class SearchToolbarContentComponent: CombinedComponent { } static func ==(lhs: SearchToolbarContentComponent, rhs: SearchToolbarContentComponent) -> Bool { - if lhs.strings !== rhs.strings { + if lhs.theme !== rhs.theme { return false } - if lhs.textColor != rhs.textColor { + if lhs.strings !== rhs.strings { return false } if lhs.index != rhs.index { @@ -430,15 +438,17 @@ final class SearchToolbarContentComponent: CombinedComponent { let availableSize = context.availableSize let performAction = context.component.performAction - let sideInset: CGFloat = 3.0 + let sideInset: CGFloat = 60.0 let buttonSize = CGSize(width: 50.0, height: availableSize.height) + let textColor = context.component.theme.rootController.navigationBar.primaryTextColor + let down = down.update( component: Button( content: AnyComponent( BundleIconComponent( name: "Chat/Input/Search/DownButton", - tintColor: context.component.textColor + tintColor: textColor ) ), isEnabled: context.component.count > 0, @@ -458,7 +468,7 @@ final class SearchToolbarContentComponent: CombinedComponent { content: AnyComponent( BundleIconComponent( name: "Chat/Input/Search/UpButton", - tintColor: context.component.textColor + tintColor: textColor ) ), isEnabled: context.component.count > 0, @@ -486,7 +496,7 @@ final class SearchToolbarContentComponent: CombinedComponent { component: Text( text: currentText, font: Font.regular(15.0), - color: context.component.textColor + color: textColor ), availableSize: availableSize, transition: .easeInOut(duration: 0.2) @@ -495,7 +505,7 @@ final class SearchToolbarContentComponent: CombinedComponent { .position(CGPoint(x: availableSize.width - sideInset - down.size.width - up.size.width - text.size.width / 2.0, y: availableSize.height / 2.0)) ) - return availableSize + return CGSize(width: availableSize.width - 60.0, height: 48.0) } } } diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index 20a3dbc5..21107048 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -23,6 +23,7 @@ import SaveProgressScreen import DeviceModel import LegacyMediaPickerUI import PassKit +import AlertComponent private final class TonSchemeHandler: NSObject, WKURLSchemeHandler { private final class PendingTask { @@ -1258,12 +1259,20 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } var completed = false - let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: message, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - if !completed { - completed = true - completionHandler() - } - })]) + + let alertController = AlertScreen( + context: self.context, + title: nil, + text: message, + actions: [ + .init(title: presentationData.strings.Common_OK, type: .default, action: { + if !completed { + completed = true + completionHandler() + } + }) + ] + ) alertController.dismissed = { byOutsideTap in if byOutsideTap { if !completed { @@ -1278,17 +1287,26 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } var completed = false - let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: message, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - if !completed { - completed = true - completionHandler(false) - } - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - if !completed { - completed = true - completionHandler(true) - } - })]) + + let alertController = AlertScreen( + context: self.context, + title: nil, + text: message, + actions: [ + .init(title: presentationData.strings.Common_Cancel, action: { + if !completed { + completed = true + completionHandler(false) + } + }), + .init(title: presentationData.strings.Common_OK, type: .default, action: { + if !completed { + completed = true + completionHandler(true) + } + }) + ] + ) alertController.dismissed = { byOutsideTap in if byOutsideTap { if !completed { @@ -1302,24 +1320,28 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { var completed = false - let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: prompt, value: defaultText, apply: { value in - if !completed { - completed = true - if let value = value { - completionHandler(value) - } else { - completionHandler(nil) + let promptController = promptController( + context: self.context, + updatedPresentationData: nil, + text: prompt, + value: defaultText, + apply: { value in + if !completed { + completed = true + if let value = value { + completionHandler(value) + } else { + completionHandler(nil) + } } - } - }) - promptController.dismissed = { byOutsideTap in - if byOutsideTap { + }, + dismissed: { if !completed { completed = true completionHandler(nil) } } - } + ) self.present(promptController, nil) } @@ -1356,17 +1378,25 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU private func presentDownloadConfirmation(fileName: String, proceed: @escaping (Bool) -> Void) { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } var completed = false - let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: presentationData.strings.WebBrowser_Download_Confirmation(fileName).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - if !completed { - completed = true - proceed(false) - } - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebBrowser_Download_Download, action: { - if !completed { - completed = true - proceed(true) - } - })]) + let alertController = AlertScreen( + context: self.context, + title: nil, + text: presentationData.strings.WebBrowser_Download_Confirmation(fileName).string, + actions: [ + .init(title: presentationData.strings.Common_Cancel, action: { + if !completed { + completed = true + proceed(false) + } + }), + .init(title: presentationData.strings.WebBrowser_Download_Download, type: .default, action: { + if !completed { + completed = true + proceed(true) + } + }) + ] + ) alertController.dismissed = { byOutsideTap in if byOutsideTap { if !completed { diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index cb97a18d..eb51c84e 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -1603,11 +1603,12 @@ public final class CalendarMessageScreen: ViewController { } frames[i] = monthFrame } + contentHeight += navigationHeight self.scrollLayout = (layout.size.width, contentHeight, frames) - self.contextGestureContainerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationHeight)) - self.scrollView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height - navigationHeight)) + self.contextGestureContainerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)) + self.scrollView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)) self.scrollView.contentSize = CGSize(width: layout.size.width, height: contentHeight) self.scrollView.verticalScrollIndicatorInsets = UIEdgeInsets(top: max(layout.intrinsicInsets.bottom, self.scrollView.contentInset.top), left: 0.0, bottom: 0.0, right: layout.size.width - 3.0 - 6.0) @@ -1862,8 +1863,9 @@ public final class CalendarMessageScreen: ViewController { self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + self._hasGlassStyle = true self.navigationPresentation = .modal self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(dismissPressed)), animated: false) diff --git a/submodules/CallListUI/BUILD b/submodules/CallListUI/BUILD index 20d63668..21a966fc 100644 --- a/submodules/CallListUI/BUILD +++ b/submodules/CallListUI/BUILD @@ -34,6 +34,9 @@ swift_library( "//submodules/InviteLinksUI", "//submodules/UndoUI", "//submodules/TelegramCallsUI", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/CallListUI/Sources/CallListCallItem.swift b/submodules/CallListUI/Sources/CallListCallItem.swift index 41f076af..507eeb5d 100644 --- a/submodules/CallListUI/Sources/CallListCallItem.swift +++ b/submodules/CallListUI/Sources/CallListCallItem.swift @@ -243,7 +243,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { self.accessibilityArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.containerNode) @@ -769,7 +769,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { } transition.updateAlpha(node: strongSelf.infoButtonNode, alpha: item.editing ? 0.0 : 1.0) - let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight + var topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight + topHighlightInset -= nodeLayout.insets.top strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size) 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)) diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index b8e54aec..17435c66 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -43,7 +43,7 @@ private final class DeleteAllButtonNode: ASDisplayNode { self.buttonNode.addSubnode(self.titleNode) self.contentNode.contentNode.addSubnode(self.buttonNode) - self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.CallList_DeleteAll, font: Font.regular(17.0), textColor: presentationData.theme.rootController.navigationBar.accentTextColor) + self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.CallList_DeleteAll, font: Font.medium(17.0), textColor: presentationData.theme.chat.inputPanel.panelControlColor) //self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } @@ -54,9 +54,10 @@ private final class DeleteAllButtonNode: ASDisplayNode { override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { let titleSize = self.titleNode.updateLayout(constrainedSize) - self.titleNode.frame = CGRect(origin: CGPoint(), size: titleSize) - self.buttonNode.frame = CGRect(origin: CGPoint(), size: titleSize) - return titleSize + let size = CGSize(width: 10.0 * 2.0 + titleSize.width, height: 44.0) + self.titleNode.frame = CGRect(origin: CGPoint(x: 10.0, y: floorToScreenPixels((size.height - titleSize.height) * 0.5)), size: titleSize) + self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) + return size } override public func layout() { @@ -102,7 +103,7 @@ public final class CallListController: TelegramBaseController { self.segmentedTitleView = ItemListControllerSegmentedTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed], selectedIndex: 0) - super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .none, locationBroadcastPanelSource: .none, groupCallPanelSource: .none) + super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) self.tabBarItemContextActionType = .always @@ -155,6 +156,8 @@ public final class CallListController: TelegramBaseController { if case .navigation = self.mode { self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) } + + self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .immediate) } required public init(coder aDecoder: NSCoder) { @@ -203,7 +206,7 @@ public final class CallListController: TelegramBaseController { } self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) if self.isNodeLoaded { self.controllerNode.updateThemeAndStrings(presentationData: self.presentationData) @@ -359,10 +362,10 @@ public final class CallListController: TelegramBaseController { if empty { switch strongSelf.mode { case .tab: - strongSelf.navigationItem.setLeftBarButton(nil, animated: true) - strongSelf.navigationItem.setRightBarButton(nil, animated: true) + strongSelf.navigationItem.setLeftBarButton(nil, animated: strongSelf.controllerNode.didSetReady) + strongSelf.navigationItem.setRightBarButton(nil, animated: strongSelf.controllerNode.didSetReady) case .navigation: - strongSelf.navigationItem.setRightBarButton(nil, animated: true) + strongSelf.navigationItem.setRightBarButton(nil, animated: strongSelf.controllerNode.didSetReady) } } else { var pressedImpl: (() -> Void)? @@ -379,25 +382,24 @@ public final class CallListController: TelegramBaseController { switch strongSelf.mode { case .tab: if strongSelf.editingMode { - strongSelf.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.donePressed)), animated: true) - strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(customDisplayNode: buttonNode), animated: true) + strongSelf.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.donePressed)), animated: strongSelf.controllerNode.didSetReady) + strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(customDisplayNode: buttonNode), animated: strongSelf.controllerNode.didSetReady) strongSelf.navigationItem.rightBarButtonItem?.setCustomAction({ pressedImpl?() }) } else { - strongSelf.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed)), animated: true) - //strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(strongSelf.presentationData.theme), style: .plain, target: self, action: #selector(strongSelf.callPressed)), animated: true) - strongSelf.navigationItem.setRightBarButton(nil, animated: true) + strongSelf.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed)), animated: strongSelf.controllerNode.didSetReady) + strongSelf.navigationItem.setRightBarButton(nil, animated: strongSelf.controllerNode.didSetReady) } case .navigation: if strongSelf.editingMode { - strongSelf.navigationItem.setLeftBarButton(UIBarButtonItem(customDisplayNode: buttonNode), animated: true) + strongSelf.navigationItem.setLeftBarButton(UIBarButtonItem(customDisplayNode: buttonNode), animated: strongSelf.controllerNode.didSetReady) strongSelf.navigationItem.leftBarButtonItem?.setCustomAction({ pressedImpl?() }) - strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.donePressed)), animated: true) + strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.donePressed)), animated: strongSelf.controllerNode.didSetReady) } else { - strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed)), animated: true) + strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed)), animated: strongSelf.controllerNode.didSetReady) } } } @@ -421,10 +423,15 @@ public final class CallListController: TelegramBaseController { self.displayNodeDidLoad() } + override public var navigationEdgeEffectExtension: CGFloat { + return self.controllerNode.navigationEdgeEffectExtension + } + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) + let navigationLayout = self.navigationLayout(layout: layout) + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationLayout.navigationFrame.maxY, transition: transition) } @objc func callPressed() { @@ -778,6 +785,10 @@ public final class CallListController: TelegramBaseController { let controller = ContextController(presentationData: self.presentationData, source: .reference(CallListTabBarContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) } + + override public func tabBarActivateSearch() { + self.beginCallImpl() + } } private final class CallListTabBarContextReferenceContentSource: ContextReferenceContentSource { diff --git a/submodules/CallListUI/Sources/CallListControllerNode.swift b/submodules/CallListUI/Sources/CallListControllerNode.swift index 9b248b7a..412302fd 100644 --- a/submodules/CallListUI/Sources/CallListControllerNode.swift +++ b/submodules/CallListUI/Sources/CallListControllerNode.swift @@ -15,6 +15,9 @@ import AnimatedStickerNode import TelegramAnimatedStickerNode import AppBundle import ItemListPeerActionItem +import EdgeEffect +import ComponentFlow +import ComponentDisplayAdapters private struct CallListNodeListViewTransition { let callListView: CallListNodeView @@ -185,7 +188,7 @@ final class CallListControllerNode: ASDisplayNode { private var containerLayout: (ContainerViewLayout, CGFloat)? private let _ready = ValuePromise() - private var didSetReady = false + private(set) var didSetReady = false var ready: Signal { return _ready.get() } @@ -220,6 +223,8 @@ final class CallListControllerNode: ASDisplayNode { private let emptyButtonIconNode: ASImageNode private let emptyButtonTextNode: ImmediateTextNode + private let edgeEffectView: EdgeEffectView + private let call: (EngineMessage) -> Void private let joinGroupCall: (EnginePeer.Id, EngineGroupCallDescription) -> Void private let openNewCall: () -> Void @@ -230,6 +235,10 @@ final class CallListControllerNode: ASDisplayNode { private let openGroupCallDisposable = MetaDisposable() + var navigationEdgeEffectExtension: CGFloat { + return max(0.0, self.listNode.edgeEffectExtension) + } + private var previousContentOffset: ListViewVisibleContentOffset? init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EngineMessage) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void, openNewCall: @escaping () -> Void) { @@ -277,6 +286,8 @@ final class CallListControllerNode: ASDisplayNode { self.emptyButtonIconNode.displaysAsynchronously = false self.emptyButtonIconNode.isUserInteractionEnabled = false + self.edgeEffectView = EdgeEffectView() + super.init() self.setViewBlock({ @@ -289,6 +300,8 @@ final class CallListControllerNode: ASDisplayNode { self.addSubnode(self.emptyButtonTextNode) self.addSubnode(self.emptyButtonIconNode) self.addSubnode(self.emptyButtonNode) + + self.view.addSubview(self.edgeEffectView) switch self.mode { case .tab: @@ -673,6 +686,13 @@ final class CallListControllerNode: ASDisplayNode { } } } + + self.listNode.onEdgeEffectExtensionUpdated = { [weak self] transition in + guard let self else { + return + } + self.controller?.updateNavigationEdgeEffectExtension(transition: transition) + } } deinit { @@ -945,5 +965,12 @@ final class CallListControllerNode: ASDisplayNode { self.dequeuedInitialTransitionOnLayout = true self.dequeueTransition() } + + let edgeEffectHeight: CGFloat = layout.intrinsicInsets.bottom + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) + transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: ComponentTransition(transition)) + + self.controller?.updateNavigationEdgeEffectExtension(transition: transition) } } diff --git a/submodules/CallListUI/Sources/CallListGroupCallItem.swift b/submodules/CallListUI/Sources/CallListGroupCallItem.swift index 5875b229..069e6134 100644 --- a/submodules/CallListUI/Sources/CallListGroupCallItem.swift +++ b/submodules/CallListUI/Sources/CallListGroupCallItem.swift @@ -208,7 +208,7 @@ class CallListGroupCallItemNode: ItemListRevealOptionsItemNode { self.accessibilityArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.indicatorNode) @@ -434,7 +434,8 @@ class CallListGroupCallItemNode: ItemListRevealOptionsItemNode { let _ = joinTitleApply() transition.updateFrameAdditive(node: strongSelf.joinTitleNode, frame: CGRect(origin: CGPoint(x: floor((joinButtonSize.width - joinTitleLayout.size.width) / 2.0), y: floor((joinButtonSize.height - joinTitleLayout.size.height) / 2.0) + 1.0), size: joinTitleLayout.size)) - let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight + var topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight + topHighlightInset -= nodeLayout.insets.top 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)) diff --git a/submodules/CallListUI/Sources/CallListHoleItem.swift b/submodules/CallListUI/Sources/CallListHoleItem.swift index 1dd52fda..bf71192f 100644 --- a/submodules/CallListUI/Sources/CallListHoleItem.swift +++ b/submodules/CallListUI/Sources/CallListHoleItem.swift @@ -70,7 +70,7 @@ class CallListHoleItemNode: ListViewItemNode { self.labelNode = TextNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.separatorNode) self.addSubnode(self.labelNode) diff --git a/submodules/ChatListFilterSettingsHeaderItem/Sources/ChatListFilterSettingsHeaderItem.swift b/submodules/ChatListFilterSettingsHeaderItem/Sources/ChatListFilterSettingsHeaderItem.swift index ebe6c50e..4fc9515c 100644 --- a/submodules/ChatListFilterSettingsHeaderItem/Sources/ChatListFilterSettingsHeaderItem.swift +++ b/submodules/ChatListFilterSettingsHeaderItem/Sources/ChatListFilterSettingsHeaderItem.swift @@ -86,7 +86,7 @@ class ChatListFilterSettingsHeaderItemNode: ListViewItemNode { self.animationNode = DefaultAnimatedStickerNodeImpl() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.animationNode) diff --git a/submodules/ChatListSearchItemHeader/BUILD b/submodules/ChatListSearchItemHeader/BUILD index 8154e629..288c626e 100644 --- a/submodules/ChatListSearchItemHeader/BUILD +++ b/submodules/ChatListSearchItemHeader/BUILD @@ -10,9 +10,12 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Display:Display", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/ListSectionHeaderNode", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift index 71b378f3..202513da 100644 --- a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift +++ b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift @@ -4,6 +4,9 @@ import AsyncDisplayKit import Display import TelegramPresentationData import ListSectionHeaderNode +import EdgeEffect +import ComponentFlow +import ComponentDisplayAdapters public enum ChatListSearchItemHeaderType { case localPeers @@ -214,6 +217,7 @@ public final class ChatListSearchItemHeader: ListViewItemHeader { public let action: ((ASDisplayNode) -> Void)? public let height: CGFloat = 28.0 + public let isSticky: Bool = false public init(type: ChatListSearchItemHeaderType, theme: PresentationTheme, strings: PresentationStrings, actionTitle: String? = nil, action: ((ASDisplayNode) -> Void)? = nil) { self.type = type @@ -250,6 +254,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode { private var validLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)? + private var edgeEffectView: EdgeEffectView? private let sectionHeaderNode: ListSectionHeaderNode public init(type: ChatListSearchItemHeaderType, theme: PresentationTheme, strings: PresentationStrings, actionTitle: String?, action: ((ASDisplayNode) -> Void)?) { @@ -263,6 +268,8 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode { super.init() + //self.contributesToEdgeEffect = true + self.sectionHeaderNode.title = type.title(strings: strings).uppercased() self.sectionHeaderNode.action = actionTitle self.sectionHeaderNode.activateAction = action diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index a28100bb..87652ea3 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -29,7 +29,6 @@ swift_library( "//submodules/ActivityIndicator:ActivityIndicator", "//submodules/SearchBarNode:SearchBarNode", "//submodules/ChatListSearchRecentPeersNode:ChatListSearchRecentPeersNode", - "//submodules/ChatListSearchItemNode:ChatListSearchItemNode", "//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader", "//submodules/TemporaryCachedPeerDataManager:TemporaryCachedPeerDataManager", "//submodules/PeerPresenceStatusManager:PeerPresenceStatusManager", @@ -118,6 +117,14 @@ swift_library( "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode", + "//submodules/TelegramUI/Components/HeaderPanelContainerComponent", + "//submodules/TelegramUI/Components/HorizontalTabsComponent", + "//submodules/TelegramUI/Components/GlobalControlPanelsContext", + "//submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent", + "//submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent", + "//submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode", + "//submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatListAdditionalCategoryItem.swift b/submodules/ChatListUI/Sources/ChatListAdditionalCategoryItem.swift index a1614b28..0dd445b2 100644 --- a/submodules/ChatListUI/Sources/ChatListAdditionalCategoryItem.swift +++ b/submodules/ChatListUI/Sources/ChatListAdditionalCategoryItem.swift @@ -169,7 +169,7 @@ public class ChatListAdditionalCategoryItemNode: ItemListRevealOptionsItemNode { self.titleNode = TextNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true diff --git a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift index 9b6974a7..55f028e0 100644 --- a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift +++ b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift @@ -450,10 +450,10 @@ final class ChatListContainerItemNode: ASDisplayNode { self.layoutAdditionalPanels(transition: transition) - let edgeEffectHeight: CGFloat = insets.bottom + let edgeEffectHeight: CGFloat = insets.bottom + 8.0 let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight), size: CGSize(width: size.width, height: edgeEffectHeight)) transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: ComponentTransition(transition)) + self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectFrame.height, 40.0), transition: ComponentTransition(transition)) transition.updateAlpha(layer: self.edgeEffectView.layer, alpha: edgeEffectHeight > 21.0 ? 1.0 : 0.0) } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 1dae5215..c020faa7 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -55,6 +55,11 @@ import TextFormat import AvatarUploadToastScreen import AdsInfoScreen import AdsReportScreen +import SearchBarNode +import ChatListFilterTabContainerNode +import HeaderPanelContainerComponent +import HorizontalTabsComponent +import GlobalControlPanelsContext private final class ContextControllerContentSourceImpl: ContextControllerContentSource { let controller: ViewController @@ -150,9 +155,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private let isReorderingTabsValue = ValuePromise(false) - let tabsNode: SparseNode - private let tabContainerNode: ChatListFilterTabContainerNode - private var tabContainerData: ([ChatListFilterTabEntry], Bool, Int32?)? + private(set) var tabContainerData: ([ChatListFilterTabEntry], Bool, Int32?)? var hasTabs: Bool { if let tabContainerData = self.tabContainerData { let isEmpty = tabContainerData.0.count <= 1 || tabContainerData.1 @@ -162,8 +165,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - var searchTabsNode: SparseNode? - private var hasDownloads: Bool = false private var activeDownloadsDisposable: Disposable? private var clearUnseenDownloadsTimer: SwiftSignalKit.Timer? @@ -221,6 +222,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private var fullScreenEffectView: RippleEffectView? + let globalControlPanelsContext: GlobalControlPanelsContext + private(set) var globalControlPanelsContextState: GlobalControlPanelsContext.State? + private var globalControlPanelsContextStateDisposable: Disposable? + public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) { if self.isNodeLoaded { self.chatListDisplayNode.effectiveContainerNode.updateSelectedChatLocation(data: data as? ChatLocation, progress: progress, transition: transition) @@ -241,21 +246,28 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.animationCache = context.animationCache self.animationRenderer = context.animationRenderer - let groupCallPanelSource: GroupCallPanelSource + var groupCallPanelSource: EnginePeer.Id? + var chatListNotices = false switch self.location { - case .chatList: - groupCallPanelSource = .all + case let .chatList(groupId): + if case .root = groupId { + chatListNotices = true + } case let .forum(peerId): - groupCallPanelSource = .peer(peerId) + groupCallPanelSource = peerId case .savedMessagesChats: - groupCallPanelSource = .none + break } - self.tabsNode = SparseNode() - self.tabContainerNode = ChatListFilterTabContainerNode(context: context) - self.tabsNode.addSubnode(self.tabContainerNode) + self.globalControlPanelsContext = GlobalControlPanelsContext( + context: context, + mediaPlayback: true, + liveLocationMode: .all, + groupCalls: groupCallPanelSource, + chatListNotices: chatListNotices + ) - super.init(context: context, navigationBarPresentationData: nil, mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource) + super.init(context: context, navigationBarPresentationData: nil) self.accessoryPanelContainer = ASDisplayNode() @@ -437,23 +449,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }).strict() if !previewing { - /* - self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder, activate: { [weak self] in - self?.chatListDisplayNode.mainContainerNode.currentItemNode.cancelTracking() - self?.activateSearch(filter: isForum ? .topics : .chats) - }) - self.searchContentNode?.updateExpansionProgress(0.0) - self.navigationBar?.setContentNode(self.searchContentNode, animated: false)*/ - - let tabsIsEmpty: Bool - if let (resolvedItems, displayTabsAtBottom, _) = self.tabContainerData { - tabsIsEmpty = resolvedItems.count <= 1 || displayTabsAtBottom - } else { - tabsIsEmpty = true - } - - self.navigationBar?.secondaryContentHeight = !tabsIsEmpty ? NavigationBar.defaultSecondaryContentHeight : 0.0 - enum State: Equatable { case empty(hasDownloads: Bool) case downloading(progress: Double) @@ -736,16 +731,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard let strongSelf = self else { return } - guard let layout = strongSelf.validLayout else { - return + + if let navigationBarView = strongSelf.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View, let headerPanelsView = navigationBarView.headerPanels as? HeaderPanelContainerComponent.View, let tabsView = headerPanelsView.tabs as? HorizontalTabsComponent.View { + tabsView.updateTabSwitchFraction(fraction: fraction, isDragging: strongSelf.chatListDisplayNode.mainContainerNode.isSwitchingCurrentItemFilterByDragging, transition: ComponentTransition(transition)) } - guard let tabContainerData = strongSelf.tabContainerData else { - return - } - if force { - strongSelf.tabContainerNode.cancelAnimations() - } - strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: tabContainerData.2, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition) } self.reloadFilters() } @@ -769,6 +758,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) self.updateNavigationMetadata() + + self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .immediate) + + self.globalControlPanelsContextStateDisposable = (self.globalControlPanelsContext.state + |> deliverOnMainQueue).startStrict(next: { [weak self] state in + guard let self else { + return + } + self.globalControlPanelsContextState = state + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + }) } required public init(coder aDecoder: NSCoder) { @@ -802,6 +802,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController for (_, disposable) in self.preloadStoryResourceDisposables { disposable.dispose() } + self.globalControlPanelsContextStateDisposable?.dispose() } private func updateNavigationMetadata() { @@ -934,11 +935,7 @@ 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?.0 ?? [], selectedFilter: self.chatListDisplayNode.effectiveContainerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.effectiveContainerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.effectiveContainerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.effectiveContainerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate) - } + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData), transition: .immediate) if self.isNodeLoaded { self.chatListDisplayNode.updatePresentationData(self.presentationData) @@ -947,6 +944,356 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.requestLayout(transition: .immediate) } + func tabContextGesture(id: Int32?, sourceNode: ContextExtractedContentContainingNode?, sourceView: ContextExtractedContentContainingView?, gesture: ContextGesture?, keepInPlace: Bool, isDisabled: Bool) { + let context = self.context + let filterPeersAreMuted: Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> = self.context.engine.peers.currentChatListFilters() + |> take(1) + |> mapToSignal { filters -> Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> in + guard let filter = filters.first(where: { $0.id == id }) else { + return .single(nil) + } + guard case let .filter(_, _, _, data) = filter else { + return .single(nil) + } + + let filterPredicate: ChatListFilterPredicate = chatListFilterPredicate(filter: data, accountPeerId: context.account.peerId) + return context.engine.peers.getChatListPeers(filterPredicate: filterPredicate) + |> mapToSignal { peers -> Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> in + let peerIds = peers.map(\.id) + return context.engine.data.get( + EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.NotificationSettings.init(id:))), + TelegramEngine.EngineData.Item.NotificationSettings.Global() + ) + |> map { list, globalSettings -> (areMuted: Bool, peerIds: [EnginePeer.Id])? in + for peer in peers { + switch list[peer.id]?.muteState { + case .unmuted: + return (false, peerIds) + case .default: + let globalValue: EngineGlobalNotificationSettings.CategorySettings + switch peer { + case .user, .secretChat: + globalValue = globalSettings.privateChats + case .legacyGroup: + globalValue = globalSettings.groupChats + case let .channel(channel): + if case .broadcast = channel.info { + globalValue = globalSettings.channels + } else { + globalValue = globalSettings.groupChats + } + } + if globalValue.enabled { + return (false, peerIds) + } + default: + break + } + } + return (true, peerIds) + } + } + } + + let _ = combineLatest( + queue: Queue.mainQueue(), + self.context.engine.peers.currentChatListFilters(), + self.context.engine.data.get( + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) + ), + filterPeersAreMuted + ).startStandalone(next: { [weak self] filters, premiumLimits, filterPeersAreMuted in + guard let self else { + return + } + var items: [ContextMenuItem] = [] + if let id = id { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_EditFolder, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { c, f in + c?.dismiss(completion: { [weak self] in + guard let self else { + return + } + if isDisabled { + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(self.tabContainerData?.0.count ?? 0), action: { + let controller = PremiumIntroScreen(context: context, source: .folders) + replaceImpl?(controller) + return true + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.push(controller) + } else { + let _ = (self.context.engine.peers.currentChatListFilters() + |> deliverOnMainQueue).startStandalone(next: { [weak self] presetList in + guard let self else { + return + } + var found = false + for filter in presetList { + if filter.id == id { + self.push(chatListFilterPresetController(context: self.context, currentPreset: filter, updated: { _ in })) + f(.dismissWithoutContent) + found = true + break + } + } + if !found { + f(.default) + } + }) + } + }) + }))) + + if let _ = filters.first(where: { $0.id == id }) { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_AddChatsToFolder, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + + if isDisabled { + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(self.tabContainerData?.0.count ?? 0), action: { + let controller = PremiumIntroScreen(context: context, source: .folders) + replaceImpl?(controller) + return true + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.push(controller) + } else { + let _ = combineLatest( + queue: Queue.mainQueue(), + self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) + ), + self.context.engine.peers.currentChatListFilters() + ).startStandalone(next: { [weak self] result, presetList in + guard let self else { + return + } + var found = false + for filter in presetList { + if filter.id == id, case let .filter(_, _, _, data) = filter { + let (accountPeer, limits, premiumLimits) = result + let isPremium = accountPeer?.isPremium ?? false + + let limit = limits.maxFolderChatsCount + let premiumLimit = premiumLimits.maxFolderChatsCount + + if data.includePeers.peers.count >= premiumLimit { + let controller = PremiumLimitScreen(context: self.context, subject: .chatsPerFolder, count: Int32(data.includePeers.peers.count), action: { + return true + }) + self.push(controller) + f(.dismissWithoutContent) + return + } else if data.includePeers.peers.count >= limit && !isPremium { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: self.context, subject: .chatsPerFolder, count: Int32(data.includePeers.peers.count), action: { + let controller = PremiumIntroScreen(context: self.context, source: .chatsPerFolder) + replaceImpl?(controller) + return true + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.push(controller) + f(.dismissWithoutContent) + return + } + + let _ = (self.context.engine.peers.currentChatListFilters() + |> deliverOnMainQueue).startStandalone(next: { [weak self] filters in + guard let self else { + return + } + self.push(chatListFilterAddChatsController(context: self.context, filter: filter, allFilters: filters, limit: limits.maxFolderChatsCount, premiumLimit: premiumLimits.maxFolderChatsCount, isPremium: isPremium, presentUndo: { [weak self] content in + guard let self else { + return + } + self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + })) + f(.dismissWithoutContent) + }) + found = true + break + } + } + if !found { + f(.default) + } + }) + } + }) + }))) + + if let filterEntries = self.tabContainerData?.0 { + for filter in filterEntries { + if case let .filter(filterId, _, unread) = filter, filterId == id { + if unread.value > 0 { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_ReadAll, textColor: .primary, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReadAll"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + self.readAllInFilter(id: id) + }) + }))) + } + + for filter in filters { + if filter.id == filterId, case let .filter(_, title, _, data) = filter { + if let filterPeersAreMuted, filterPeersAreMuted.peerIds.count <= 200 { + items.append(.action(ContextMenuActionItem(text: filterPeersAreMuted.areMuted ? self.presentationData.strings.ChatList_ContextUnmuteAll : self.presentationData.strings.ChatList_ContextMuteAll, textColor: .primary, badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: filterPeersAreMuted.areMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + }) + + guard let self else { + return + } + + let _ = (self.context.engine.peers.updateMultiplePeerMuteSettings(peerIds: filterPeersAreMuted.peerIds, muted: !filterPeersAreMuted.areMuted) + |> deliverOnMainQueue).startStandalone(completed: { [weak self] in + guard let self else { + return + } + + let iconColor: UIColor = .white + let overlayController: UndoOverlayController + if !filterPeersAreMuted.areMuted { + let text = NSMutableAttributedString(string: self.presentationData.strings.ChatList_ToastFolderMutedV2) + let folderNameRange = (text.string as NSString).range(of: "{folder}") + if folderNameRange.location != NSNotFound { + text.replaceCharacters(in: folderNameRange, with: "") + text.insert(title.attributedString(attributes: [ + ChatTextInputAttributes.bold: true + ]), at: folderNameRange.location) + } + + overlayController = UndoOverlayController(presentationData: self.presentationData, content: .universalWithEntities(context: self.context, animation: "anim_profilemute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) + } else { + let text = NSMutableAttributedString(string: self.presentationData.strings.ChatList_ToastFolderUnmutedV2) + let folderNameRange = (text.string as NSString).range(of: "{folder}") + if folderNameRange.location != NSNotFound { + text.replaceCharacters(in: folderNameRange, with: "") + text.insert(title.attributedString(attributes: [ + ChatTextInputAttributes.bold: true + ]), at: folderNameRange.location) + } + + overlayController = UndoOverlayController(presentationData: self.presentationData, content: .universalWithEntities(context: self.context, animation: "anim_profileunmute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) + } + self.present(overlayController, in: .current) + }) + }))) + } + + if !data.includePeers.peers.isEmpty && data.categories.isEmpty && !data.excludeRead && !data.excludeMuted && !data.excludeArchived && data.excludePeers.isEmpty { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_ContextMenuShare, textColor: .primary, badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + self.shareFolder(filterId: filterId, data: data, title: title) + }) + }))) + } + + break + } + } + + break + } + } + } + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_RemoveFolder, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + self.askForFilterRemoval(id: id) + }) + }))) + } + } else { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_EditFolders, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + self.openFilterSettings() + }) + }))) + } + + if filters.count > 1 { + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_ReorderTabs, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { + guard let self else { + return + } + + self.chatListDisplayNode.isReorderingFilters = true + self.isReorderingTabsValue.set(true) + + (self.parent as? TabBarController)?.updateIsTabBarEnabled(false, transition: .animated(duration: 0.2, curve: .easeInOut)) + if let layout = self.validLayout { + self.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + }) + }))) + } + + if let sourceNode { + let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, sourceView: sourceView, keepInPlace: keepInPlace)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) + self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) + } else if let sourceView { + let controller = ContextController(presentationData: self.presentationData, source: .reference(ChatListHeaderBarContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) + self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) + } + }) + } + override public func loadDisplayNode() { self.displayNode = ChatListControllerNode(context: self.context, location: self.location, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, controller: self) @@ -1653,7 +2000,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } else { let contextContentSource: ContextContentSource if peer.id.namespace == Namespaces.Peer.SecretChat, let node = node.subnodes?.first as? ContextExtractedContentContainingNode { - contextContentSource = .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: node, keepInPlace: false)) + contextContentSource = .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: node, sourceView: nil, keepInPlace: false)) } else { var subject: ChatControllerSubject? if case let .search(messageId) = source, let id = messageId { @@ -1669,406 +2016,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - self.tabContainerNode.tabSelected = { [weak self] id, isDisabled in - guard let strongSelf = self else { - return - } - if isDisabled { - let context = strongSelf.context - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: strongSelf.tabContainerNode.filtersCount, action: { - let controller = PremiumIntroScreen(context: context, source: .folders) - replaceImpl?(controller) - return true - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - strongSelf.push(controller) - } else { - strongSelf.selectTab(id: id) - } - } - - self.tabContainerNode.tabRequestedDeletion = { [weak self] id in - if case let .filter(id) = id { - self?.askForFilterRemoval(id: id) - } - } - self.tabContainerNode.presentPremiumTip = { [weak self] in - if let strongSelf = self { - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_reorder", scale: 0.05, colors: [:], title: nil, text: strongSelf.presentationData.strings.ChatListFolderSettings_SubscribeToMoveAll, customUndoText: strongSelf.presentationData.strings.ChatListFolderSettings_SubscribeToMoveAllAction, timeout: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in - if case .undo = action { - let context = strongSelf.context - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumDemoScreen(context: context, subject: .advancedChatManagement, action: { - let controller = PremiumIntroScreen(context: context, source: .folders) - replaceImpl?(controller) - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - strongSelf.push(controller) - } - return false }), in: .current) - } - } - - let tabContextGesture: (Int32?, ContextExtractedContentContainingNode, ContextGesture, Bool, Bool) -> Void = { [weak self] id, sourceNode, gesture, keepInPlace, isDisabled in - guard let strongSelf = self else { - return - } - - let context = strongSelf.context - let filterPeersAreMuted: Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> = strongSelf.context.engine.peers.currentChatListFilters() - |> take(1) - |> mapToSignal { filters -> Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> in - guard let filter = filters.first(where: { $0.id == id }) else { - return .single(nil) - } - guard case let .filter(_, _, _, data) = filter else { - return .single(nil) - } - - let filterPredicate: ChatListFilterPredicate = chatListFilterPredicate(filter: data, accountPeerId: context.account.peerId) - return context.engine.peers.getChatListPeers(filterPredicate: filterPredicate) - |> mapToSignal { peers -> Signal<(areMuted: Bool, peerIds: [EnginePeer.Id])?, NoError> in - let peerIds = peers.map(\.id) - return context.engine.data.get( - EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.NotificationSettings.init(id:))), - TelegramEngine.EngineData.Item.NotificationSettings.Global() - ) - |> map { list, globalSettings -> (areMuted: Bool, peerIds: [EnginePeer.Id])? in - for peer in peers { - switch list[peer.id]?.muteState { - case .unmuted: - return (false, peerIds) - case .default: - let globalValue: EngineGlobalNotificationSettings.CategorySettings - switch peer { - case .user, .secretChat: - globalValue = globalSettings.privateChats - case .legacyGroup: - globalValue = globalSettings.groupChats - case let .channel(channel): - if case .broadcast = channel.info { - globalValue = globalSettings.channels - } else { - globalValue = globalSettings.groupChats - } - } - if globalValue.enabled { - return (false, peerIds) - } - default: - break - } - } - return (true, peerIds) - } - } - } - - let _ = combineLatest( - queue: Queue.mainQueue(), - strongSelf.context.engine.peers.currentChatListFilters(), - strongSelf.context.engine.data.get( - TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) - ), - filterPeersAreMuted - ).startStandalone(next: { [weak self] filters, premiumLimits, filterPeersAreMuted in - guard let strongSelf = self else { - return - } - var items: [ContextMenuItem] = [] - if let id = id { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_EditFolder, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - if isDisabled { - let context = strongSelf.context - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: strongSelf.tabContainerNode.filtersCount, action: { - let controller = PremiumIntroScreen(context: context, source: .folders) - replaceImpl?(controller) - return true - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - strongSelf.push(controller) - } else { - let _ = (strongSelf.context.engine.peers.currentChatListFilters() - |> deliverOnMainQueue).startStandalone(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 !found { - f(.default) - } - }) - } - }) - }))) - - if let _ = filters.first(where: { $0.id == id }) { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_AddChatsToFolder, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - - if isDisabled { - let context = strongSelf.context - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: strongSelf.tabContainerNode.filtersCount, action: { - let controller = PremiumIntroScreen(context: context, source: .folders) - replaceImpl?(controller) - return true - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - strongSelf.push(controller) - } else { - let _ = combineLatest( - queue: Queue.mainQueue(), - strongSelf.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId), - TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), - TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) - ), - strongSelf.context.engine.peers.currentChatListFilters() - ).startStandalone(next: { result, presetList in - guard let strongSelf = self else { - return - } - var found = false - for filter in presetList { - if filter.id == id, case let .filter(_, _, _, data) = filter { - let (accountPeer, limits, premiumLimits) = result - let isPremium = accountPeer?.isPremium ?? false - - let limit = limits.maxFolderChatsCount - let premiumLimit = premiumLimits.maxFolderChatsCount - - if data.includePeers.peers.count >= premiumLimit { - let controller = PremiumLimitScreen(context: strongSelf.context, subject: .chatsPerFolder, count: Int32(data.includePeers.peers.count), action: { - return true - }) - strongSelf.push(controller) - f(.dismissWithoutContent) - return - } else if data.includePeers.peers.count >= limit && !isPremium { - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: strongSelf.context, subject: .chatsPerFolder, count: Int32(data.includePeers.peers.count), action: { - let controller = PremiumIntroScreen(context: strongSelf.context, source: .chatsPerFolder) - replaceImpl?(controller) - return true - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - strongSelf.push(controller) - f(.dismissWithoutContent) - return - } - - let _ = (strongSelf.context.engine.peers.currentChatListFilters() - |> deliverOnMainQueue).startStandalone(next: { filters in - guard let strongSelf = self else { - return - } - strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter, allFilters: filters, limit: limits.maxFolderChatsCount, premiumLimit: premiumLimits.maxFolderChatsCount, isPremium: isPremium, presentUndo: { content in - guard let strongSelf = self else { - return - } - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - })) - f(.dismissWithoutContent) - }) - found = true - break - } - } - if !found { - f(.default) - } - }) - } - }) - }))) - - if let filterEntries = strongSelf.tabContainerData?.0 { - for filter in filterEntries { - if case let .filter(filterId, _, unread) = filter, filterId == id { - if unread.value > 0 { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ReadAll, textColor: .primary, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReadAll"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - strongSelf.readAllInFilter(id: id) - }) - }))) - } - - for filter in filters { - if filter.id == filterId, case let .filter(_, title, _, data) = filter { - if let filterPeersAreMuted, filterPeersAreMuted.peerIds.count <= 200 { - items.append(.action(ContextMenuActionItem(text: filterPeersAreMuted.areMuted ? strongSelf.presentationData.strings.ChatList_ContextUnmuteAll : strongSelf.presentationData.strings.ChatList_ContextMuteAll, textColor: .primary, badge: nil, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: filterPeersAreMuted.areMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - }) - - guard let strongSelf = self else { - return - } - - let _ = (strongSelf.context.engine.peers.updateMultiplePeerMuteSettings(peerIds: filterPeersAreMuted.peerIds, muted: !filterPeersAreMuted.areMuted) - |> deliverOnMainQueue).startStandalone(completed: { - guard let strongSelf = self else { - return - } - - let iconColor: UIColor = .white - let overlayController: UndoOverlayController - if !filterPeersAreMuted.areMuted { - let text = NSMutableAttributedString(string: strongSelf.presentationData.strings.ChatList_ToastFolderMutedV2) - let folderNameRange = (text.string as NSString).range(of: "{folder}") - if folderNameRange.location != NSNotFound { - text.replaceCharacters(in: folderNameRange, with: "") - text.insert(title.attributedString(attributes: [ - ChatTextInputAttributes.bold: true - ]), at: folderNameRange.location) - } - - overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universalWithEntities(context: strongSelf.context, animation: "anim_profilemute", scale: 0.075, colors: [ - "Middle.Group 1.Fill 1": iconColor, - "Top.Group 1.Fill 1": iconColor, - "Bottom.Group 1.Fill 1": iconColor, - "EXAMPLE.Group 1.Fill 1": iconColor, - "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) - } else { - let text = NSMutableAttributedString(string: strongSelf.presentationData.strings.ChatList_ToastFolderUnmutedV2) - let folderNameRange = (text.string as NSString).range(of: "{folder}") - if folderNameRange.location != NSNotFound { - text.replaceCharacters(in: folderNameRange, with: "") - text.insert(title.attributedString(attributes: [ - ChatTextInputAttributes.bold: true - ]), at: folderNameRange.location) - } - - overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universalWithEntities(context: strongSelf.context, animation: "anim_profileunmute", scale: 0.075, colors: [ - "Middle.Group 1.Fill 1": iconColor, - "Top.Group 1.Fill 1": iconColor, - "Bottom.Group 1.Fill 1": iconColor, - "EXAMPLE.Group 1.Fill 1": iconColor, - "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) - } - strongSelf.present(overlayController, in: .current) - }) - }))) - } - - if !data.includePeers.peers.isEmpty && data.categories.isEmpty && !data.excludeRead && !data.excludeMuted && !data.excludeArchived && data.excludePeers.isEmpty { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ContextMenuShare, textColor: .primary, badge: data.hasSharedLinks ? nil : ContextMenuActionBadge(value: strongSelf.presentationData.strings.ChatList_ContextMenuBadgeNew, color: .accent, style: .label), icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - strongSelf.shareFolder(filterId: filterId, data: data, title: title) - }) - }))) - } - - break - } - } - - break - } - } - } - - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_RemoveFolder, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - strongSelf.askForFilterRemoval(id: id) - }) - }))) - } - } else { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_EditFolders, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - strongSelf.openFilterSettings() - }) - }))) - } - - if filters.count > 1 { - items.append(.separator) - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ReorderTabs, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) - }, action: { c, f in - c?.dismiss(completion: { - guard let strongSelf = self else { - return - } - - strongSelf.chatListDisplayNode.isReorderingFilters = true - strongSelf.isReorderingTabsValue.set(true) - - //TODO:update search enabled - //strongSelf.searchContentNode?.setIsEnabled(false, animated: true) - - (strongSelf.parent as? TabBarController)?.updateIsTabBarEnabled(false, transition: .animated(duration: 0.2, curve: .easeInOut)) - if let layout = strongSelf.validLayout { - strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) - } - }) - }))) - } - - let controller = ContextController(presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode, keepInPlace: keepInPlace)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) - strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) - }) - } - self.tabContainerNode.contextGesture = { id, sourceNode, gesture, isDisabled in - tabContextGesture(id, sourceNode, gesture, false, isDisabled) - } - if case .chatList(.root) = self.location { self.ready.set(combineLatest([self.mainReady.get(), self.storiesReady.get()]) |> map { values -> Bool in @@ -2433,7 +2380,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } let context = strongSelf.context var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: strongSelf.tabContainerNode.filtersCount, action: { + let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(strongSelf.tabContainerData?.0.count ?? 0), action: { let controller = PremiumIntroScreen(context: context, source: .folders) replaceImpl?(controller) return true @@ -2598,13 +2545,25 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard let strongSelf, currentValues.contains(.setupPasskey) else { return } - if let navigationController = strongSelf.navigationController as? NavigationController { - let controller = strongSelf.context.sharedContext.makePasskeySetupController(context: strongSelf.context, displaySkip: true, navigationController: navigationController, completion: { - let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPasskey.id).startStandalone() - }, dismiss: { - let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPasskey.id).startStandalone() - }) - navigationController.pushViewController(controller) + + Task { @MainActor [weak strongSelf] in + guard let strongSelf else { + return + } + + let passkeysData = await strongSelf.context.engine.auth.passkeysData().get() + if !passkeysData.isEmpty { + return + } + + if let navigationController = strongSelf.navigationController as? NavigationController { + let controller = strongSelf.context.sharedContext.makePasskeySetupController(context: strongSelf.context, displaySkip: true, navigationController: navigationController, completion: { + let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPasskey.id).startStandalone() + }, dismiss: { + let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPasskey.id).startStandalone() + }) + navigationController.pushViewController(controller) + } } }) @@ -2618,7 +2577,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } strongSelf.didSuggestAutoarchive = true - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_AutoarchiveSuggestion_Title, text: strongSelf.presentationData.strings.ChatList_AutoarchiveSuggestion_Text, actions: [ + strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.ChatList_AutoarchiveSuggestion_Title, text: strongSelf.presentationData.strings.ChatList_AutoarchiveSuggestion_Text, actions: [ TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { guard let strongSelf = self else { return @@ -2670,7 +2629,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return true } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_DismissTitle, text: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_DismissText(value), actions: [ + controller.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_DismissTitle, text: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_DismissText(value), actions: [ TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_DismissActionCancel, action: { }), TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ForcedPasswordSetup_Intro_DismissActionOK, action: { [weak controller] in @@ -2863,14 +2822,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController func updateHeaderContent() -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) { var primaryContent: ChatListHeaderComponent.Content? if let primaryContext = self.primaryContext { - var backTitle: String? - if let previousItem = self.previousItem { - switch previousItem { - case let .item(item): - backTitle = item.title ?? self.presentationData.strings.Common_Back - case .close: - backTitle = self.presentationData.strings.Common_Close - } + var displayBackButton: Bool = false + if self.previousItem != nil { + displayBackButton = true } var navigationBackTitle: String? if case .chatList(.archive) = self.location { @@ -2883,8 +2837,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatListTitle: primaryContext.chatListTitle, leftButton: primaryContext.leftButton, rightButtons: primaryContext.rightButtons, - backTitle: backTitle, - backPressed: backTitle != nil ? { [weak self] in + backPressed: displayBackButton ? { [weak self] in guard let self else { return } @@ -2901,7 +2854,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatListTitle: secondaryContext.chatListTitle, leftButton: secondaryContext.leftButton, rightButtons: secondaryContext.rightButtons, - backTitle: nil, backPressed: { [weak self] in guard let self else { return @@ -2934,7 +2886,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.push(controller) return } - + var reachedCountLimit = false var premiumNeeded = false var hasActiveCall = false @@ -3083,7 +3035,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } func displayContinueLiveStream() { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: self.presentationData.strings.ChatList_AlertResumeLiveStreamTitle, text: self.presentationData.strings.ChatList_AlertResumeLiveStreamText, actions: [ + self.present(textAlertController(context: self.context, title: self.presentationData.strings.ChatList_AlertResumeLiveStreamTitle, text: self.presentationData.strings.ChatList_AlertResumeLiveStreamText, actions: [ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatList_AlertResumeLiveStreamAction, action: { [weak self] in @@ -3473,7 +3425,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }))) } - let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) + let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, sourceView: nil, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) }) } @@ -3550,14 +3502,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController tabContainerOffset += 44.0 + 20.0 } - let navigationBarHeight: CGFloat = 0.0//self.navigationBar?.frame.maxY ?? 0.0 - //let secondaryContentHeight = self.navigationBar?.secondaryContentHeight ?? 0.0 - - transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 46.0))) - - if !skipTabContainerUpdate { - self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.mainContainerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.effectiveContainerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.effectiveContainerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.effectiveContainerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) - } + let navigationBarHeight: CGFloat = 0.0 self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: navigationBarHeight, storiesInset: 0.0, transition: transition) } @@ -3643,10 +3588,35 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return id } } + let _ = defaultFilterIds var reorderedFilterIdsValue: [Int32]? - if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds, reorderedFilterIds != defaultFilterIds { - reorderedFilterIdsValue = reorderedFilterIds + if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View, let headerPanelsView = navigationBarView.headerPanels as? HeaderPanelContainerComponent.View, let tabsView = headerPanelsView.tabs as? HorizontalTabsComponent.View, let reorderedItemIds = tabsView.reorderedItemIds { + reorderedFilterIdsValue = reorderedItemIds.compactMap { item -> Int32? in + guard let value = item.base as? Int32 else { + return nil + } + if value == Int32.min { + return 0 + } + return value + } + } + + if let reorderedFilterIdsValue, let tabContainerData = self.tabContainerData { + var entries: [ChatListFilterTabEntry] = [] + for id in reorderedFilterIdsValue { + let mappedId: ChatListFilterTabEntryId + if id == 0 { + mappedId = .all + } else { + mappedId = .filter(id) + } + if let entry = tabContainerData.0.first(where: { $0.id == mappedId }) { + entries.append(entry) + } + } + self.tabContainerData?.0 = entries } let completion = { [weak self] in @@ -4095,11 +4065,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let layout = strongSelf.validLayout { if wasEmpty != isEmpty { let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate - transition.updateAlpha(node: strongSelf.tabContainerNode, alpha: isEmpty ? 0.0 : 1.0) strongSelf.containerLayoutUpdated(layout, transition: transition) (strongSelf.parent as? TabBarController)?.updateLayout(transition: transition) - } 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.mainContainerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: filtersLimit, transitionFraction: strongSelf.chatListDisplayNode.mainContainerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) } } @@ -4114,7 +4081,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController })) } - private func selectTab(id: ChatListFilterTabEntryId, switchToChatsIfNeeded: Bool = true) { + func selectTab(id: ChatListFilterTabEntryId, switchToChatsIfNeeded: Bool = true) { if self.parent == nil, switchToChatsIfNeeded { if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController { for controller in navigationController.viewControllers { @@ -4510,7 +4477,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) } - private func askForFilterRemoval(id: Int32) { + func askForFilterRemoval(id: Int32) { let apply: () -> Void = { [weak self] in guard let strongSelf = self else { return @@ -4621,7 +4588,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if hasLinks { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatList_AlertDeleteFolderTitle, text: presentationData.strings.ChatList_AlertDeleteFolderText, actions: [ + self.present(textAlertController(context: self.context, title: presentationData.strings.ChatList_AlertDeleteFolderTitle, text: presentationData.strings.ChatList_AlertDeleteFolderText, actions: [ TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: { confirmDeleteFolder() }), @@ -4658,14 +4625,16 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController public private(set) var isSearchActive: Bool = false public func activateSearch(filter: ChatListSearchFilter, query: String? = nil) { + self.activateSearchInternal(isFromTabBar: false, filter: filter, query: query) + } + + public func activateSearchInternal(isFromTabBar: Bool, filter: ChatListSearchFilter, query: String? = nil) { var searchContentNode: NavigationBarSearchContentNode? - if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View { + if !isFromTabBar, let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View { searchContentNode = navigationBarView.searchContentNode } - if let searchContentNode { - self.activateSearch(filter: filter, query: query, skipScrolling: false, searchContentNode: searchContentNode) - } + self.activateSearch(filter: filter, query: query, skipScrolling: false, searchContentNode: searchContentNode) } public func activateSearch(query: String? = nil) { @@ -4679,88 +4648,70 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } private var previousSearchToggleTimestamp: Double? - func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil, skipScrolling: Bool = false, searchContentNode: NavigationBarSearchContentNode) { - let currentTimestamp = CACurrentMediaTime() - if let previousSearchActivationTimestamp = self.previousSearchToggleTimestamp, currentTimestamp < previousSearchActivationTimestamp + 0.6 { - return - } - self.previousSearchToggleTimestamp = currentTimestamp - - if let storyTooltip = self.storyTooltip { - storyTooltip.dismiss() - } - - var filter = filter - if case .forum = self.chatListDisplayNode.effectiveContainerNode.location { - filter = .topics - } - - if self.chatListDisplayNode.searchDisplayController == nil { - /*if !skipScrolling, let searchContentNode = self.searchContentNode, searchContentNode.expansionProgress != 1.0 { - self.scrollToTop?() - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { [weak self] in - self?.activateSearch(filter: filter, query: query, skipScrolling: true) - }) + func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil, skipScrolling: Bool = false, searchContentNode: NavigationBarSearchContentNode?) { + Task { @MainActor [weak self] in + guard let self else { return - }*/ - //TODO:scroll to top? + } + + let currentTimestamp = CACurrentMediaTime() + if let previousSearchActivationTimestamp = self.previousSearchToggleTimestamp, currentTimestamp < previousSearchActivationTimestamp + 0.6 { + return + } + self.previousSearchToggleTimestamp = currentTimestamp - let _ = (combineLatest(self.chatListDisplayNode.mainContainerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(components: [:])) |> take(1)) - |> deliverOnMainQueue).startStandalone(next: { [weak self] _, chatListView in - Task { @MainActor in - guard let strongSelf = self else { - return - } - - /*if let scrollToTop = strongSelf.scrollToTop { - scrollToTop() - }*/ - - let tabsIsEmpty: Bool - if let (resolvedItems, displayTabsAtBottom, _) = strongSelf.tabContainerData { - tabsIsEmpty = resolvedItems.count <= 1 || displayTabsAtBottom - } else { - tabsIsEmpty = true - } - let _ = tabsIsEmpty - //TODO:swap tabs - + if let storyTooltip = self.storyTooltip { + storyTooltip.dismiss() + } + + var filter = filter + if case .forum = self.chatListDisplayNode.effectiveContainerNode.location { + filter = .topics + } + + if self.chatListDisplayNode.searchDisplayController == nil { + let (_, _) = await combineLatest(self.chatListDisplayNode.mainContainerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(components: [:])) |> take(1)).get() + + do { let displaySearchFilters = true - if let filterContainerNodeAndActivate = await strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, hasDownloads: strongSelf.hasDownloads, initialFilter: filter, navigationController: strongSelf.navigationController as? NavigationController) { - let (filterContainerNode, activate) = filterContainerNodeAndActivate - if displaySearchFilters { - let searchTabsNode = SparseNode() - strongSelf.searchTabsNode = searchTabsNode - searchTabsNode.addSubnode(filterContainerNode) - } + if let filterContainerNodeAndActivate = await self.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode?.placeholderNode, displaySearchFilters: displaySearchFilters, hasDownloads: self.hasDownloads, initialFilter: filter, navigationController: self.navigationController as? NavigationController, searchBarIsExternal: searchContentNode == nil) { + let activate = filterContainerNodeAndActivate activate(filter != .downloads) - if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode { + if let searchContentNode = self.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode { searchContentNode.search(filter: filter, query: query) } } let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - strongSelf.setDisplayNavigationBar(false, transition: transition) - - (strongSelf.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.4, curve: .spring)) - } - }) - - self.isSearchActive = true - if let navigationController = self.navigationController as? NavigationController { - for controller in navigationController.globalOverlayControllers { - if let controller = controller as? VoiceChatOverlayController { - controller.updateVisibility() - break + self.setDisplayNavigationBar(false, transition: transition) + if searchContentNode == nil { + self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: true), transition: transition) + + if let searchBarNode = self.currentTabBarSearchNode?() as? SearchBarNode { + self.chatListDisplayNode.searchDisplayController?.setSearchBar(searchBarNode) + searchBarNode.activate() + } + } else { + (self.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: transition) + } + + self.isSearchActive = true + if let navigationController = self.navigationController as? NavigationController { + for controller in navigationController.globalOverlayControllers { + if let controller = controller as? VoiceChatOverlayController { + controller.updateVisibility() + break + } + } } } - } - } else if self.isSearchActive { - if let searchContentNode = self.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode { - searchContentNode.search(filter: filter, query: query) + } else if self.isSearchActive { + if let searchContentNode = self.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode { + searchContentNode.search(filter: filter, query: query) + } } } } @@ -4777,8 +4728,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController var completion: (() -> Void)? - self.searchTabsNode = nil - var searchContentNode: NavigationBarSearchContentNode? if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View { searchContentNode = navigationBarView.searchContentNode @@ -4791,21 +4740,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } completion = self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated) searchContentNode.placeholderNode.frame = previousFrame + } else { + completion = self.chatListDisplayNode.deactivateSearch(placeholderNode: nil, animated: animated) } self.chatListDisplayNode.tempAllowAvatarExpansion = true self.requestLayout(transition: .animated(duration: 0.5, curve: .spring)) self.chatListDisplayNode.tempAllowAvatarExpansion = false - //TODO:swap tabs - let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate - //transition.updateAlpha(node: self.tabContainerNode, alpha: tabsIsEmpty ? 0.0 : 1.0) self.setDisplayNavigationBar(true, transition: transition) completion?() - (self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring)) + self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: transition) + (self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: transition) self.isSearchActive = false if let navigationController = self.navigationController as? NavigationController { @@ -5498,7 +5447,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController deleteForAllConfirmation = strongSelf.presentationData.strings.ChannelInfo_DeleteGroupConfirmation } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: deleteForAllConfirmation, actions: [ + strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: deleteForAllConfirmation, actions: [ TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationAction, action: { @@ -5613,7 +5562,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ]) strongSelf.present(actionSheet, in: .window(.root)) } else { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ + strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: { @@ -5681,7 +5630,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController deleteForAllConfirmation = strongSelf.presentationData.strings.ChatList_DeleteForAllMembersConfirmationText } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: deleteForAllConfirmation, actions: [ + strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: deleteForAllConfirmation, actions: [ TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationAction, action: { @@ -5889,7 +5838,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard let strongSelf = self else { return } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationText, actions: [ + strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationText, actions: [ TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { completion(false) }), @@ -5913,7 +5862,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ]) self.present(actionSheet, in: .window(.root)) } else if peer.peerId == self.context.account.peerId { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ + self.present(textAlertController(context: self.context, title: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { completion(false) }), @@ -6237,7 +6186,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if isDisabled { let context = strongSelf.context var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: strongSelf.tabContainerNode.filtersCount, action: { + let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(strongSelf.tabContainerData?.0.count ?? 0), action: { let controller = PremiumIntroScreen(context: context, source: .folders) replaceImpl?(controller) return true @@ -6261,6 +6210,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) }) } + + override public func tabBarActivateSearch() { + self.activateSearchInternal(isFromTabBar: true, filter: .chats, query: nil) + } + + override public func tabBarDeactivateSearch() { + self.deactivateSearch(animated: true) + } private var playedSignUpCompletedAnimation = false public func playSignUpCompletedAnimation() { @@ -6541,22 +6498,49 @@ private final class ChatListTabBarContextReferenceContentSource: ContextReferenc } } +private final class ChatListHeaderBarContextReferenceContentSource: ContextReferenceContentSource { + let keepInPlace: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center + + private let controller: ChatListController + private let sourceView: ContextExtractedContentContainingView + + init(controller: ChatListController, sourceView: ContextExtractedContentContainingView) { + self.controller = controller + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo( + referenceView: self.sourceView.contentView, + contentAreaInScreenSpace: UIScreen.main.bounds, + actionsPosition: .bottom + ) + } +} + private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource { let keepInPlace: Bool let ignoreContentTouches: Bool = true let blurBackground: Bool = true private let controller: ChatListController - private let sourceNode: ContextExtractedContentContainingNode + private let sourceNode: ContextExtractedContentContainingNode? + private let sourceView: ContextExtractedContentContainingView? - init(controller: ChatListController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool) { + init(controller: ChatListController, sourceNode: ContextExtractedContentContainingNode?, sourceView: ContextExtractedContentContainingView?, keepInPlace: Bool) { self.controller = controller self.sourceNode = sourceNode + self.sourceView = sourceView self.keepInPlace = keepInPlace } func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) + if let sourceNode = self.sourceNode { + return ContextControllerTakeViewInfo(containingItem: .node(sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds) + } else { + return ContextControllerTakeViewInfo(containingItem: .view(self.sourceView!), contentAreaInScreenSpace: UIScreen.main.bounds) + } } func putBack() -> ContextControllerPutBackViewInfo? { @@ -6954,6 +6938,7 @@ private final class ChatListLocationContext { } if strongSelf.toolbar != toolbar { strongSelf.toolbar = toolbar + transition = .animated(duration: 0.4, curve: .spring) if parentController.effectiveContext === strongSelf { parentController.setToolbar(toolbar, transition: transition) } @@ -7221,7 +7206,10 @@ private final class ChatListLocationContext { strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, - content: .custom(presentationData.strings.ChatList_SelectedTopics(Int32(stateAndFilterId.state.selectedThreadIds.count)), nil, false), + displayBackground: false, + content: .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(presentationData.strings.ChatList_SelectedTopics(Int32(stateAndFilterId.state.selectedThreadIds.count))))], subtitle: nil, isEnabled: false), + activities: nil, + networkState: nil, tapped: { }, longTapped: { @@ -7234,7 +7222,10 @@ private final class ChatListLocationContext { strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, + displayBackground: false, content: .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true), + activities: nil, + networkState: nil, tapped: { [weak self] in guard let self else { return diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 3a835731..9847039d 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -21,7 +21,13 @@ import ChatFolderLinkPreviewScreen import ChatListHeaderComponent import StoryPeerListComponent import TelegramNotices -import EdgeEffect +import HeaderPanelContainerComponent +import HorizontalTabsComponent +import PremiumUI +import MediaPlaybackHeaderPanelComponent +import LiveLocationHeaderPanelComponent +import ChatListHeaderNoticeComponent +import ChatListFilterTabContainerNode public enum ChatListContainerNodeFilter: Equatable { case all @@ -132,6 +138,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele } public var currentItemFilterUpdated: ((ChatListFilterTabEntryId, CGFloat, ContainedViewLayoutTransition, Bool) -> Void)? + public private(set) var isSwitchingCurrentItemFilterByDragging: Bool = false public var currentItemFilter: ChatListFilterTabEntryId { return self.currentItemNode.chatListFilter.flatMap { .filter($0.id) } ?? .all } @@ -575,6 +582,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele itemNode.layer.removeAllAnimations() } self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .immediate) + self.isSwitchingCurrentItemFilterByDragging = true self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, true) } } @@ -651,6 +659,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele } } self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, storiesInset: storiesInset, transition: .immediate) + self.isSwitchingCurrentItemFilterByDragging = true self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) } case .cancelled, .ended: @@ -712,6 +721,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele if let switchToId = applyNodeAsCurrent, let itemNode = self.itemNodes[switchToId] { self.applyItemNodeAsCurrent(id: switchToId, itemNode: itemNode) } + self.isSwitchingCurrentItemFilterByDragging = false self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) } default: @@ -1091,9 +1101,10 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { private var toolbarNode: ToolbarNode? var toolbarActionSelected: ((ToolbarActionOption) -> Void)? - private var isSearchDisplayControllerActive: Bool = false + private var isSearchDisplayControllerActive: ChatListNavigationBar.ActiveSearch? private var skipSearchDisplayControllerLayout: Bool = false private(set) var searchDisplayController: SearchDisplayController? + private var disappearingSearchDisplayController: SearchDisplayController? var isReorderingFilters: Bool = false var didBeginSelectingChatsWhileEditing: Bool = false @@ -1353,14 +1364,226 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { private func updateNavigationBar(layout: ContainerViewLayout, deferScrollApplication: Bool, transition: ComponentTransition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) { let headerContent = self.controller?.updateHeaderContent() - var tabsNode: ASDisplayNode? - var tabsNodeIsSearch = false + var panels: [HeaderPanelContainerComponent.Panel] = [] + if let chatListNotice = self.controller?.globalControlPanelsContextState?.chatListNotice { + panels.append(HeaderPanelContainerComponent.Panel( + key: "chatListNotice", + orderIndex: 0, + component: AnyComponent(ChatListHeaderNoticeComponent( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + data: chatListNotice, + activateAction: { [weak self] notice in + guard let self else { + return + } + switch notice { + case .clearStorage: + self.effectiveContainerNode.currentItemNode.interaction?.openStorageManagement() + case .setupPassword: + self.effectiveContainerNode.currentItemNode.interaction?.openPasswordSetup() + case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore: + self.effectiveContainerNode.currentItemNode.interaction?.openPremiumIntro() + case .xmasPremiumGift: + self.effectiveContainerNode.currentItemNode.interaction?.openPremiumGift([], nil) + case .premiumGrace: + self.effectiveContainerNode.currentItemNode.interaction?.openPremiumManagement() + case .setupBirthday: + self.effectiveContainerNode.currentItemNode.interaction?.openBirthdaySetup() + case let .birthdayPremiumGift(peers, birthdays): + self.effectiveContainerNode.currentItemNode.interaction?.openPremiumGift(peers, birthdays) + case .reviewLogin: + break + case let .starsSubscriptionLowBalance(amount, _): + self.effectiveContainerNode.currentItemNode.interaction?.openStarsTopup(amount.value) + case .setupPhoto: + self.effectiveContainerNode.currentItemNode.interaction?.openPhotoSetup() + case .accountFreeze: + self.effectiveContainerNode.currentItemNode.interaction?.openAccountFreezeInfo() + case let .link(_, url, _, _): + self.effectiveContainerNode.currentItemNode.interaction?.openUrl(url) + } + }, + dismissAction: { [weak self] notice in + guard let self, let controller = self.controller else { + return + } + controller.globalControlPanelsContext.dismissChatListNotice(parentController: controller, notice: notice) + }, + selectAction: { [weak self] notice, isPositive in + guard let self else { + return + } + switch notice { + case let .reviewLogin(newSessionReview, _): + self.effectiveContainerNode.currentItemNode.interaction?.performActiveSessionAction(newSessionReview, isPositive) + default: + break + } + } + ))) + ) + } + if let mediaPlayback = self.controller?.globalControlPanelsContextState?.mediaPlayback { + panels.append(HeaderPanelContainerComponent.Panel( + key: "media", + orderIndex: 1, + component: AnyComponent(MediaPlaybackHeaderPanelComponent( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + data: mediaPlayback, + controller: { [weak self] in + return self?.controller + } + ))) + ) + } + if let liveLocation = self.controller?.globalControlPanelsContextState?.liveLocation { + panels.append(HeaderPanelContainerComponent.Panel( + key: "liveLocation", + orderIndex: 2, + component: AnyComponent(LiveLocationHeaderPanelComponent( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + data: liveLocation, + controller: { [weak self] in + return self?.controller + } + ))) + ) + } - if let value = self.controller?.searchTabsNode { - tabsNode = value - tabsNodeIsSearch = true - } else if let value = self.controller?.tabsNode, self.controller?.hasTabs == true { - tabsNode = value + var navigationHeaderPanels: AnyComponent? + if self.controller?.tabContainerData != nil || !panels.isEmpty { + var tabs: AnyComponent? + if let tabContainerData = self.controller?.tabContainerData, tabContainerData.0.count > 1 { + let selectedTab: HorizontalTabsComponent.Tab.Id + switch self.effectiveContainerNode.currentItemFilter { + case .all: + selectedTab = AnyHashable(Int32.min) + case let .filter(id): + selectedTab = AnyHashable(id) + } + + let isEditing = self.isReorderingFilters || (self.mainContainerNode.currentItemNode.currentState.editing && !self.didBeginSelectingChatsWhileEditing) + + tabs = AnyComponent(HorizontalTabsComponent( + context: self.context, + theme: self.presentationData.theme, + tabs: tabContainerData.0.map { entry -> HorizontalTabsComponent.Tab in + let id: HorizontalTabsComponent.Tab.Id + let title: HorizontalTabsComponent.Tab.Title + var badge: HorizontalTabsComponent.Tab.Badge? + var isMainTab = false + switch entry { + case .all: + id = Int32.min + title = HorizontalTabsComponent.Tab.Title(text: self.presentationData.strings.ChatList_Tabs_All, entities: [], enableAnimations: false) + isMainTab = true + case let .filter(idValue, text, unread): + id = AnyHashable(idValue) + title = HorizontalTabsComponent.Tab.Title(text: text.text, entities: text.entities, enableAnimations: text.enableAnimations) + if unread.value != 0 { + badge = HorizontalTabsComponent.Tab.Badge( + title: "\(unread.value)", + isAccent: unread.hasUnmuted + ) + } + } + + return HorizontalTabsComponent.Tab( + id: id, + content: .title(title), + badge: badge, + action: { [weak self] in + guard let self, let tabContainerData = self.controller?.tabContainerData else { + return + } + + let isPremium = self.context.isPremium + + let mappedId: ChatListFilterTabEntryId = entry.id + + var isDisabled = false + if let filtersLimit = tabContainerData.2 { + guard let folderIndex = tabContainerData.0.firstIndex(where: { $0.id == mappedId }) else { + return + } + isDisabled = !isPremium && folderIndex >= filtersLimit + } + + if isDisabled { + let filtersCount = tabContainerData.0.count(where: { item in + if case .all = item { + return false + } else { + return true + } + }) + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(filtersCount), action: { + let controller = PremiumIntroScreen(context: context, source: .folders) + replaceImpl?(controller) + return true + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.controller?.push(controller) + } else { + self.controller?.selectTab(id: mappedId) + } + }, + contextAction: { [weak self] sourceView, gesture in + guard let self, let tabContainerData = self.controller?.tabContainerData else { + return + } + + let isPremium = self.context.isPremium + + let mappedId: Int32? + switch entry { + case .all: + mappedId = nil + case let .filter(idValue, _, _): + mappedId = idValue + } + + var isDisabled = false + if let filtersLimit = tabContainerData.2 { + guard let folderIndex = tabContainerData.0.firstIndex(where: { $0.id == entry.id }) else { + return + } + isDisabled = !isPremium && folderIndex >= filtersLimit + } + + self.controller?.tabContextGesture(id: mappedId, sourceNode: nil, sourceView: sourceView, gesture: gesture, keepInPlace: false, isDisabled: isDisabled) + }, + deleteAction: (!isEditing || isMainTab) ? nil : { [weak self] in + guard let self else { + return + } + if case let .filter(id) = entry.id { + self.controller?.askForFilterRemoval(id: id) + } + } + ) + }, + selectedTab: selectedTab, + isEditing: isEditing, + liftWhileSwitching: layout.deviceMetrics.type != .tablet + )) + } + + navigationHeaderPanels = AnyComponent(HeaderPanelContainerComponent( + theme: self.presentationData.theme, + tabs: tabs, + panels: panels + )) } var effectiveStorySubscriptions: EngineStorySubscriptions? @@ -1382,16 +1605,17 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { strings: self.presentationData.strings, statusBarHeight: layout.statusBarHeight ?? 0.0, sideInset: layout.safeInsets.left, - isSearchActive: self.isSearchDisplayControllerActive, - isSearchEnabled: true, + search: ChatListNavigationBar.Search(isEnabled: true), + activeSearch: self.isSearchDisplayControllerActive, primaryContent: headerContent?.primaryContent, secondaryContent: headerContent?.secondaryContent, secondaryTransition: self.inlineStackContainerTransitionFraction, storySubscriptions: effectiveStorySubscriptions, storiesIncludeHidden: self.location == .chatList(groupId: .archive), uploadProgress: self.controller?.storyUploadProgress ?? [:], - tabsNode: tabsNode, - tabsNodeIsSearch: tabsNodeIsSearch, + headerPanels: navigationHeaderPanels, + tabsNode: nil, + tabsNodeIsSearch: false, accessoryPanelContainer: self.controller?.accessoryPanelContainer, accessoryPanelContainerHeight: self.controller?.accessoryPanelContainerHeight ?? 0.0, activateSearch: { [weak self] searchContentNode in @@ -1479,7 +1703,7 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { } var offset = resultingOffset - if self.isSearchDisplayControllerActive { + if self.isSearchDisplayControllerActive != nil { offset = 0.0 } @@ -1656,6 +1880,9 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: transition) } } + if let disappearingSearchDisplayController = self.disappearingSearchDisplayController { + disappearingSearchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: transition) + } self.updateNavigationScrolling(navigationHeight: navigationBarLayout.navigationHeight, transition: transition) @@ -1666,7 +1893,7 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { } @MainActor - func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) async -> (ASDisplayNode, (Bool) -> Void)? { + func activateSearch(placeholderNode: SearchBarPlaceholderNode?, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?, searchBarIsExternal: Bool) async -> ((Bool) -> Void)? { guard let (containerLayout, _, _, cleanNavigationBarHeight, _) = self.containerLayout, self.searchDisplayController == nil else { return nil } @@ -1712,16 +1939,16 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } - }) + }, fieldStyle: placeholderNode?.fieldStyle ?? .modern, searchBarIsExternal: searchBarIsExternal) self.mainContainerNode.accessibilityElementsHidden = true self.inlineStackContainerNode?.accessibilityElementsHidden = true - return (contentNode.filterContainerNode, { [weak self] focus in + return ({ [weak self] focus in guard let strongSelf = self else { return } - strongSelf.isSearchDisplayControllerActive = true + strongSelf.isSearchDisplayControllerActive = ChatListNavigationBar.ActiveSearch(isExternal: placeholderNode == nil) strongSelf.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate) strongSelf.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in @@ -1731,7 +1958,7 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { if isSearchBar { if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View { - navigationBarComponentView.addSubnode(subnode) + navigationBarComponentView.searchContentNode?.addSubnode(subnode) } } else { self.insertSubnode(subnode, aboveSubnode: self.debugListView) @@ -1742,21 +1969,31 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { }) } - func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) -> (() -> Void)? { + func deactivateSearch(placeholderNode: SearchBarPlaceholderNode?, animated: Bool) -> (() -> Void)? { if let searchDisplayController = self.searchDisplayController { - self.isSearchDisplayControllerActive = false + self.isSearchDisplayControllerActive = nil self.searchDisplayController = nil + self.disappearingSearchDisplayController = searchDisplayController self.mainContainerNode.accessibilityElementsHidden = false self.inlineStackContainerNode?.accessibilityElementsHidden = false return { [weak self, weak placeholderNode] in - if let strongSelf = self, let placeholderNode, let (layout, _, _, cleanNavigationBarHeight, _) = strongSelf.containerLayout { - searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated) - - searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) - - strongSelf.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + guard let self, let (layout, _, _, cleanNavigationBarHeight, _) = self.containerLayout else { + return } + let placeholderNode = placeholderNode + searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated, completion: { [weak self, weak searchDisplayController] in + guard let self, let searchDisplayController else { + return + } + if self.disappearingSearchDisplayController === searchDisplayController { + self.disappearingSearchDisplayController = nil + } + }) + + searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) + + self.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } } else { return nil diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift index 24e3612d..6b75d9a1 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift @@ -148,7 +148,7 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 30ffe647..da255877 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -1283,7 +1283,6 @@ private final class ChatListFilterPresetController: ItemListController { pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, @@ -1753,14 +1752,14 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi if initialPreset == nil { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let text = presentationData.strings.ChatListFilter_AlertCreateFolderBeforeSharingText - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) } else { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let state = stateValue.with({ $0 }) if state.additionallyIncludePeers.isEmpty { let text = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) return } @@ -1785,7 +1784,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi statusController?.dismiss() let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: unavailableText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + presentControllerImpl?(textAlertController(context: context, title: nil, text: unavailableText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) return } @@ -2360,7 +2359,7 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec case .someUserTooManyChannels: text = presentationData.strings.ChatListFilter_CreateLinkErrorSomeoneHasChannelLimit } - presentController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])) + presentController(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])) }) } }) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 56c5c9ba..8750c6f8 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -5,6 +5,7 @@ import SwiftSignalKit import TelegramCore import TelegramPresentationData import TelegramUIPreferences +import PresentationDataUtils import ItemListUI import AccountContext import ItemListPeerActionItem @@ -516,13 +517,13 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch } if hasLinks { - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatList_AlertDeleteFolderTitle, text: presentationData.strings.ChatList_AlertDeleteFolderText, actions: [ + presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.ChatList_AlertDeleteFolderTitle, text: presentationData.strings.ChatList_AlertDeleteFolderText, actions: [ TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: { confirmDeleteFolder() }), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: { + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { }) - ])) + ], actionLayout: .vertical)) } else { confirmDeleteFolder() } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift index 55540442..84e53950 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift @@ -201,7 +201,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.titleNode.textNode) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift index 34f96f72..4cce378d 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift @@ -132,7 +132,7 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.labelNode) diff --git a/submodules/ChatListUI/Sources/ChatListFilterTagSectionHeaderItem.swift b/submodules/ChatListUI/Sources/ChatListFilterTagSectionHeaderItem.swift index 93438c9a..3c7fd1fc 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterTagSectionHeaderItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterTagSectionHeaderItem.swift @@ -113,7 +113,7 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() self.activateArea.accessibilityTraits = [.staticText, .header] - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.accessoryTextNode) diff --git a/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift b/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift index 52541007..0cac0a1b 100644 --- a/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift @@ -79,7 +79,7 @@ class ChatListRecentPeersListItemNode: ListViewItemNode { self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.separatorNode) diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index aab2bb49..11af4ee9 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -36,6 +36,10 @@ import MultiAnimationRenderer import PremiumUI import AvatarNode import StoryContainerScreen +import ChatListSearchFiltersContainerNode +import EdgeEffect +import ComponentFlow +import ComponentDisplayAdapters private enum ChatListTokenId: Int32 { case archive @@ -107,8 +111,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo var dismissSearch: (() -> Void)? var openAdInfo: ((ASDisplayNode, AdPeer) -> Void)? - private let dimNode: ASDisplayNode - let filterContainerNode: ChatListSearchFiltersContainerNode + private let edgeEffectView: EdgeEffectView + + private let filterContainerNode: ChatListSearchFiltersContainerNode private let paneContainerNode: ChatListSearchPaneContainerNode private var selectionPanelNode: ChatListSearchMessageSelectionPanelNode? @@ -181,9 +186,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.openMessage = originalOpenMessage self.present = present self.presentInGlobalOverlay = presentInGlobalOverlay - - self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) + + self.edgeEffectView = EdgeEffectView() self.filterContainerNode = ChatListSearchFiltersContainerNode() self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, peersFilter: self.peersFilter, requestPeerType: self.requestPeerType, location: location, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController, parentController: parentController()) @@ -193,7 +197,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.backgroundColor = filter.contains(.excludeRecent) ? nil : self.presentationData.theme.chatList.backgroundColor -// self.addSubnode(self.dimNode) self.addSubnode(self.paneContainerNode) let interaction = ChatListSearchInteraction(openPeer: { peer, chatPeer, threadId, value in @@ -325,6 +328,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo parentController()?.view.endEditing(true) } + self.view.addSubview(self.edgeEffectView) + + self.addSubnode(self.filterContainerNode) self.filterContainerNode.filterPressed = { [weak self] filter in guard let strongSelf = self else { return @@ -553,9 +559,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo public override func didLoad() { super.didLoad() - - - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) } public override var hasDim: Bool { @@ -705,18 +708,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.transitionFraction = transitionFraction if let (layout, _) = self.validLayout { - let filters: [ChatListSearchFilter] - if let suggestedFilters = self.suggestedFilters, !suggestedFilters.isEmpty { - filters = suggestedFilters - } else { - var isForum = false - if case .forum = self.location { - isForum = true - } - - filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: !isForum && self.hasDownloads, hasPublicPosts: self.showPublicPostsTab).map(\.filter) - } - self.filterContainerNode.update(size: CGSize(width: layout.size.width - 40.0, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, displayGlobalPostsNewBadge: self.displayGlobalPostsNewBadge, selectedFilter: self.selectedFilter?.id, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: transition) + self.updateFilterContainerNode(layout: layout, transition: transition) } } @@ -762,18 +754,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.cancel?() } } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) - - let isFirstTime = self.validLayout == nil - self.validLayout = (layout, navigationBarHeight) - - let topInset = navigationBarHeight - - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))) - transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 6.0), size: CGSize(width: layout.size.width, height: 38.0))) - + + private func updateFilterContainerNode(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { var isForum = false if case .forum = self.location { isForum = true @@ -786,8 +768,46 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: self.hasDownloads, hasPublicPosts: self.showPublicPostsTab).map(\.filter) } - let overflowInset: CGFloat = 20.0 - self.filterContainerNode.update(size: CGSize(width: layout.size.width - overflowInset * 2.0, height: 38.0), sideInset: layout.safeInsets.left - overflowInset, filters: filters.map { .filter($0) }, displayGlobalPostsNewBadge: self.displayGlobalPostsNewBadge, selectedFilter: self.selectedFilter?.id, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + var filtersInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: layout.insets(options: [.input]).bottom + 34.0, right: 12.0) + if layout.insets(options: [.input]).bottom <= 30.0 { + filtersInsets = ContainerViewLayout.concentricInsets(bottomInset: layout.insets(options: [.input]).bottom, innerDiameter: 40.0, sideInset: 32.0) + } else if layout.insets(options: [.input]).bottom <= 84.0 { + filtersInsets.left = 20.0 + filtersInsets.right = filtersInsets.left + } + + self.filterContainerNode.update(size: CGSize(width: layout.size.width - (layout.safeInsets.left + filtersInsets.left) * 2.0, height: 40.0), sideInset: 0.0, filters: filters.map { .filter($0) }, displayGlobalPostsNewBadge: self.displayGlobalPostsNewBadge, selectedFilter: self.selectedFilter?.id, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) + + let isFirstTime = self.validLayout == nil + self.validLayout = (layout, navigationBarHeight) + + let topInset = navigationBarHeight + + var filtersInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: layout.insets(options: [.input]).bottom, right: 12.0) + if filtersInsets.bottom == 84.0 { + filtersInsets.bottom -= 6.0 + } + if layout.insets(options: [.input]).bottom <= 30.0 { + filtersInsets = ContainerViewLayout.concentricInsets(bottomInset: layout.insets(options: [.input]).bottom, innerDiameter: 40.0, sideInset: 32.0) + } else if layout.insets(options: [.input]).bottom <= 84.0 { + filtersInsets.left = 20.0 + filtersInsets.right = filtersInsets.left + } else { + if let inputHeight = layout.inputHeight, filtersInsets.bottom == inputHeight { + filtersInsets.bottom += 8.0 + } + filtersInsets.bottom = max(8.0, filtersInsets.bottom) + } + if self.stateValue.selectedMessageIds != nil { + filtersInsets.bottom += 48.0 + } + + transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + filtersInsets.left, y: layout.size.height - filtersInsets.bottom - 40.0), size: CGSize(width: layout.size.width - (layout.safeInsets.left + filtersInsets.left) * 2.0, height: 40.0))) + self.updateFilterContainerNode(layout: layout, transition: transition) if isFirstTime { self.filterContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -795,13 +815,13 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } var bottomIntrinsicInset = layout.intrinsicInsets.bottom - if case .chatList(.root) = self.location { - if layout.safeInsets.left > overflowInset { + /*if case .chatList(.root) = self.location { + if layout.safeInsets.left > 20.0 { bottomIntrinsicInset -= 34.0 } else { bottomIntrinsicInset -= 49.0 } - } + }*/ if let selectedMessageIds = self.stateValue.selectedMessageIds { var wasAdded = false @@ -927,7 +947,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo return strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: messageIds, messages: messages, peers: peers) } self.selectionPanelNode = selectionPanelNode - self.addSubnode(selectionPanelNode) + self.insertSubnode(selectionPanelNode, aboveSubnode: self.filterContainerNode) } selectionPanelNode.selectedMessages = selectedMessageIds @@ -948,25 +968,36 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }) } - transition.updateFrame(node: self.paneContainerNode, frame: CGRect(x: 0.0, y: topInset, width: layout.size.width, height: layout.size.height - topInset)) + transition.updateFrame(node: self.paneContainerNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)) var bottomInset = layout.intrinsicInsets.bottom if let inputHeight = layout.inputHeight { bottomInset = inputHeight } else if let _ = self.selectionPanelNode { bottomInset = bottomIntrinsicInset - } else if case .chatList(.root) = self.location { - bottomInset -= bottomIntrinsicInset } + bottomInset += 10.0 let availablePanes: [ChatListSearchPaneKey] + var isForum = false + if case .forum = self.location { + isForum = true + } if self.displaySearchFilters { availablePanes = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: self.hasDownloads, hasPublicPosts: self.hasPublicPostsTab) } else { availablePanes = isForum ? [.topics] : [.chats] } + + bottomInset += 44.0 + + let edgeEffectHeight: CGFloat = bottomInset + 8.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) + transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, 50.0), transition: ComponentTransition(transition)) + transition.updateAlpha(layer: self.edgeEffectView.layer, alpha: edgeEffectHeight > 21.0 ? 1.0 : 0.0) - self.paneContainerNode.update(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), sideInset: layout.safeInsets.left, bottomInset: bottomInset, visibleHeight: layout.size.height - topInset, presentationData: self.presentationData, availablePanes: availablePanes, transition: transition) + self.paneContainerNode.update(size: CGSize(width: layout.size.width, height: layout.size.height), sideInset: layout.safeInsets.left, topInset: topInset, bottomInset: bottomInset, visibleHeight: layout.size.height, presentationData: self.presentationData, availablePanes: availablePanes, transition: transition) } private var currentMessages: ([EnginePeer.Id: EnginePeer], [EngineMessage.Id: EngineMessage]) { @@ -1325,7 +1356,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo title = strongSelf.presentationData.strings.DownloadList_RemoveFileAlertTitle(Int32(messages.count)) text = strongSelf.presentationData.strings.DownloadList_RemoveFileAlertText(Int32(messages.count)) - strongSelf.present?(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [ + strongSelf.present?(textAlertController(context: strongSelf.context, title: title, text: text, actions: [ TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.DownloadList_RemoveFileAlertRemove, action: { diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index c3854ee8..88a8cdf0 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -38,6 +38,7 @@ import MultilineTextComponent import ButtonComponent import BundleIconComponent import AnimatedTextComponent +import TextFormat private enum ChatListRecentEntryStableId: Hashable { case topPeers @@ -1696,7 +1697,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { private var emptyRecentAnimationNode: AnimatedStickerNode? private var emptyRecentAnimationSize = CGSize() - private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -3495,7 +3496,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { self.interaction.openStories?(id, sourceNode.avatarNode) } }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { @@ -4508,7 +4508,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { return } dismissImpl?() - if let value = attributes[NSAttributedString.Key(rawValue: "URL")] as? String { + if let value = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { if !value.isEmpty { context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: value, forceExternal: false, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: navigationController, dismissInput: {}) } else { @@ -4536,7 +4536,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { ) interaction.present(alertController, nil) dismissImpl = { [weak alertController] in - alertController?.dismissAnimated() + alertController?.dismiss() } }, isChannelsTabExpanded: recentItems.isChannelsTabExpanded, @@ -4640,8 +4640,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { self.playlistStateAndType = nil } - if let (size, sideInset, bottomInset, visibleHeight, presentationData) = self.currentParams { - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring)) + if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData) = self.currentParams { + self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring)) } } self.playlistLocation = playlistStateAndType?.1.playlistLocation @@ -4758,10 +4758,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } - func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { let hadValidLayout = self.currentParams != nil - let layoutChanged = self.currentParams?.size != size || self.currentParams?.sideInset != sideInset || self.currentParams?.bottomInset != bottomInset || self.currentParams?.visibleHeight != visibleHeight - self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData) + let layoutChanged = self.currentParams?.size != size || self.currentParams?.sideInset != sideInset || self.currentParams?.topInset != topInset || self.currentParams?.bottomInset != bottomInset || self.currentParams?.visibleHeight != visibleHeight + self.currentParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData) var topPanelHeight: CGFloat = 0.0 if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType { @@ -5035,9 +5035,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { transition.updateFrame(node: self.mediaAccessoryPanelContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: MediaNavigationAccessoryHeaderNode.minimizedHeight))) - let topInset: CGFloat = topPanelHeight - let overflowInset: CGFloat = 20.0 - let insets = UIEdgeInsets(top: topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset) + let topInset: CGFloat = topInset + topPanelHeight + let overflowInset: CGFloat = 0.0 + let insets = UIEdgeInsets(top: topInset + topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset) self.shimmerNode.frame = CGRect(origin: CGPoint(x: overflowInset, y: topInset), size: CGSize(width: size.width - overflowInset * 2.0, height: size.height)) self.shimmerNode.update(context: self.context, size: CGSize(width: size.width - overflowInset * 2.0, height: size.height), presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, key: !(self.searchQueryValue?.isEmpty ?? true) && self.key == .media ? .chats : self.key, hasSelection: self.selectedMessages != nil, transition: transition) @@ -5480,8 +5480,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { strongSelf.emptyResultsButtonSubtitleText = nil } - if let (size, sideInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring)) + if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring)) } if strongSelf.key == .downloads { @@ -5783,7 +5783,6 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { diff --git a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift index 7d52bddb..054acc95 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift @@ -15,7 +15,7 @@ protocol ChatListSearchPaneNode: ASDisplayNode { var isReady: Signal { get } var isCurrent: Bool { get set } - func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) + func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) func scrollToTop() -> Bool func cancelPreviewGestures() func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? @@ -32,21 +32,21 @@ final class ChatListSearchPaneWrapper { let key: ChatListSearchPaneKey let node: ChatListSearchPaneNode var isAnimatingOut: Bool = false - private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, PresentationData)? + private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, CGFloat, PresentationData)? init(key: ChatListSearchPaneKey, node: ChatListSearchPaneNode) { self.key = key self.node = node } - func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - if let (currentSize, currentSideInset, currentBottomInset, _, currentPresentationData) = self.appliedParams { - if currentSize == size && currentSideInset == sideInset && currentBottomInset == bottomInset && currentPresentationData === presentationData { + func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + if let (currentSize, currentSideInset, currentTopInset, currentBottomInset, _, currentPresentationData) = self.appliedParams { + if currentSize == size && currentSideInset == sideInset && currentTopInset == topInset && currentBottomInset == bottomInset && currentPresentationData === presentationData { return } } - self.appliedParams = (size, sideInset, bottomInset, visibleHeight, presentationData) - self.node.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: synchronous, transition: transition) + self.appliedParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData) + self.node.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: synchronous, transition: transition) } } @@ -190,7 +190,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD var isAdjacentLoadingEnabled = false - private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, [ChatListSearchPaneKey])? + private var currentParams: (size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, [ChatListSearchPaneKey])? private(set) var currentPaneKey: ChatListSearchPaneKey? var pendingSwitchToPaneKey: ChatListSearchPaneKey? @@ -251,8 +251,8 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD if self.currentPanes[key] != nil { self.currentPaneKey = key - if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams { - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring)) + if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams { + self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring)) } if case .apps = key { @@ -261,8 +261,8 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD } else if self.pendingSwitchToPaneKey != key { self.pendingSwitchToPaneKey = key - if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams { - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring)) + if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams { + self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.4, curve: .spring)) } if case .apps = key { @@ -275,7 +275,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD super.didLoad() let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in - guard let strongSelf = self, let (_, _, _, _, _, availablePanes) = strongSelf.currentParams, let currentPaneKey = strongSelf.currentPaneKey, let index = availablePanes.firstIndex(of: currentPaneKey) else { + guard let strongSelf = self, let (_, _, _, _, _, _, availablePanes) = strongSelf.currentParams, let currentPaneKey = strongSelf.currentPaneKey, let index = availablePanes.firstIndex(of: currentPaneKey) else { return [] } if index == 0 { @@ -321,7 +321,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD cancelContextGestures(view: self.view) case .changed: - if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { + if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { self.isAdjacentLoadingEnabled = true let translation = recognizer.translation(in: self.view) var transitionFraction = translation.x / size.width @@ -332,10 +332,10 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD transitionFraction = max(0.0, transitionFraction) } self.transitionFraction = transitionFraction - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .immediate) + self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .immediate) } case .cancelled, .ended: - if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { + if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = self.currentParams, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { let translation = recognizer.translation(in: self.view) let velocity = recognizer.velocity(in: self.view) var directionIsToRight: Bool? @@ -364,7 +364,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD } } self.transitionFraction = 0.0 - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.35, curve: .spring)) + self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: .animated(duration: 0.35, curve: .spring)) } default: break @@ -396,7 +396,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD } } - func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, availablePanes: [ChatListSearchPaneKey], transition: ContainedViewLayoutTransition) { + func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, availablePanes: [ChatListSearchPaneKey], transition: ContainedViewLayoutTransition) { let previousAvailablePanes = self.currentAvailablePanes ?? [] self.currentAvailablePanes = availablePanes @@ -430,7 +430,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD currentIndex = nil } - self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) + self.currentParams = (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) switch self.location { case .forum, .savedMessagesChats: @@ -489,12 +489,12 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD guard let strongSelf = self else { return } - if let (size, sideInset, bottomInset, visibleHeight, presentationData, availablePanes) = strongSelf.currentParams { + if let (size, sideInset, topInset, bottomInset, visibleHeight, presentationData, availablePanes) = strongSelf.currentParams { var transition: ContainedViewLayoutTransition = .immediate if strongSelf.pendingSwitchToPaneKey == key && strongSelf.currentPaneKey != nil { transition = .animated(duration: 0.4, curve: .spring) } - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: transition) + strongSelf.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, availablePanes: availablePanes, transition: transition) } } if leftScope { @@ -504,14 +504,14 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD ) self.pendingPanes[key] = pane pane.pane.node.frame = paneFrame - pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .immediate) + pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: .immediate) leftScope = true } } for (key, pane) in self.pendingPanes { pane.pane.node.frame = paneFrame - pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate) + pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate) if pane.isReady { self.pendingPanes.removeValue(forKey: key) @@ -587,7 +587,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD paneCompletion() }) } - pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition) + pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition) pane.node.isCurrent = key == self.currentPaneKey if paneWasAdded && key == self.currentPaneKey { pane.node.didBecomeFocused() @@ -598,7 +598,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD for (_, pane) in self.pendingPanes { let paneTransition: ContainedViewLayoutTransition = .immediate paneTransition.updateFrame(node: pane.pane.node, frame: paneFrame) - pane.pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: paneTransition) + pane.pane.update(size: paneFrame.size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: true, transition: paneTransition) } if !self.didSetIsReady { if let currentPaneKey = self.currentPaneKey, let currentPane = self.currentPanes[currentPaneKey] { diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift index 2cf134e2..6ca1a33b 100644 --- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift @@ -157,7 +157,6 @@ public final class ChatListShimmerNode: ASDisplayNode { }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { diff --git a/submodules/ChatListUI/Sources/ItemListFilterTitleInputItem.swift b/submodules/ChatListUI/Sources/ItemListFilterTitleInputItem.swift index 127a4ae5..2f5e5b51 100644 --- a/submodules/ChatListUI/Sources/ItemListFilterTitleInputItem.swift +++ b/submodules/ChatListUI/Sources/ItemListFilterTitleInputItem.swift @@ -126,7 +126,7 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele self.maskNode = ASImageNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) } override public func didLoad() { diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift index 4dda5fe5..411cd295 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift @@ -172,7 +172,7 @@ class ChatListArchiveInfoItemNode: ListViewItemNode, ASScrollViewDelegate { self.infoPageNodes = (0 ..< 3).map({ _ in InfoPageNode() }) self.pageControlNode.pagesCount = self.infoPageNodes.count - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.scrollNode) self.infoPageNodes.forEach(self.scrollNode.addSubnode) diff --git a/submodules/ChatListUI/Sources/Node/ChatListEmptyHeaderItem.swift b/submodules/ChatListUI/Sources/Node/ChatListEmptyHeaderItem.swift index 9edf523d..a2b5e4ee 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListEmptyHeaderItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListEmptyHeaderItem.swift @@ -55,7 +55,7 @@ class ChatListEmptyHeaderItemNode: ListViewItemNode { private var item: ChatListEmptyHeaderItem? required init() { - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) } override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { diff --git a/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift index 21363bb4..06615f98 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift @@ -92,7 +92,7 @@ class ChatListEmptyInfoItemNode: ListViewItemNode { self.animationNode = DefaultAnimatedStickerNodeImpl() self.textNode = TextNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.animationNode) self.addSubnode(self.textNode) @@ -207,7 +207,7 @@ class ChatListSectionHeaderNode: ListViewItemNode { private var headerNode: ListSectionHeaderNode? required init() { - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.zPosition = 1.0 } diff --git a/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift b/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift index 1716677a..324f4245 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift @@ -56,7 +56,7 @@ class ChatListHoleItemNode: ListViewItemNode { var relativePosition: (first: Bool, last: Bool) = (false, false) required init() { - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) } override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { @@ -153,7 +153,7 @@ class ChatListSearchEmptyFooterItemNode: ListViewItemNode { self.searchAllMessagesTitle = TextNode() self.searchAllMessagesTitle.isUserInteractionEnabled = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.contentNode) self.contentNode.addSubnode(self.titleNode) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 46974fd9..35b8650c 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -15,7 +15,6 @@ import PeerOnlineMarkerNode import LocalizedPeerData import PeerPresenceStatusManager import PhotoResources -import ChatListSearchItemNode import ContextUI import ChatInterfaceState import TextFormat @@ -219,6 +218,7 @@ public enum ChatListItemContent { public var message: EngineMessage? public var unreadCount: Int public var hiddenByDefault: Bool + public var appearsPinned: Bool public var storyState: StoryState? public init( @@ -227,6 +227,7 @@ public enum ChatListItemContent { message: EngineMessage?, unreadCount: Int, hiddenByDefault: Bool, + appearsPinned: Bool, storyState: StoryState? ) { self.groupId = groupId @@ -234,6 +235,7 @@ public enum ChatListItemContent { self.message = message self.unreadCount = unreadCount self.hiddenByDefault = hiddenByDefault + self.appearsPinned = appearsPinned self.storyState = storyState } } @@ -454,7 +456,7 @@ private final class ChatListItemTagListComponent: Component { } } -public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { +public class ChatListItem: ListViewItem { public enum EnabledContextActions { public struct Actions: OptionSet { public var rawValue: Int32 @@ -1472,7 +1474,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } else { result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage } - let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) + let (_, initialHideAuthor, messageText, _, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author { result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)" } @@ -1506,7 +1508,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } else { result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage } - let (_, initialHideAuthor, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: peerData.messages, chatPeer: peerData.peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) + let (_, initialHideAuthor, messageText, _, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: peerData.messages, chatPeer: peerData.peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author { result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)" } @@ -1656,7 +1658,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true @@ -2452,7 +2454,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let leftInset: CGFloat = params.leftInset + avatarLeftInset enum ContentData { - case chat(itemPeer: EngineRenderedPeer, threadInfo: ChatListItemContent.ThreadInfo?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) + case chat(itemPeer: EngineRenderedPeer, threadInfo: ChatListItemContent.ThreadInfo?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, messageEntities: [MessageTextEntity], spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) case group(peers: [EngineChatList.GroupItem.Item]) } @@ -2461,7 +2463,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var hideAuthor = false switch contentPeer { case let .chat(itemPeer): - var (peer, initialHideAuthor, messageText, spoilers, customEmojiRanges) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup) + var (peer, initialHideAuthor, messageText, messageEntities, spoilers, customEmojiRanges) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup) if case let .psa(_, maybePsaText) = promoInfo, let psaText = maybePsaText { initialHideAuthor = true @@ -2489,7 +2491,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { break } - contentData = .chat(itemPeer: itemPeer, threadInfo: threadInfo, peer: peer, hideAuthor: hideAuthor, messageText: messageText, spoilers: spoilers, customEmojiRanges: customEmojiRanges) + contentData = .chat(itemPeer: itemPeer, threadInfo: threadInfo, peer: peer, hideAuthor: hideAuthor, messageText: messageText, messageEntities: messageEntities, spoilers: spoilers, customEmojiRanges: customEmojiRanges) hideAuthor = initialHideAuthor case let .group(groupPeers): contentData = .group(peers: groupPeers) @@ -2508,7 +2510,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { forumTopicData = nil topForumTopicItems = [] - if case let .chat(itemPeer, _, _, _, _, _, _) = contentData { + if case let .chat(itemPeer, _, _, _, _, _, _, _) = contentData { if let messagePeer = itemPeer.chatMainPeer { switch messagePeer { case let .channel(channel): @@ -2556,7 +2558,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var ignoreForwardedIcon = false switch contentData { - case let .chat(itemPeer, _, _, _, text, spoilers, customEmojiRanges): + case let .chat(itemPeer, _, _, _, text, entities, spoilers, customEmojiRanges): var isUser = false if case .user = itemPeer.chatMainPeer { isUser = true @@ -2639,7 +2641,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } chatListText = (text, messageText) } - + if inlineAuthorPrefix == nil, let mediaDraftContentType { hasDraft = true authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor) @@ -2671,8 +2673,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { if let peerText = peerText { authorAttributedString = NSAttributedString(string: peerText, font: textFont, textColor: theme.authorNameColor) } - - var entities = (message._asMessage().textEntitiesAttribute?.entities ?? []).filter { entity in + + var entities = entities.filter { entity in switch entity.type { case .Spoiler, .CustomEmoji: return true @@ -2690,14 +2692,14 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } else { regex = loginCodeRegex } - if let cached = currentCustomTextEntities, cached.matches(text: message.text) { + if let cached = currentCustomTextEntities, cached.matches(text: messageText) { customTextEntities = cached - } else if let matches = regex?.matches(in: message.text, options: [], range: NSMakeRange(0, (message.text as NSString).length)) { + } else if let matches = regex?.matches(in: messageText, options: [], range: NSMakeRange(0, (messageText as NSString).length)) { var entities: [MessageTextEntity] = [] if let first = matches.first { entities.append(MessageTextEntity(range: first.range.location ..< first.range.location + first.range.length, type: .Spoiler)) } - customTextEntities = CachedCustomTextEntities(text: message.text, textEntities: entities) + customTextEntities = CachedCustomTextEntities(text: messageText, textEntities: entities) } } @@ -2706,14 +2708,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } let messageString: NSAttributedString - if !message.text.isEmpty && entities.count > 0 { - var messageText = message.text - var entities = entities - if !"".isEmpty, let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, !translation.text.isEmpty { - messageText = translation.text - entities = translation.entities - } - + if !messageText.isEmpty && entities.count > 0 { messageString = foldLineBreaks(stringWithAppliedEntities(messageText, entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: italicTextFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())) } else if spoilers != nil || customEmojiRanges != nil { let mutableString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor) @@ -3100,7 +3095,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } switch contentData { - case let .chat(itemPeer, threadInfo, _, _, _, _, _): + case let .chat(itemPeer, threadInfo, _, _, _, _, _, _): if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData { if customMessageListData.commandPrefix != nil { titleAttributedString = nil @@ -3854,6 +3849,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { transition = .immediate } + transition.updateAlpha(node: strongSelf, alpha: item.hiddenOffset ? 0.0 : 1.0) + ComponentTransition(transition).setBlur(layer: strongSelf.layer, radius: item.hiddenOffset ? 8.0 : 0.0) + let contextContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: itemHeight)) // strongSelf.contextContainer.position = contextContainerFrame.center transition.updatePosition(node: strongSelf.contextContainer, position: contextContainerFrame.center) @@ -5031,7 +5029,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { if case let .groupReference(groupReferenceData) = item.content, groupReferenceData.hiddenByDefault { separatorInset = 0.0 } else if (!nextIsPinned && isPinned) || last { - separatorInset = 0.0 + separatorInset = 0.0 } else { separatorInset = editingOffset + leftInset + rawContentRect.origin.x } @@ -5057,8 +5055,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { highlightedBackgroundColor = theme.itemHighlightedBackgroundColor } else if isPinned { if case let .groupReference(groupReferenceData) = item.content, groupReferenceData.hiddenByDefault { - backgroundColor = theme.itemBackgroundColor - highlightedBackgroundColor = theme.itemHighlightedBackgroundColor + backgroundColor = groupReferenceData.appearsPinned ? theme.pinnedItemBackgroundColor : theme.itemBackgroundColor + highlightedBackgroundColor = groupReferenceData.appearsPinned ? theme.pinnedItemHighlightedBackgroundColor : theme.itemHighlightedBackgroundColor } else { backgroundColor = theme.pinnedItemBackgroundColor highlightedBackgroundColor = theme.pinnedItemHighlightedBackgroundColor diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index ba82ca6f..a356607d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -77,20 +77,21 @@ private func paidContentGroupType(paidContent: TelegramMediaPaidContent) -> Mess return currentType } -public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, contentSettings: ContentSettings, messages: [EngineMessage], chatPeer: EngineRenderedPeer, accountPeerId: EnginePeer.Id, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) { +public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, contentSettings: ContentSettings, messages: [EngineMessage], chatPeer: EngineRenderedPeer, accountPeerId: EnginePeer.Id, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: EnginePeer?, hideAuthor: Bool, messageText: String, messageEntities: [MessageTextEntity], spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) { let peer: EnginePeer? let message = messages.last if let restrictionReason = message?._asMessage().restrictionReason(platform: "ios", contentSettings: contentSettings) { - return (nil, false, restrictionReason, nil, nil) + return (nil, false, restrictionReason, [], nil, nil) } if let restrictionReason = chatPeer.chatMainPeer?.restrictionText(platform: "ios", contentSettings: contentSettings) { - return (nil, false, restrictionReason, nil, nil) + return (nil, false, restrictionReason, [], nil, nil) } var hideAuthor = false var messageText: String + var messageEntities: [MessageTextEntity] = [] var spoilers: [NSRange]? var customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]? if let message = message { @@ -104,6 +105,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: for message in messages { if !message.text.isEmpty { messageText = message.text + messageEntities = message._asMessage().textEntitiesAttribute?.entities ?? [] break } } @@ -469,5 +471,5 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: } } - return (peer, hideAuthor, messageText, spoilers, customEmojiRanges) + return (peer, hideAuthor, messageText, messageEntities, spoilers, customEmojiRanges) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index ad864f6b..b7df981a 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -23,6 +23,7 @@ import ChatListHeaderComponent import UndoUI import NewSessionInfoScreen import PresentationDataUtils +import GlobalControlPanelsContext public enum ChatListNodeMode { case chatList(appendContacts: Bool) @@ -110,7 +111,6 @@ public final class ChatListNodeInteraction { let hideChatFolderUpdates: () -> Void let openStories: (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void let openStarsTopup: (Int64?) -> Void - let dismissNotice: (ChatListNotice) -> Void let editPeer: (ChatListItem) -> Void let openWebApp: (TelegramUser) -> Void let openPhotoSetup: () -> Void @@ -171,7 +171,6 @@ public final class ChatListNodeInteraction { hideChatFolderUpdates: @escaping () -> Void, openStories: @escaping (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void, openStarsTopup: @escaping (Int64?) -> Void, - dismissNotice: @escaping (ChatListNotice) -> Void, editPeer: @escaping (ChatListItem) -> Void, openWebApp: @escaping (TelegramUser) -> Void, openPhotoSetup: @escaping () -> Void, @@ -219,7 +218,6 @@ public final class ChatListNodeInteraction { self.hideChatFolderUpdates = hideChatFolderUpdates self.openStories = openStories self.openStarsTopup = openStarsTopup - self.dismissNotice = dismissNotice self.editPeer = editPeer self.openWebApp = openWebApp self.openPhotoSetup = openPhotoSetup @@ -698,6 +696,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL message: groupReferenceEntry.message, unreadCount: groupReferenceEntry.unreadCount, hiddenByDefault: groupReferenceEntry.hiddenByDefault, + appearsPinned: groupReferenceEntry.appearsPinned, storyState: groupReferenceEntry.storyState.flatMap { storyState in return ChatListItemContent.StoryState( stats: storyState.stats, @@ -751,47 +750,6 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? { hideChatListContacts(context: context) } : nil), directionHint: entry.directionHint) - case let .Notice(presentationData, notice): - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in - switch action { - case .activate: - switch notice { - case .clearStorage: - nodeInteraction?.openStorageManagement() - case .setupPassword: - nodeInteraction?.openPasswordSetup() - case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore: - nodeInteraction?.openPremiumIntro() - case .xmasPremiumGift: - nodeInteraction?.openPremiumGift([], nil) - case .premiumGrace: - nodeInteraction?.openPremiumManagement() - case .setupBirthday: - nodeInteraction?.openBirthdaySetup() - case let .birthdayPremiumGift(peers, birthdays): - nodeInteraction?.openPremiumGift(peers, birthdays) - case .reviewLogin: - break - case let .starsSubscriptionLowBalance(amount, _): - nodeInteraction?.openStarsTopup(amount.value) - case .setupPhoto: - nodeInteraction?.openPhotoSetup() - case .accountFreeze: - nodeInteraction?.openAccountFreezeInfo() - case let .link(_, url, _, _): - nodeInteraction?.openUrl(url) - } - case .hide: - nodeInteraction?.dismissNotice(notice) - case let .buttonChoice(isPositive): - switch notice { - case let .reviewLogin(newSessionReview, _): - nodeInteraction?.performActiveSessionAction(newSessionReview, isPositive) - default: - break - } - } - }), directionHint: entry.directionHint) } } } @@ -1048,6 +1006,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL message: groupReferenceEntry.message, unreadCount: groupReferenceEntry.unreadCount, hiddenByDefault: groupReferenceEntry.hiddenByDefault, + appearsPinned: groupReferenceEntry.appearsPinned, storyState: groupReferenceEntry.storyState.flatMap { storyState in return ChatListItemContent.StoryState( stats: storyState.stats, @@ -1101,47 +1060,6 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? { hideChatListContacts(context: context) } : nil), directionHint: entry.directionHint) - case let .Notice(presentationData, notice): - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListNoticeItem(context: context, theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in - switch action { - case .activate: - switch notice { - case .clearStorage: - nodeInteraction?.openStorageManagement() - case .setupPassword: - nodeInteraction?.openPasswordSetup() - case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore: - nodeInteraction?.openPremiumIntro() - case .xmasPremiumGift: - nodeInteraction?.openPremiumGift([], nil) - case .premiumGrace: - nodeInteraction?.openPremiumManagement() - case .setupBirthday: - nodeInteraction?.openBirthdaySetup() - case let .birthdayPremiumGift(peers, birthdays): - nodeInteraction?.openPremiumGift(peers, birthdays) - case .reviewLogin: - break - case let .starsSubscriptionLowBalance(amount, _): - nodeInteraction?.openStarsTopup(amount.value) - case .setupPhoto: - nodeInteraction?.openPhotoSetup() - case .accountFreeze: - nodeInteraction?.openAccountFreezeInfo() - case let .link(_, url, _, _): - nodeInteraction?.openUrl(url) - } - case .hide: - nodeInteraction?.dismissNotice(notice) - case let .buttonChoice(isPositive): - switch notice { - case let .reviewLogin(newSessionReview, _): - nodeInteraction?.performActiveSessionAction(newSessionReview, isPositive) - default: - break - } - } - }), 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, appearance, selected, presentationData): @@ -1282,7 +1200,7 @@ public final class ChatListNode: ListView { return [] } } - private var interaction: ChatListNodeInteraction? + public private(set) var interaction: ChatListNodeInteraction? private var dequeuedInitialTransitionOnLayout = false private var enqueuedTransition: (ChatListNodeListViewTransition, () -> Void)? @@ -1383,7 +1301,6 @@ public final class ChatListNode: ListView { private let autoSetReady: Bool public let isMainTab = ValuePromise(false, ignoreRepeated: true) - private let suggestedChatListNotice = Promise(nil) public var synchronousDrawingWhenNotAnimated: Bool = false @@ -1868,38 +1785,6 @@ public final class ChatListNode: ListView { return } self.openStarsTopup?(amount) - }, dismissNotice: { [weak self] notice in - guard let self else { - return - } - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - switch notice { - case .xmasPremiumGift: - let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.xmasPremiumGift.id).startStandalone() - self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in - return true - })) - case .setupBirthday: - let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupBirthday.id).startStandalone() - self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_BirthdayInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in - return true - })) - case .birthdayPremiumGift: - let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.todayBirthdays.id).startStandalone() - self.present?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in - return true - })) - case .premiumGrace: - let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.gracePremium.id).startStandalone() - case .setupPhoto: - let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPhoto.id).startStandalone() - case .starsSubscriptionLowBalance: - let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.starsSubscriptionLowBalance.id).startStandalone() - case let .link(id, _, _, _): - let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: id).startStandalone() - default: - break - } }, editPeer: { _ in }, openWebApp: { [weak self] user in guard let self else { @@ -1992,172 +1877,12 @@ public final class ChatListNode: ListView { } else { displayArchiveIntro = .single(false) } - - let starsSubscriptionsContextPromise = Promise(nil) self.updateIsMainTabDisposable = (self.isMainTab.get() - |> deliverOnMainQueue).startStrict(next: { [weak self] isMainTab in - guard let self else { - return + |> deliverOnMainQueue).startStrict(next: { isMainTab in + if isMainTab { + let _ = context.engine.privacy.cleanupSessionReviews().startStandalone() } - - guard case .chatList(groupId: .root) = location, isMainTab else { - self.suggestedChatListNotice.set(.single(nil)) - return - } - - let _ = context.engine.privacy.cleanupSessionReviews().startStandalone() - - let twoStepData: Signal = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init)) - - let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) - |> map { view -> AppConfiguration in - let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue - return appConfiguration - } - |> distinctUntilChanged - |> map { appConfiguration -> AccountFreezeConfiguration in - return AccountFreezeConfiguration.with(appConfiguration: appConfiguration) - }) - - let suggestedChatListNoticeSignal: Signal = combineLatest( - context.engine.notices.getServerProvidedSuggestions(), - context.engine.notices.getServerDismissedSuggestions(), - twoStepData, - newSessionReviews(postbox: context.account.postbox), - context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), - TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId) - ), - context.account.stateManager.contactBirthdays, - starsSubscriptionsContextPromise.get(), - accountFreezeConfiguration - ) - |> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal in - let (accountPeer, birthday) = data - - if let newSessionReview = newSessionReviews.first { - return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count)) - } - if suggestions.contains(.setupPassword), let configuration { - var notSet = false - switch configuration { - case let .notSet(pendingEmail): - if pendingEmail == nil { - notSet = true - } - case .set: - break - } - if notSet { - return .single(.setupPassword) - } - } - - let today = Calendar(identifier: .gregorian).component(.day, from: Date()) - var todayBirthdayPeerIds: [EnginePeer.Id] = [] - for (peerId, birthday) in birthdays { - if birthday.day == today { - todayBirthdayPeerIds.append(peerId) - } - } - todayBirthdayPeerIds.sort { lhs, rhs in - return lhs < rhs - } - - if dismissedSuggestions.contains(ServerProvidedSuggestion.todayBirthdays.id) { - todayBirthdayPeerIds = [] - } - - if let _ = accountFreezeConfiguration.freezeUntilDate { - return .single(.accountFreeze) - } else if suggestions.contains(.starsSubscriptionLowBalance) { - if let starsSubscriptionsContext { - return starsSubscriptionsContext.state - |> map { state in - if state.balance > StarsAmount.zero && !state.subscriptions.isEmpty { - return .starsSubscriptionLowBalance( - amount: state.balance, - peers: state.subscriptions.map { $0.peer } - ) - } else { - return nil - } - } - } else { - starsSubscriptionsContextPromise.set(.single(context.engine.payments.peerStarsSubscriptionsContext(starsContext: nil, missingBalance: true))) - return .single(nil) - } - } else if suggestions.contains(.setupPhoto), let accountPeer, accountPeer.smallProfileImage == nil { - return .single(.setupPhoto(accountPeer)) - } else if suggestions.contains(.gracePremium) { - return .single(.premiumGrace) - } else if suggestions.contains(.xmasPremiumGift) { - return .single(.xmasPremiumGift) - } else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager { - return inAppPurchaseManager.availableProducts - |> map { products -> ChatListNotice? in - if products.count > 1 { - let shortestOptionPrice: (Int64, NSDecimalNumber) - if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) { - shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue) - } else { - shortestOptionPrice = (1, NSDecimalNumber(decimal: 1)) - } - for product in products { - if product.id.hasSuffix(".annual") { - let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(12) / Float(shortestOptionPrice.0) - let discount = Int32(round((1.0 - fraction) * 20.0) * 5.0) - if discount > 0 { - if suggestions.contains(.restorePremium) { - return .premiumRestore(discount: discount) - } else if suggestions.contains(.annualPremium) { - return .premiumAnnualDiscount(discount: discount) - } else if suggestions.contains(.upgradePremium) { - return .premiumUpgrade(discount: discount) - } - } - break - } - } - return nil - } else { - if !GlobalExperimentalSettings.isAppStoreBuild { - if suggestions.contains(.restorePremium) { - return .premiumRestore(discount: 0) - } else if suggestions.contains(.annualPremium) { - return .premiumAnnualDiscount(discount: 0) - } else if suggestions.contains(.upgradePremium) { - return .premiumUpgrade(discount: 0) - } - } - return nil - } - } - } else if !todayBirthdayPeerIds.isEmpty { - return context.engine.data.get( - EngineDataMap(todayBirthdayPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))) - ) - |> map { result -> ChatListNotice? in - var todayBirthdayPeers: [EnginePeer] = [] - for (peerId, _) in birthdays { - if let maybePeer = result[peerId], let peer = maybePeer { - todayBirthdayPeers.append(peer) - } - } - return .birthdayPremiumGift(peers: todayBirthdayPeers, birthdays: birthdays) - } - } else if suggestions.contains(.setupBirthday) && birthday == nil { - return .single(.setupBirthday) - } else if case let .link(id, url, title, subtitle) = suggestions.first(where: { if case .link = $0 { return true } else { return false} }) { - return .single(.link(id: id, url: url, title: title, subtitle: subtitle)) - } else { - return .single(nil) - } - } - |> distinctUntilChanged - - self.suggestedChatListNotice.set(suggestedChatListNoticeSignal) }).strict() let storageInfo: Signal @@ -2346,7 +2071,6 @@ public final class ChatListNode: ListView { hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, - suggestedChatListNotice.get(), savedMessagesPeer, chatListViewUpdate, self.statePromise.get(), @@ -2354,23 +2078,14 @@ public final class ChatListNode: ListView { chatListFilters, accountIsPremium ) - |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal in + |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal in let (update, filter) = updateAndFilter let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault) - let notice: ChatListNotice? - if let suggestedChatListNotice { - notice = suggestedChatListNotice - } else if let storageInfo { - notice = .clearStorage(sizeFraction: storageInfo) - } else { - notice = nil - } - let innerIsMainTab = location == .chatList(groupId: .root) && chatListFilter == nil - let (rawEntries, isLoading) = chatListNodeEntriesForView(view: update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location, contacts: contacts, accountPeerId: accountPeerId, isMainTab: innerIsMainTab) + let (rawEntries, isLoading) = chatListNodeEntriesForView(view: update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location, contacts: contacts, accountPeerId: accountPeerId, isMainTab: innerIsMainTab) var isEmpty = true var entries = rawEntries.filter { entry in switch entry { @@ -2697,7 +2412,6 @@ public final class ChatListNode: ListView { var didIncludeRemovingPeerId = false var didIncludeHiddenByDefaultArchive = false var didIncludeHiddenThread = false - var didIncludeNotice = false if let previous = previousView { for entry in previous.filteredEntries { if case let .PeerEntry(peerEntry) = entry { @@ -2724,15 +2438,12 @@ public final class ChatListNode: ListView { } } else if case let .GroupReferenceEntry(groupReferenceEntry) = entry { didIncludeHiddenByDefaultArchive = groupReferenceEntry.hiddenByDefault - } else if case .Notice = entry { - didIncludeNotice = true } } } var doesIncludeRemovingPeerId = false var doesIncludeArchive = false var doesIncludeHiddenByDefaultArchive = false - var doesIncludeNotice = false var doesIncludeHiddenThread = false for entry in processedView.filteredEntries { @@ -2761,8 +2472,6 @@ public final class ChatListNode: ListView { } else if case let .GroupReferenceEntry(groupReferenceEntry) = entry { doesIncludeArchive = true doesIncludeHiddenByDefaultArchive = groupReferenceEntry.hiddenByDefault - } else if case .Notice = entry { - doesIncludeNotice = true } } if previousPinnedChats != updatedPinnedChats || previousPinnedThreads != updatedPinnedThreads { @@ -2789,9 +2498,6 @@ public final class ChatListNode: ListView { if didIncludeHiddenThread != doesIncludeHiddenThread { disableAnimations = false } - if didIncludeNotice != doesIncludeNotice { - disableAnimations = false - } } if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault { @@ -3547,8 +3253,10 @@ public final class ChatListNode: ListView { if entryCount - 1 - i < 0 { continue } - if case .PeerEntry = transition.chatListView.filteredEntries[entryCount - 1 - i] { - } else { + switch transition.chatListView.filteredEntries[entryCount - 1 - i] { + case .PeerEntry, .GroupReferenceEntry: + break + default: continue } if case let .index(index) = transition.chatListView.filteredEntries[entryCount - 1 - i].sortIndex, case let .chatList(chatListIndex) = index, chatListIndex.pinningIndex != nil { @@ -3658,7 +3366,7 @@ public final class ChatListNode: ListView { } else { break loop } - case .ArchiveIntro, .EmptyIntro, .SectionHeader, .Notice, .HeaderEntry, .AdditionalCategory: + case .ArchiveIntro, .EmptyIntro, .SectionHeader, .HeaderEntry, .AdditionalCategory: break } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index dfc33618..cd42c2a4 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -79,23 +79,6 @@ public enum ChatListNodeEntryPromoInfo: Equatable { case psa(type: String, message: String?) } -public enum ChatListNotice: Equatable { - case clearStorage(sizeFraction: Double) - case setupPassword - case premiumUpgrade(discount: Int32) - case premiumAnnualDiscount(discount: Int32) - case premiumRestore(discount: Int32) - case xmasPremiumGift - case setupBirthday - case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday]) - case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int) - case premiumGrace - case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer]) - case setupPhoto(EnginePeer) - case accountFreeze - case link(id: String, url: String, title: ServerSuggestionInfo.Item.Text, subtitle: ServerSuggestionInfo.Item.Text) -} - enum ChatListNodeEntry: Comparable, Identifiable { struct PeerEntryData: Equatable { var index: EngineChatList.Item.Index @@ -339,6 +322,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { var unreadCount: Int var revealed: Bool var hiddenByDefault: Bool + var appearsPinned: Bool var storyState: ChatListNodeState.StoryState? init( @@ -351,6 +335,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { unreadCount: Int, revealed: Bool, hiddenByDefault: Bool, + appearsPinned: Bool, storyState: ChatListNodeState.StoryState? ) { self.index = index @@ -362,6 +347,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { self.unreadCount = unreadCount self.revealed = revealed self.hiddenByDefault = hiddenByDefault + self.appearsPinned = appearsPinned self.storyState = storyState } @@ -393,6 +379,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { if lhs.hiddenByDefault != rhs.hiddenByDefault { return false } + if lhs.appearsPinned != rhs.appearsPinned { + return false + } if lhs.storyState != rhs.storyState { return false } @@ -409,7 +398,6 @@ enum ChatListNodeEntry: Comparable, Identifiable { case ArchiveIntro(presentationData: ChatListPresentationData) case EmptyIntro(presentationData: ChatListPresentationData) case SectionHeader(presentationData: ChatListPresentationData, displayHide: Bool) - case Notice(presentationData: ChatListPresentationData, notice: ChatListNotice) case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData) var sortIndex: ChatListNodeEntrySortIndex { @@ -430,8 +418,6 @@ enum ChatListNodeEntry: Comparable, Identifiable { return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor)) case .SectionHeader: return .sectionHeader - case .Notice: - return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor.successor)) case let .AdditionalCategory(index, _, _, _, _, _, _): return .additionalCategory(index) } @@ -460,8 +446,6 @@ enum ChatListNodeEntry: Comparable, Identifiable { return .EmptyIntro case .SectionHeader: return .SectionHeader - case .Notice: - return .Notice case let .AdditionalCategory(_, id, _, _, _, _, _): return .additionalCategory(id) } @@ -534,18 +518,6 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else { return false } - case let .Notice(lhsPresentationData, lhsInfo): - if case let .Notice(rhsPresentationData, rhsInfo) = rhs { - if lhsPresentationData !== rhsPresentationData { - return false - } - if lhsInfo != rhsInfo { - return false - } - return true - } else { - return false - } case let .AdditionalCategory(lhsIndex, lhsId, lhsTitle, lhsImage, lhsAppearance, lhsSelected, lhsPresentationData): if case let .AdditionalCategory(rhsIndex, rhsId, rhsTitle, rhsImage, rhsAppearance, rhsSelected, rhsPresentationData) = rhs { if lhsIndex != rhsIndex { @@ -595,7 +567,7 @@ struct ChatListContactPeer { } } -func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer], accountPeerId: EnginePeer.Id, isMainTab: Bool) -> (entries: [ChatListNodeEntry], loading: Bool) { +func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer], accountPeerId: EnginePeer.Id, isMainTab: Bool) -> (entries: [ChatListNodeEntry], loading: Bool) { var groupItems = view.groupItems if isMainTab && state.archiveStoryState != nil && groupItems.isEmpty { groupItems.append(EngineChatList.GroupItem( @@ -656,6 +628,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, var hiddenGeneralThread: ChatListNodeEntry? + var hasPinned = false + loop: for entry in view.items { var peerId: EnginePeer.Id? var threadId: Int64? @@ -707,6 +681,17 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, if let threadData = entry.threadData, let threadId { threadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed, isHidden: threadData.isHidden, threadPeer: nil) } + + switch entry.index { + case let .chatList(chatList): + if chatList.pinningIndex != nil { + hasPinned = true + } + case let .forum(pinnedIndex, _, _, _, _): + if case .index = pinnedIndex { + hasPinned = true + } + } let entry: ChatListNodeEntry = .PeerEntry(ChatListNodeEntry.PeerEntryData( index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), @@ -796,6 +781,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, ))) if foundPinningIndex != 0 { foundPinningIndex -= 1 + hasPinned = true } } } @@ -886,6 +872,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, ))) if pinningIndex != 0 { pinningIndex -= 1 + hasPinned = true } } } @@ -908,10 +895,12 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, unreadCount: groupReference.unreadCount, revealed: state.hiddenItemShouldBeTemporaryRevealed, hiddenByDefault: hideArchivedFolderByDefault, + appearsPinned: hasPinned, storyState: mappedStoryState ))) if pinningIndex != 0 { pinningIndex -= 1 + hasPinned = true } } @@ -927,10 +916,6 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, result.append(.EmptyIntro(presentationData: state.presentationData)) } - if let notice { - result.append(.Notice(presentationData: state.presentationData, notice: notice)) - } - result.append(.HeaderEntry) } diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index 6b677831..b6134692 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -339,14 +339,6 @@ public struct ChatActiveGroupCallInfo: Equatable { } } -public struct ChatPresentationImportState: Equatable { - public var progress: Float - - public init(progress: Float) { - self.progress = progress - } -} - public struct ChatPresentationTranslationState: Equatable { public var isEnabled: Bool public var fromLang: String @@ -526,7 +518,6 @@ public final class ChatPresentationInterfaceState: Equatable { public let pendingUnpinnedAllMessages: Bool public let activeGroupCallInfo: ChatActiveGroupCallInfo? public let hasActiveGroupCall: Bool - public let importState: ChatPresentationImportState? public let reportReason: ReportReasonData? public let showCommands: Bool public let hasBotCommands: Bool @@ -568,7 +559,7 @@ public final class ChatPresentationInterfaceState: Equatable { public let persistentData: PersistentPeerData public let removePaidMessageFeeData: RemovePaidMessageFeeData? - public init(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, chatLocation: ChatLocation, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, threadData: ThreadData?, isGeneralThreadClosed: Bool?, replyMessage: Message?, accountPeerColor: AccountPeerColor?, businessIntro: TelegramBusinessIntro?) { + public init(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, chatLocation: ChatLocation, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, threadData: ThreadData?, isGeneralThreadClosed: Bool?, replyMessage: Message?, accountPeerColor: AccountPeerColor?, businessIntro: TelegramBusinessIntro?) { self.interfaceState = ChatInterfaceState() self.inputTextPanelState = ChatTextInputPanelState() self.editMessageState = nil @@ -619,7 +610,6 @@ public final class ChatPresentationInterfaceState: Equatable { self.pendingUnpinnedAllMessages = pendingUnpinnedAllMessages self.activeGroupCallInfo = activeGroupCallInfo self.hasActiveGroupCall = hasActiveGroupCall - self.importState = importState self.reportReason = nil self.showCommands = false self.hasBotCommands = false @@ -664,7 +654,7 @@ public final class ChatPresentationInterfaceState: Equatable { self.removePaidMessageFeeData = nil } - public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: UrlPreview?, editingUrlPreview: UrlPreview?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, historyFilter: HistoryFilter?, displayHistoryFilterAsList: Bool, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: ReportReasonData?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [SendAsPeer]?, botMenuButton: BotMenuButton, showWebView: Bool, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool, hasAtLeast3Messages: Bool, hasPlentyOfMessages: Bool, isPremium: Bool, premiumGiftOptions: [CachedPremiumGiftOption], suggestPremiumGift: Bool, forceInputCommandsHidden: Bool, voiceMessagesAvailable: Bool, customEmojiAvailable: Bool, threadData: ThreadData?, forumTopicData: ThreadData?, isGeneralThreadClosed: Bool?, translationState: ChatPresentationTranslationState?, replyMessage: Message?, accountPeerColor: AccountPeerColor?, savedMessagesTopicPeer: EnginePeer?, hasSearchTags: Bool, isPremiumRequiredForMessaging: Bool, sendPaidMessageStars: StarsAmount?, acknowledgedPaidMessage: Bool, hasSavedChats: Bool, appliedBoosts: Int32?, boostsToUnrestrict: Int32?, businessIntro: TelegramBusinessIntro?, hasBirthdayToday: Bool, adMessage: Message?, peerVerification: PeerVerification?, starGiftsAvailable: Bool, alwaysShowGiftButton: Bool, disallowedGifts: TelegramDisallowedGifts?, persistentData: PersistentPeerData, removePaidMessageFeeData: RemovePaidMessageFeeData?) { + public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: UrlPreview?, editingUrlPreview: UrlPreview?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, historyFilter: HistoryFilter?, displayHistoryFilterAsList: Bool, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, reportReason: ReportReasonData?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [SendAsPeer]?, botMenuButton: BotMenuButton, showWebView: Bool, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool, hasAtLeast3Messages: Bool, hasPlentyOfMessages: Bool, isPremium: Bool, premiumGiftOptions: [CachedPremiumGiftOption], suggestPremiumGift: Bool, forceInputCommandsHidden: Bool, voiceMessagesAvailable: Bool, customEmojiAvailable: Bool, threadData: ThreadData?, forumTopicData: ThreadData?, isGeneralThreadClosed: Bool?, translationState: ChatPresentationTranslationState?, replyMessage: Message?, accountPeerColor: AccountPeerColor?, savedMessagesTopicPeer: EnginePeer?, hasSearchTags: Bool, isPremiumRequiredForMessaging: Bool, sendPaidMessageStars: StarsAmount?, acknowledgedPaidMessage: Bool, hasSavedChats: Bool, appliedBoosts: Int32?, boostsToUnrestrict: Int32?, businessIntro: TelegramBusinessIntro?, hasBirthdayToday: Bool, adMessage: Message?, peerVerification: PeerVerification?, starGiftsAvailable: Bool, alwaysShowGiftButton: Bool, disallowedGifts: TelegramDisallowedGifts?, persistentData: PersistentPeerData, removePaidMessageFeeData: RemovePaidMessageFeeData?) { self.interfaceState = interfaceState self.chatLocation = chatLocation self.renderedPeer = renderedPeer @@ -715,7 +705,6 @@ public final class ChatPresentationInterfaceState: Equatable { self.pendingUnpinnedAllMessages = pendingUnpinnedAllMessages self.activeGroupCallInfo = activeGroupCallInfo self.hasActiveGroupCall = hasActiveGroupCall - self.importState = importState self.reportReason = reportReason self.showCommands = showCommands self.hasBotCommands = hasBotCommands @@ -918,9 +907,6 @@ public final class ChatPresentationInterfaceState: Equatable { if lhs.hasActiveGroupCall != rhs.hasActiveGroupCall { return false } - if lhs.importState != rhs.importState { - return false - } if lhs.reportReason != rhs.reportReason { return false } @@ -1045,35 +1031,35 @@ public final class ChatPresentationInterfaceState: Equatable { } public func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedChatLocation(_ chatLocation: ChatLocation) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPeer(_ f: (RenderedPeer?) -> RenderedPeer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedIsNotAccessible(_ isNotAccessible: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedExplicitelyCanPinMessages(_ explicitelyCanPinMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedContactStatus(_ contactStatus: ChatContactStatus?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedHasBots(_ hasBots: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedIsArchived(_ isArchived: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { @@ -1090,311 +1076,307 @@ public final class ChatPresentationInterfaceState: Equatable { inputQueryResults.removeValue(forKey: queryKind) } - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedInputTextPanelState(_ f: (ChatTextInputPanelState) -> ChatTextInputPanelState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedEditMessageState(_ editMessageState: ChatEditInterfaceMessageState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedInputMode(_ f: (ChatInputMode) -> ChatInputMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedTitlePanelContext(_ f: ([ChatTitlePanelContext]) -> [ChatTitlePanelContext]) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedKeyboardButtonsMessage(_ message: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPinnedMessage(_ pinnedMessage: ChatPinnedMessage?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPeerIsBlocked(_ peerIsBlocked: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPeerIsMuted(_ peerIsMuted: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPeerDiscussionId(_ peerDiscussionId: PeerId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedCallsAvailable(_ callsAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedCallsPrivate(_ callsPrivate: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedSlowmodeState(_ slowmodeState: ChatSlowmodeState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedBotStartPayload(_ botStartPayload: String?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedChatHistoryState(_ chatHistoryState: ChatHistoryNodeHistoryState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedUrlPreview(_ urlPreview: UrlPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedEditingUrlPreview(_ editingUrlPreview: UrlPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedSearch(_ search: ChatSearchData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedSearchQuerySuggestionResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedHistoryFilter(_ historyFilter: HistoryFilter?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedDisplayHistoryFilterAsList(_ displayHistoryFilterAsList: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedMode(_ mode: ChatControllerPresentationMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPresentationReady(_ presentationReady: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedTheme(_ theme: PresentationTheme) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedStrings(_ strings: PresentationStrings) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedDateTimeFormat(_ dateTimeFormat: PresentationDateTimeFormat) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedChatWallpaper(_ chatWallpaper: TelegramWallpaper) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedBubbleCorners(_ bubbleCorners: PresentationChatBubbleCorners) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedSubject(_ subject: ChatControllerSubject?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedAutoremoveTimeout(_ autoremoveTimeout: Int32?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPendingUnpinnedAllMessages(_ pendingUnpinnedAllMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedActiveGroupCallInfo(_ activeGroupCallInfo: ChatActiveGroupCallInfo?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedHasActiveGroupCall(_ hasActiveGroupCall: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) - } - - public func updatedImportState(_ importState: ChatPresentationImportState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedReportReason(_ reportReason: ReportReasonData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedShowCommands(_ showCommands: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedHasBotCommands(_ hasBotCommands: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedShowSendAsPeers(_ showSendAsPeers: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedSendAsPeers(_ sendAsPeers: [SendAsPeer]?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedCurrentSendAsPeerId(_ currentSendAsPeerId: PeerId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedBotMenuButton(_ botMenuButton: BotMenuButton) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedShowWebView(_ showWebView: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedCopyProtectionEnabled(_ copyProtectionEnabled: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedHasAtLeast3Messages(_ hasAtLeast3Messages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedHasPlentyOfMessages(_ hasPlentyOfMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedIsPremium(_ isPremium: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPremiumGiftOptions(_ premiumGiftOptions: [CachedPremiumGiftOption]) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedSuggestPremiumGift(_ suggestPremiumGift: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedForceInputCommandsHidden(_ forceInputCommandsHidden: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedVoiceMessagesAvailable(_ voiceMessagesAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedCustomEmojiAvailable(_ customEmojiAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedThreadData(_ threadData: ThreadData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedForumTopicData(_ forumTopicData: ThreadData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedIsGeneralThreadClosed(_ isGeneralThreadClosed: Bool?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedTranslationState(_ translationState: ChatPresentationTranslationState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedReplyMessage(_ replyMessage: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedAccountPeerColor(_ accountPeerColor: AccountPeerColor?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedSavedMessagesTopicPeer(_ savedMessagesTopicPeer: EnginePeer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedHasSearchTags(_ hasSearchTags: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedIsPremiumRequiredForMessaging(_ isPremiumRequiredForMessaging: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedSendPaidMessageStars(_ sendPaidMessageStars: StarsAmount?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedAcknowledgedPaidMessage(_ acknowledgedPaidMessage: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedHasSavedChats(_ hasSavedChats: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedAppliedBoosts(_ appliedBoosts: Int32?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedBoostsToUnrestrict(_ boostsToUnrestrict: Int32?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedBusinessIntro(_ businessIntro: TelegramBusinessIntro?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedHasBirthdayToday(_ hasBirthdayToday: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedAdMessage(_ adMessage: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPeerVerification(_ peerVerification: PeerVerification?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedStarGiftsAvailable(_ starGiftsAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedAlwaysShowGiftButton(_ alwaysShowGiftButton: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedDisallowedGifts(_ disallowedGifts: TelegramDisallowedGifts?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedPersistentData(_ persistentData: PersistentPeerData) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData) } public func updatedRemovePaidMessageFeeData(_ removePaidMessageFeeData: RemovePaidMessageFeeData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: removePaidMessageFeeData) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: removePaidMessageFeeData) } } diff --git a/submodules/ChatTextLinkEditUI/BUILD b/submodules/ChatTextLinkEditUI/BUILD index e48e5676..39f5f783 100644 --- a/submodules/ChatTextLinkEditUI/BUILD +++ b/submodules/ChatTextLinkEditUI/BUILD @@ -10,14 +10,17 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/Postbox:Postbox", - "//submodules/TelegramCore:TelegramCore", - "//submodules/AccountContext:AccountContext", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/UrlEscaping:UrlEscaping", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", + "//submodules/UrlEscaping", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift b/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift index c802ca39..4229f746 100644 --- a/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift +++ b/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift @@ -7,458 +7,90 @@ import TelegramCore import TelegramPresentationData import AccountContext import UrlEscaping +import ComponentFlow +import AlertComponent +import AlertMultilineInputFieldComponent -private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { - private var theme: PresentationTheme - private let backgroundNode: ASImageNode - fileprivate let textInputNode: EditableTextNode - private let placeholderNode: ASTextNode +public func chatTextLinkEditController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + text: String, + link: String?, + apply: @escaping (String?) -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + let inputState = AlertMultilineInputFieldComponent.ExternalState() - var updateHeight: (() -> Void)? - var complete: (() -> Void)? - var textChanged: ((String) -> Void)? + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: link != nil ? strings.TextFormat_EditLinkTitle : strings.TextFormat_AddLinkTitle) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.TextFormat_AddLinkText(text).string)) + ) + )) - private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0) - private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0) - - var text: String { - get { - return self.textInputNode.attributedText?.string ?? "" - } - set { - self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor) - self.placeholderNode.isHidden = !newValue.isEmpty - } - } - - var placeholder: String = "" { - didSet { - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - } - } - - init(theme: PresentationTheme, placeholder: String) { - self.theme = theme - - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - - self.textInputNode = EditableTextNode() - self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor] - self.textInputNode.clipsToBounds = true - self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) - self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0) - self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance - self.textInputNode.keyboardType = .URL - self.textInputNode.autocapitalizationType = .none - self.textInputNode.returnKeyType = .done - self.textInputNode.autocorrectionType = .no - self.textInputNode.tintColor = theme.actionSheet.controlAccentColor - - self.placeholderNode = ASTextNode() - self.placeholderNode.isUserInteractionEnabled = false - self.placeholderNode.displaysAsynchronously = false - self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - - super.init() - - self.textInputNode.delegate = self - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.textInputNode) - self.addSubnode(self.placeholderNode) - } - - func updateTheme(_ theme: PresentationTheme) { - self.theme = theme - - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor - } - - func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let textFieldHeight = self.calculateTextFieldMetrics(width: width) - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - - let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom)) - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - - let placeholderSize = self.placeholderNode.measure(backgroundFrame.size) - transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) - - transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height))) - - return panelHeight - } - - func activateInput() { - self.textInputNode.becomeFirstResponder() - } - - func deactivateInput() { - self.textInputNode.resignFirstResponder() - } - - @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { - self.updateTextNodeText(animated: true) - self.textChanged?(editableTextNode.textView.text) - self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty - } - - func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - if text == "\n" { - self.complete?() - return false - } - return true - } - - private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height)) - - return min(61.0, max(33.0, unboundTextFieldHeight)) - } - - private func updateTextNodeText(animated: Bool) { - let backgroundInsets = self.backgroundInsets - - let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width) - - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - if !self.bounds.size.height.isEqual(to: panelHeight) { - self.updateHeight?() - } - } - - @objc func clearPressed() { - self.textInputNode.attributedText = nil - self.deactivateInput() - } -} - -private final class ChatTextLinkEditAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - private let text: String - - private let titleNode: ASTextNode - private let textNode: ASTextNode - let inputFieldNode: ChatTextLinkEditInputFieldNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let disposable = MetaDisposable() - - private var validLayout: CGSize? - - private let hapticFeedback = HapticFeedback() - - var complete: (() -> Void)? { - didSet { - self.inputFieldNode.complete = self.complete - } - } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - private var isEditing = false - private let allowEmpty: Bool - - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, link: String?, allowEmpty: Bool) { - self.strings = strings - self.text = text - self.isEditing = link != nil - self.allowEmpty = allowEmpty - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 2 - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 2 - - self.inputFieldNode = ChatTextLinkEditInputFieldNode(theme: ptheme, placeholder: strings.TextFormat_AddLinkPlaceholder) - self.inputFieldNode.text = link ?? "" - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - - self.addSubnode(self.inputFieldNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - self.actionNodes.last?.actionEnabled = !(link ?? "").isEmpty - if allowEmpty { - self.actionNodes.last?.actionEnabled = true - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.inputFieldNode.updateHeight = { [weak self] in - if let strongSelf = self { - if let _ = strongSelf.validLayout { - strongSelf.requestLayout?(.animated(duration: 0.15, curve: .spring)) - } - } - } - - self.inputFieldNode.textChanged = { [weak self] text in - if let strongSelf = self, let lastNode = strongSelf.actionNodes.last { - if strongSelf.allowEmpty { - lastNode.actionEnabled = true - } else { - lastNode.actionEnabled = !text.isEmpty - } - } - } - - self.updateTheme(theme) - - if (link ?? "").isEmpty { - Queue.mainQueue().after(0.1, { - let pasteboard = UIPasteboard.general - if pasteboard.hasURLs { - if let url = pasteboard.url?.absoluteString, !url.isEmpty { - self.inputFieldNode.text = url - if let lastNode = self.actionNodes.last { - lastNode.actionEnabled = true - } - self.inputFieldNode.textInputNode.textView.selectAll(nil) - } - } - }) - } - } - - deinit { - self.disposable.dispose() - } - - var link: String { - return self.inputFieldNode.text - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.isEditing ? self.strings.TextFormat_EditLinkTitle : self.strings.TextFormat_AddLinkTitle, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = NSAttributedString(string: self.strings.TextFormat_AddLinkText(self.text).string, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) - - let hadValidLayout = self.validLayout != nil - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - let spacing: CGFloat = 5.0 - - let titleSize = self.titleNode.measure(measureSize) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 4.0 - - let textSize = self.textNode.measure(measureSize) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 6.0 + spacing - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) - - var contentWidth = max(titleSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - let inputFieldWidth = resultWidth - let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) - let inputHeight = inputFieldHeight - transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight)) - transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0) - - let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if !hadValidLayout { - self.inputFieldNode.activateInput() - } - - return resultSize - } - - func animateError() { - self.inputFieldNode.layer.addShakeAnimation() - self.hapticFeedback.error() - } -} - -public func chatTextLinkEditController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, account: Account, text: String, link: String?, allowEmpty: Bool = false, apply: @escaping (String?) -> Void) -> AlertController { - let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 } - - var dismissImpl: ((Bool) -> Void)? var applyImpl: (() -> Void)? + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertMultilineInputFieldComponent( + context: context, + initialValue: link.flatMap { NSAttributedString(string: $0) }, + placeholder: strings.TextFormat_AddLinkPlaceholder, + returnKeyType: .done, + keyboardType: .URL, + autocapitalizationType: .none, + autocorrectionType: .no, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - apply(nil) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: { - applyImpl?() - })] - - let contentNode = ChatTextLinkEditAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, link: link, allowEmpty: allowEmpty) - contentNode.complete = { - applyImpl?() + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) } - applyImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - let updatedLink = explicitUrl(contentNode.link) + + var dismissImpl: (() -> Void)? + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.Common_Done, type: .default, action: { + applyImpl?() + }, autoDismiss: false) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + applyImpl = { + let updatedLink = explicitUrl(inputState.value.string) if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true, "tg": false, "ton": false, "tonsite": true]) { - dismissImpl?(true) + dismissImpl?() apply(updatedLink) - } else if allowEmpty && contentNode.link.isEmpty { - dismissImpl?(true) + } else if inputState.value.string.isEmpty { + dismissImpl?() apply("") } else { - contentNode.animateError() + inputState.animateError() } } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.inputFieldNode.updateTheme(presentationData.theme) - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() + dismissImpl = { [weak alertController] in + alertController?.dismiss(completion: nil) } - dismissImpl = { [weak controller] animated in - contentNode.inputFieldNode.deactivateInput() - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + return alertController } diff --git a/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityContentNode.swift b/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityContentNode.swift index 1944d18b..11a1b756 100644 --- a/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityContentNode.swift +++ b/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityContentNode.swift @@ -110,7 +110,7 @@ public class ChatTitleActivityContentNode: ASDisplayNode { }) if case .slide = style { - self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 14.0), duration: transitionDuration, additive: true) + self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 4.0), duration: transitionDuration, additive: true) } } @@ -118,7 +118,7 @@ public class ChatTitleActivityContentNode: ASDisplayNode { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: transitionDuration) if case .slide = style { - self.layer.animatePosition(from: CGPoint(x: 0.0, y: -14.0), to: CGPoint(), duration: transitionDuration, additive: true) + self.layer.animatePosition(from: CGPoint(x: 0.0, y: -4.0), to: CGPoint(), duration: transitionDuration, additive: true) } } diff --git a/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.swift b/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.swift index b9013f66..86efa6d0 100644 --- a/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.swift +++ b/submodules/ChatTitleActivityNode/Sources/ChatTitleActivityNode.swift @@ -130,6 +130,9 @@ public class ChatTitleActivityNode: ASDisplayNode { } public func updateLayout(_ constrainedSize: CGSize, offset: CGFloat = 0.0, alignment: NSTextAlignment) -> CGSize { - return CGSize(width: 0.0, height: self.contentNode?.updateLayout(constrainedSize, offset: offset, alignment: alignment).height ?? 0.0) + guard let contentSize = self.contentNode?.updateLayout(constrainedSize, offset: offset, alignment: alignment) else { + return CGSize() + } + return contentSize } } diff --git a/submodules/ComponentFlow/Source/Base/CombinedComponent.swift b/submodules/ComponentFlow/Source/Base/CombinedComponent.swift index 5f019bc5..3c818879 100644 --- a/submodules/ComponentFlow/Source/Base/CombinedComponent.swift +++ b/submodules/ComponentFlow/Source/Base/CombinedComponent.swift @@ -410,7 +410,7 @@ public final class CombinedComponentContext { public let component: ComponentType public let availableSize: CGSize public let transition: ComponentTransition - private let addImpl: (_ updatedComponent: _UpdatedChildComponent) -> Void + private let addImpl: (_ updatedComponent: _UpdatedChildComponent, _ container: UIView?) -> Void public var environment: Environment { return self.context.environment @@ -425,7 +425,7 @@ public final class CombinedComponentContext { component: ComponentType, availableSize: CGSize, transition: ComponentTransition, - add: @escaping (_ updatedComponent: _UpdatedChildComponent) -> Void + add: @escaping (_ updatedComponent: _UpdatedChildComponent, _ container: UIView?) -> Void ) { self.context = context self.view = view @@ -436,7 +436,11 @@ public final class CombinedComponentContext { } public func add(_ updatedComponent: _UpdatedChildComponent) { - self.addImpl(updatedComponent) + self.addImpl(updatedComponent, nil) + } + + public func addWithExternalContainer(_ updatedComponent: _UpdatedChildComponent, container: UIView) { + self.addImpl(updatedComponent, container) } } @@ -671,7 +675,7 @@ public extension CombinedComponent { component: self, availableSize: availableSize, transition: transition, - add: { updatedChild in + add: { updatedChild, optionalContainer in if !addedChildIds.insert(updatedChild.id).inserted { preconditionFailure("Child component can only be added once") } @@ -692,7 +696,11 @@ public extension CombinedComponent { context.childViewIndices.remove(at: previousView.index) context.childViewIndices.insert(updatedChild.id, at: index) previousView.index = index - view.insertSubview(previousView.view, at: index) + if let optionalContainer { + optionalContainer.addSubview(previousView.view) + } else { + view.insertSubview(previousView.view, at: index) + } } previousView.updateGestures(updatedChild.gestures) @@ -715,7 +723,11 @@ public extension CombinedComponent { childView.transition = updatedChild.transitionDisappear childView.transitionWithGuide = updatedChild.transitionDisappearWithGuide - view.insertSubview(updatedChild.view, at: index) + if let optionalContainer { + optionalContainer.addSubview(updatedChild.view) + } else { + view.insertSubview(updatedChild.view, at: index) + } updatedChild.view.layer.anchorPoint = updatedChild._anchorPoint ?? CGPoint(x: 0.5, y: 0.5) diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index 4dcd0401..4beba45c 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -489,7 +489,25 @@ public struct ComponentTransition { } public func setAlpha(view: UIView, alpha: CGFloat, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) { - self.setAlpha(layer: view.layer, alpha: alpha, delay: delay, completion: completion) + if view.alpha == alpha { + completion?(true) + return + } + switch self.animation { + case .none: + view.alpha = alpha + view.layer.removeAnimation(forKey: "opacity") + completion?(true) + case .curve: + let previousAlpha: Float + if view.layer.animation(forKey: "opacity") != nil { + previousAlpha = view.layer.presentation()?.opacity ?? Float(view.alpha) + } else { + previousAlpha = Float(view.alpha) + } + view.alpha = alpha + self.animateAlpha(layer: view.layer, from: CGFloat(previousAlpha), to: alpha, delay: delay, completion: completion) + } } public func setAlpha(layer: CALayer, alpha: CGFloat, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) { @@ -1335,6 +1353,41 @@ public struct ComponentTransition { completion: completion ) } + + public func setBlur(layer: CALayer, radius: CGFloat, completion: ((Bool) -> Void)? = nil) { + var currentRadius: CGFloat = 0.0 + if let currentFilters = layer.filters { + for filter in currentFilters { + if let filter = filter as? NSObject, filter.description.contains("gaussianBlur") { + currentRadius = filter.value(forKey: "inputRadius") as? CGFloat ?? 0.0 + } + } + } + + if currentRadius == radius { + completion?(true) + return + } + + if let blurFilter = CALayer.blur() { + blurFilter.setValue(radius as NSNumber, forKey: "inputRadius") + layer.filters = [blurFilter] + switch self.animation { + case .none: + completion?(true) + case let .curve(duration, curve): + layer.animate(from: currentRadius as NSNumber, to: radius as NSNumber, keyPath: "filters.gaussianBlur.inputRadius", duration: duration, delay: 0.0, curve: curve, removeOnCompletion: true, additive: false,completion: { [weak layer] flag in + if let layer { + if radius <= 0.0 { + layer.filters = nil + } + } + + completion?(flag) + }) + } + } + } public func animateBlur(layer: CALayer, fromRadius: CGFloat, toRadius: CGFloat, delay: Double = 0.0, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { let duration: Double @@ -1359,4 +1412,23 @@ public struct ComponentTransition { }) } } + + public func animateMeshTransform(layer: CALayer, from fromValue: NSObject, to toValue: NSObject, delay: Double = 0.0, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + switch self.animation { + case .none: + completion?(true) + case let .curve(duration, curve): + layer.animate( + from: fromValue, + to: toValue, + keyPath: "meshTransform", + duration: duration, + delay: delay, + curve: curve, + removeOnCompletion: removeOnCompletion, + additive: false, + completion: completion + ) + } + } } diff --git a/submodules/ComponentFlow/Source/Components/Button.swift b/submodules/ComponentFlow/Source/Components/Button.swift index c93ef055..a40aeb0d 100644 --- a/submodules/ComponentFlow/Source/Components/Button.swift +++ b/submodules/ComponentFlow/Source/Components/Button.swift @@ -3,6 +3,7 @@ import UIKit public final class Button: Component { public let content: AnyComponent + public let contentInsets: UIEdgeInsets public let minSize: CGSize? public let hitTestEdgeInsets: UIEdgeInsets? public let tag: AnyObject? @@ -15,6 +16,7 @@ public final class Button: Component { convenience public init( content: AnyComponent, + contentInsets: UIEdgeInsets = UIEdgeInsets(), isEnabled: Bool = true, automaticHighlight: Bool = true, action: @escaping () -> Void, @@ -22,6 +24,7 @@ public final class Button: Component { ) { self.init( content: content, + contentInsets: contentInsets, minSize: nil, hitTestEdgeInsets: nil, tag: nil, @@ -35,6 +38,7 @@ public final class Button: Component { private init( content: AnyComponent, + contentInsets: UIEdgeInsets = UIEdgeInsets(), minSize: CGSize? = nil, hitTestEdgeInsets: UIEdgeInsets? = nil, tag: AnyObject? = nil, @@ -46,6 +50,7 @@ public final class Button: Component { highlightedAction: ActionSlot? ) { self.content = content + self.contentInsets = contentInsets self.minSize = minSize self.hitTestEdgeInsets = hitTestEdgeInsets self.tag = tag @@ -60,6 +65,7 @@ public final class Button: Component { public func minSize(_ minSize: CGSize?) -> Button { return Button( content: self.content, + contentInsets: self.contentInsets, minSize: minSize, hitTestEdgeInsets: self.hitTestEdgeInsets, tag: self.tag, @@ -75,6 +81,7 @@ public final class Button: Component { public func withHitTestEdgeInsets(_ hitTestEdgeInsets: UIEdgeInsets?) -> Button { return Button( content: self.content, + contentInsets: self.contentInsets, minSize: self.minSize, hitTestEdgeInsets: hitTestEdgeInsets, tag: self.tag, @@ -90,6 +97,7 @@ public final class Button: Component { public func withIsExclusive(_ isExclusive: Bool) -> Button { return Button( content: self.content, + contentInsets: self.contentInsets, minSize: self.minSize, hitTestEdgeInsets: self.hitTestEdgeInsets, tag: self.tag, @@ -106,6 +114,7 @@ public final class Button: Component { public func withHoldAction(_ holdAction: ((UIView) -> Void)?) -> Button { return Button( content: self.content, + contentInsets: self.contentInsets, minSize: self.minSize, hitTestEdgeInsets: self.hitTestEdgeInsets, tag: self.tag, @@ -121,6 +130,7 @@ public final class Button: Component { public func tagged(_ tag: AnyObject) -> Button { return Button( content: self.content, + contentInsets: self.contentInsets, minSize: self.minSize, hitTestEdgeInsets: self.hitTestEdgeInsets, tag: tag, @@ -137,6 +147,9 @@ public final class Button: Component { if lhs.content != rhs.content { return false } + if lhs.contentInsets != rhs.contentInsets { + return false + } if lhs.minSize != rhs.minSize { return false } @@ -318,6 +331,8 @@ public final class Button: Component { size.width = max(size.width, minSize.width) size.height = max(size.height, minSize.height) } + size.width += component.contentInsets.left + component.contentInsets.right + size.height += component.contentInsets.top + component.contentInsets.bottom self.component = component diff --git a/submodules/ComponentFlow/Source/Components/Image.swift b/submodules/ComponentFlow/Source/Components/Image.swift index 78475c94..f18aba2e 100644 --- a/submodules/ComponentFlow/Source/Components/Image.swift +++ b/submodules/ComponentFlow/Source/Components/Image.swift @@ -6,17 +6,20 @@ public final class Image: Component { public let tintColor: UIColor? public let size: CGSize? public let contentMode: UIImageView.ContentMode + public let cornerRadius: CGFloat public init( image: UIImage?, tintColor: UIColor? = nil, size: CGSize? = nil, - contentMode: UIImageView.ContentMode = .scaleToFill + contentMode: UIImageView.ContentMode = .scaleToFill, + cornerRadius: CGFloat = 0.0 ) { self.image = image self.tintColor = tintColor self.size = size self.contentMode = contentMode + self.cornerRadius = cornerRadius } public static func ==(lhs: Image, rhs: Image) -> Bool { @@ -32,6 +35,9 @@ public final class Image: Component { if lhs.contentMode != rhs.contentMode { return false } + if lhs.cornerRadius != rhs.cornerRadius { + return false + } return true } @@ -47,7 +53,9 @@ public final class Image: Component { func update(component: Image, availableSize: CGSize, environment: Environment, transition: ComponentTransition) -> CGSize { self.image = component.image self.contentMode = component.contentMode - + self.clipsToBounds = component.cornerRadius > 0.0 + + transition.setCornerRadius(layer: self.layer, cornerRadius: component.cornerRadius) transition.setTintColor(view: self, color: component.tintColor ?? .white) switch component.contentMode { diff --git a/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift b/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift index 7766272b..2519f2f6 100644 --- a/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift +++ b/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift @@ -43,6 +43,7 @@ public final class MultilineTextWithEntitiesComponent: Component { public let handleSpoilers: Bool public let manualVisibilityControl: Bool public let resetAnimationsOnVisibilityChange: Bool + public let enableLooping: Bool public let displaysAsynchronously: Bool public let maxWidth: CGFloat? public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? @@ -71,6 +72,7 @@ public final class MultilineTextWithEntitiesComponent: Component { handleSpoilers: Bool = false, manualVisibilityControl: Bool = false, resetAnimationsOnVisibilityChange: Bool = false, + enableLooping: Bool = true, displaysAsynchronously: Bool = true, maxWidth: CGFloat? = nil, highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil, @@ -99,6 +101,7 @@ public final class MultilineTextWithEntitiesComponent: Component { self.handleSpoilers = handleSpoilers self.manualVisibilityControl = manualVisibilityControl self.resetAnimationsOnVisibilityChange = resetAnimationsOnVisibilityChange + self.enableLooping = enableLooping self.displaysAsynchronously = displaysAsynchronously self.maxWidth = maxWidth self.tapAction = tapAction @@ -142,6 +145,9 @@ public final class MultilineTextWithEntitiesComponent: Component { if lhs.resetAnimationsOnVisibilityChange != rhs.resetAnimationsOnVisibilityChange { return false } + if lhs.enableLooping != rhs.enableLooping { + return false + } if lhs.displaysAsynchronously != rhs.displaysAsynchronously { return false } @@ -250,6 +256,7 @@ public final class MultilineTextWithEntitiesComponent: Component { self.textNode.tapAttributeAction = component.tapAction self.textNode.longTapAttributeAction = component.longTapAction self.textNode.spoilerColor = component.spoilerColor + self.textNode.enableLooping = component.enableLooping self.textNode.resetEmojiToFirstFrameAutomatically = component.resetAnimationsOnVisibilityChange diff --git a/submodules/Components/PagerComponent/Sources/PagerComponent.swift b/submodules/Components/PagerComponent/Sources/PagerComponent.swift index 42f1c2b0..4b11ff0e 100644 --- a/submodules/Components/PagerComponent/Sources/PagerComponent.swift +++ b/submodules/Components/PagerComponent/Sources/PagerComponent.swift @@ -974,8 +974,9 @@ public final class PagerComponent: Component { public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment) @@ -342,7 +341,7 @@ public final class SheetComponent: C let contentOffset = (self.scrollView.contentOffset.y + self.scrollView.contentInset.top - self.scrollView.contentSize.height) * -1.0 let dismissalOffset = self.scrollView.contentSize.height + abs(contentView.frame.minY) let delta = dismissalOffset - contentOffset - var targetPosition = self.scrollView.center.y + delta + var targetPosition = self.scrollView.center.y + delta + 6.0 if self.isCentered { targetPosition = self.frame.height + self.scrollView.frame.height * 0.5 } @@ -351,7 +350,7 @@ public final class SheetComponent: C completion() }) } else { - var targetOffset: CGFloat = self.scrollView.contentSize.height + abs(contentView.frame.minY) + var targetOffset: CGFloat = self.scrollView.contentSize.height + abs(contentView.frame.minY) + 6.0 if self.isCentered { targetOffset = self.frame.height + self.scrollView.frame.height * 0.5 } @@ -468,7 +467,7 @@ public final class SheetComponent: C switch component.style { case .glass: let clipFrame = CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)) - self.clipView.update(size: clipFrame.size, color: .clear, topCornerRadius: topCornerRadius, bottomCornerRadius: bottomCornerRadius, transition: transition) + self.clipView.update(size: clipFrame.size, color: .clear, topCornerRadius: topCornerRadius - 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition) transition.setFrame(view: self.clipView, frame: clipFrame) transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil) @@ -482,7 +481,7 @@ public final class SheetComponent: C transition.setFrame(view: effectView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) } } - self.backgroundView.update(size: contentSize, color: backgroundColor, topCornerRadius: topCornerRadius, bottomCornerRadius: bottomCornerRadius, transition: transition) + self.backgroundView.update(size: contentSize, color: backgroundColor, topCornerRadius: topCornerRadius + 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition) } } transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) diff --git a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift index 25d70a78..e017ad64 100644 --- a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift +++ b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift @@ -280,12 +280,14 @@ open class ViewControllerComponentContainer: ViewController { case .none: navigationBarPresentationData = nil case .transparent: - navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true) + navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true, style: .glass) case .default: - navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData) + navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, style: .glass) } super.init(navigationBarPresentationData: navigationBarPresentationData) + self._hasGlassStyle = true + self.setupPresentationData(effectiveUpdatedPresentationData, navigationBarAppearance: navigationBarAppearance, statusBarStyle: statusBarStyle, presentationMode: presentationMode) } @@ -308,9 +310,9 @@ open class ViewControllerComponentContainer: ViewController { case .none: navigationBarPresentationData = nil case .transparent: - navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true) + navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true, style: .glass) case .default: - navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData) + navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, style: .glass) } super.init(navigationBarPresentationData: navigationBarPresentationData) @@ -356,12 +358,12 @@ open class ViewControllerComponentContainer: ViewController { case .none: navigationBarPresentationData = nil case .transparent: - navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true) + navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true, style: .glass) case .default: - navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData) + navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, style: .glass) } if let navigationBarPresentationData { - strongSelf.navigationBar?.updatePresentationData(navigationBarPresentationData) + strongSelf.navigationBar?.updatePresentationData(navigationBarPresentationData, transition: .immediate) } if let layout = strongSelf.validLayout { diff --git a/submodules/ComposePollUI/BUILD b/submodules/ComposePollUI/BUILD index 04f0e88a..db6a0ef3 100644 --- a/submodules/ComposePollUI/BUILD +++ b/submodules/ComposePollUI/BUILD @@ -42,7 +42,6 @@ swift_library( "//submodules/ChatPresentationInterfaceState", "//submodules/TelegramUI/Components/EmojiSuggestionsComponent", "//submodules/TelegramUI/Components/ListComposePollOptionComponent", - "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ diff --git a/submodules/ComposePollUI/Sources/ComposePollScreen.swift b/submodules/ComposePollUI/Sources/ComposePollScreen.swift index c00d4021..32b9cdde 100644 --- a/submodules/ComposePollUI/Sources/ComposePollScreen.swift +++ b/submodules/ComposePollUI/Sources/ComposePollScreen.swift @@ -27,7 +27,6 @@ import EmojiSuggestionsComponent import TextFormat import TextFieldComponent import ListComposePollOptionComponent -import EdgeEffect import GlassBarButtonComponent public final class ComposedPoll { @@ -76,6 +75,7 @@ final class ComposePollScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let peer: EnginePeer let isQuiz: Bool? let initialData: ComposePollScreen.InitialData @@ -83,12 +83,14 @@ final class ComposePollScreenComponent: Component { init( context: AccountContext, + overNavigationContainer: UIView, peer: EnginePeer, isQuiz: Bool?, initialData: ComposePollScreen.InitialData, completion: @escaping (ComposedPoll) -> Void ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.peer = peer self.isQuiz = isQuiz self.initialData = initialData @@ -112,7 +114,6 @@ final class ComposePollScreenComponent: Component { final class View: UIView, UIScrollViewDelegate { private let scrollView: UIScrollView - private let edgeEffectView: EdgeEffectView private var reactionInput: ComponentView? private let pollTextSection = ComponentView() @@ -184,8 +185,6 @@ final class ComposePollScreenComponent: Component { self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.alwaysBounceVertical = true - self.edgeEffectView = EdgeEffectView() - self.pollOptionsSectionContainer = ListSectionContentView(frame: CGRect()) super.init(frame: frame) @@ -193,8 +192,6 @@ final class ComposePollScreenComponent: Component { self.scrollView.delegate = self self.addSubview(self.scrollView) - self.addSubview(self.edgeEffectView) - let reorderRecognizer = ReorderGestureRecognizer( shouldBegin: { [weak self] point in guard let self, let (id, item) = self.item(at: point) else { @@ -550,7 +547,6 @@ final class ComposePollScreenComponent: Component { pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, @@ -1624,11 +1620,6 @@ final class ComposePollScreenComponent: Component { self.scrollView.verticalScrollIndicatorInsets = scrollInsets } - let edgeEffectHeight: CGFloat = 80.0 - let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) - transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) - let title = self.isQuiz ? environment.strings.CreatePoll_QuizTitle : environment.strings.CreatePoll_Title let titleSize = self.title.update( transition: .immediate, @@ -1649,23 +1640,23 @@ final class ComposePollScreenComponent: Component { let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((environment.navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { - self.addSubview(titleView) + component.overNavigationContainer.addSubview(titleView) } transition.setFrame(view: titleView, frame: titleFrame) } - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let cancelButtonSize = self.cancelButton.update( transition: transition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: environment.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -1681,7 +1672,7 @@ final class ComposePollScreenComponent: Component { let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize) if let cancelButtonView = self.cancelButton.view { if cancelButtonView.superview == nil { - self.addSubview(cancelButtonView) + component.overNavigationContainer.addSubview(cancelButtonView) } transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) } @@ -1717,7 +1708,7 @@ final class ComposePollScreenComponent: Component { let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize) if let doneButtonView = self.doneButton.view { if doneButtonView.superview == nil { - self.addSubview(doneButtonView) + component.overNavigationContainer.addSubview(doneButtonView) } transition.setFrame(view: doneButtonView, frame: doneButtonFrame) } @@ -1798,6 +1789,8 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont fileprivate let completion: (ComposedPoll) -> Void private var isDismissed: Bool = false + private let overNavigationContainer: UIView + fileprivate private(set) var sendButtonItem: UIBarButtonItem? public var isMinimized: Bool = false @@ -1842,13 +1835,16 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont self.context = context self.completion = completion + self.overNavigationContainer = SparseContainerView() + super.init(context: context, component: ComposePollScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, peer: peer, isQuiz: isQuiz, initialData: initialData, completion: completion - ), navigationBarAppearance: .transparent, theme: .default) + ), navigationBarAppearance: .default, theme: .default) self._hasGlassStyle = true @@ -1883,6 +1879,10 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont return componentView.attemptNavigation(complete: complete) } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/ContactListUI/BUILD b/submodules/ContactListUI/BUILD index 99168662..67211ef0 100644 --- a/submodules/ContactListUI/BUILD +++ b/submodules/ContactListUI/BUILD @@ -22,7 +22,6 @@ swift_library( "//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader", "//submodules/ItemListPeerItem:ItemListPeerItem", "//submodules/ContactsPeerItem:ContactsPeerItem", - "//submodules/ChatListSearchItemNode:ChatListSearchItemNode", "//submodules/TelegramPermissionsUI:TelegramPermissionsUI", "//submodules/TelegramNotices:TelegramNotices", "//submodules/AlertUI:AlertUI", @@ -48,6 +47,7 @@ swift_library( "//submodules/ContextUI", "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/SearchInputPanelComponent", + "//submodules/SearchBarNode", ], visibility = [ "//visibility:public", diff --git a/submodules/ContactListUI/Sources/ContactAddItem.swift b/submodules/ContactListUI/Sources/ContactAddItem.swift index 22d6856c..550001c4 100644 --- a/submodules/ContactListUI/Sources/ContactAddItem.swift +++ b/submodules/ContactListUI/Sources/ContactAddItem.swift @@ -129,7 +129,7 @@ class ContactsAddItemNode: ListViewItemNode { self.iconNode = ASImageNode() self.titleNode = TextNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.separatorNode) diff --git a/submodules/ContactListUI/Sources/ContactListActionItem.swift b/submodules/ContactListUI/Sources/ContactListActionItem.swift index b6dcf22b..3b25b455 100644 --- a/submodules/ContactListUI/Sources/ContactListActionItem.swift +++ b/submodules/ContactListUI/Sources/ContactListActionItem.swift @@ -181,7 +181,7 @@ class ContactListActionItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.iconNode) self.addSubnode(self.titleNode) diff --git a/submodules/ContactListUI/Sources/ContactListNameIndexHeader.swift b/submodules/ContactListUI/Sources/ContactListNameIndexHeader.swift index 9fd3a49d..a693ff97 100644 --- a/submodules/ContactListUI/Sources/ContactListNameIndexHeader.swift +++ b/submodules/ContactListUI/Sources/ContactListNameIndexHeader.swift @@ -11,6 +11,7 @@ final class ContactListNameIndexHeader: Equatable, ListViewItemHeader { let letter: unichar let stickDirection: ListViewItemHeaderStickDirection = .top public let stickOverInsets: Bool = true + public let isSticky: Bool = false let height: CGFloat = 29.0 diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 9dc1ed23..ed179a82 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -16,7 +16,6 @@ import AccountContext import TelegramPermissions import TelegramNotices import ContactsPeerItem -import ChatListSearchItemNode import ChatListSearchItemHeader import SearchUI import TelegramPermissionsUI @@ -96,7 +95,6 @@ private enum ContactListNodeEntry: Comparable, Identifiable { var hasUnseenCloseFriends: Bool } - case search(PresentationTheme, PresentationStrings) case sort(PresentationTheme, PresentationStrings, ContactsSortOrder) case permissionInfo(PresentationTheme, String, String, Bool) case permissionEnable(PresentationTheme, String) @@ -107,8 +105,6 @@ private enum ContactListNodeEntry: Comparable, Identifiable { var stableId: ContactListNodeEntryId { switch self { - case .search: - return .search case .sort: return .sort case .permissionInfo: @@ -133,10 +129,6 @@ private enum ContactListNodeEntry: Comparable, Identifiable { func item(context: AccountContext, presentationData: PresentationData, interaction: ContactListNodeInteraction, isSearch: Bool, listStyle: ItemListStyle) -> ListViewItem { switch self { - case let .search(theme, strings): - return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: { - interaction.activateSearch() - }) case let .sort(_, strings, sortOrder): var text = strings.Contacts_SortedByName if case .presence = sortOrder { @@ -270,12 +262,6 @@ private enum ContactListNodeEntry: Comparable, Identifiable { static func ==(lhs: ContactListNodeEntry, rhs: ContactListNodeEntry) -> Bool { switch lhs { - case let .search(lhsTheme, lhsStrings): - if case let .search(rhsTheme, rhsStrings) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings { - return true - } else { - return false - } case let .sort(lhsTheme, lhsStrings, lhsSortOrder): if case let .sort(rhsTheme, rhsStrings, rhsSortOrder) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsSortOrder == rhsSortOrder { return true @@ -376,39 +362,35 @@ private enum ContactListNodeEntry: Comparable, Identifiable { static func <(lhs: ContactListNodeEntry, rhs: ContactListNodeEntry) -> Bool { switch lhs { - case .search: - return true case .sort: switch rhs { - case .search: - return false default: return true } case .permissionInfo: switch rhs { - case .search, .sort: + case .sort: return false default: return true } case .permissionEnable: switch rhs { - case .search, .sort, .permissionInfo: + case .sort, .permissionInfo: return false default: return true } case .permissionLimited: switch rhs { - case .search, .sort, .permissionInfo, .permissionEnable: + case .sort, .permissionInfo, .permissionEnable: return false default: return true } case let .option(lhsIndex, _, _, _, _): switch rhs { - case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited: + case .sort, .permissionInfo, .permissionEnable, .permissionLimited: return false case let .option(rhsIndex, _, _, _, _): return lhsIndex < rhsIndex @@ -417,14 +399,14 @@ private enum ContactListNodeEntry: Comparable, Identifiable { } case let .header(lhsIndex, _): switch rhs { - case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited, .option: + case .sort, .permissionInfo, .permissionEnable, .permissionLimited, .option: return false case let .header(rhsIndex, _), let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return lhsIndex < rhsIndex } case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): switch rhs { - case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited, .option: + case .sort, .permissionInfo, .permissionEnable, .permissionLimited, .option: return false case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _), let .header(rhsIndex, _): // if (lhsStoryData == nil) != (rhsStoryData == nil) { @@ -901,9 +883,6 @@ private func preparedContactListNodeTransition(context: AccountContext, presenta switch entry { case .sort: shouldFixScroll = true - case .search: - //indexSections.apend(CollectionIndexNode.searchIndex) - break case let .peer(_, _, _, header, _, _, _, _, _, _, _, _, _, _, _, _): if let header = header as? ContactListNameIndexHeader { if !existingSections.contains(header.letter) { @@ -1255,7 +1234,6 @@ public final class ContactListNode: ASDisplayNode { self.presentationData = presentationData self.listNode = ListView() - self.listNode.dynamicBounceEnabled = false self.listNode.accessibilityPageScrolledString = { row, count in return presentationData.strings.VoiceOver_ScrollStatus(row, count).string } @@ -1419,11 +1397,6 @@ public final class ContactListNode: ASDisplayNode { var peerIndex = 0 loop: for entry in entries { switch entry { - case .search: - if section == CollectionIndexNode.searchIndex { - strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.PreferSynchronousDrawing, .PreferSynchronousResourceLoading], scrollToItem: ListViewScrollToItem(index: index, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: nil), directionHint: .Down), additionalScrollDistance: 0.0, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - break loop - } case let .peer(_, _, _, header, _, _, _, _, _, _, _, _, _, _, _, _): if let header = header as? ContactListNameIndexHeader { if let scalar = UnicodeScalar(header.letter) { @@ -2124,8 +2097,6 @@ public final class ContactListNode: ASDisplayNode { strongSelf.authorizationNode.isHidden = authorizationPreviousHidden strongSelf.addSubnode(strongSelf.authorizationNode) - strongSelf.listNode.dynamicBounceEnabled = false - strongSelf.listNode.forEachAccessoryItemNode({ accessoryItemNode in if let accessoryItemNode = accessoryItemNode as? ContactsSectionHeaderAccessoryItemNode { accessoryItemNode.updateTheme(theme: presentationData.theme) diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 425ca2b7..293c10db 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -24,6 +24,7 @@ import ChatListHeaderComponent import TelegramIntents import UndoUI import ShareController +import SearchBarNode private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController @@ -141,7 +142,6 @@ public class ContactsController: ViewController { self.sortButton = SortHeaderButton(presentationData: self.presentationData) - //super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) super.init(navigationBarPresentationData: nil) self.tabBarItemContextActionType = .always @@ -226,6 +226,8 @@ public class ContactsController: ViewController { } self.sortButton.addTarget(self, action: #selector(self.sortPressed), forControlEvents: .touchUpInside) + + self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .immediate) } required public init(coder aDecoder: NSCoder) { @@ -242,7 +244,6 @@ public class ContactsController: ViewController { private func updateThemeAndStrings() { self.sortButton.update(theme: self.presentationData.theme, strings: self.presentationData.strings) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) self.title = self.presentationData.strings.Contacts_Title self.tabBarItem.title = self.presentationData.strings.Contacts_Title @@ -280,8 +281,6 @@ public class ContactsController: ViewController { |> take(1) |> map { _ -> Bool in true }) - self.contactsNode.navigationBar = self.navigationBar - let openPeer: (ContactListPeer, Bool) -> Void = { [weak self] peer, fromSearch in if let strongSelf = self { switch peer { @@ -343,7 +342,7 @@ public class ContactsController: ViewController { } self.contactsNode.contactListNode.activateSearch = { [weak self] in - self?.activateSearch() + self?.activateSearch(isFromTabBar: false) } self.contactsNode.contactListNode.openPeer = { [weak self] peer, _, _, _ in @@ -580,18 +579,26 @@ public class ContactsController: ViewController { self.sortButton.contextAction?(self.sortButton.containerNode, nil) } - private func activateSearch() { - if let searchContentNode = self.searchContentNode() { - self.contactsNode.activateSearch(placeholderNode: searchContentNode.placeholderNode) + private func activateSearch(isFromTabBar: Bool) { + let placeholderNode = isFromTabBar ? nil : self.searchContentNode()?.placeholderNode + self.contactsNode.activateSearch(placeholderNode: placeholderNode) + if placeholderNode != nil { + (self.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.5, curve: .spring)) + } else { + self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: true), transition: .animated(duration: 0.5, curve: .spring)) + if let searchBarNode = self.currentTabBarSearchNode?() as? SearchBarNode { + self.contactsNode.searchDisplayController?.setSearchBar(searchBarNode) + searchBarNode.activate() + } } self.requestLayout(transition: .animated(duration: 0.5, curve: .spring)) } private func deactivateSearch(animated: Bool) { - if let searchContentNode = self.searchContentNode() { - self.contactsNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated) - self.requestLayout(transition: .animated(duration: 0.5, curve: .spring)) - } + self.contactsNode.deactivateSearch(placeholderNode: self.searchContentNode()?.placeholderNode, animated: animated) + self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .animated(duration: 0.5, curve: .spring)) + (self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.5, curve: .spring)) + self.requestLayout(transition: .animated(duration: 0.5, curve: .spring)) } func presentSortMenu(sourceView: UIView, gesture: ContextGesture?) { @@ -796,6 +803,14 @@ public class ContactsController: ViewController { let controller = ContextController(presentationData: self.presentationData, source: .reference(ContactsTabBarContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) } + + override public func tabBarActivateSearch() { + self.activateSearch(isFromTabBar: true) + } + + override public func tabBarDeactivateSearch() { + self.deactivateSearch(animated: true) + } } private final class ContactsTabBarContextReferenceContentSource: ContextReferenceContentSource { diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 2718f81f..cb9b316c 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -53,12 +53,11 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { private let context: AccountContext private(set) var searchDisplayController: SearchDisplayController? - private var isSearchDisplayControllerActive: Bool = false + private var isSearchDisplayControllerActive: ChatListNavigationBar.ActiveSearch? private var storiesUnlocked: Bool = false private var containerLayout: (ContainerViewLayout, CGFloat)? - var navigationBar: NavigationBar? let navigationBarView = ComponentView() var requestDeactivateSearch: (() -> Void)? @@ -350,7 +349,6 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { chatListTitle: NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil), leftButton: leftButton, rightButtons: rightButtons, - backTitle: nil, backPressed: nil ) @@ -362,14 +360,15 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { strings: self.presentationData.strings, statusBarHeight: layout.statusBarHeight ?? 0.0, sideInset: layout.safeInsets.left, - isSearchActive: self.isSearchDisplayControllerActive, - isSearchEnabled: true, + search: ChatListNavigationBar.Search(isEnabled: true), + activeSearch: self.isSearchDisplayControllerActive, primaryContent: primaryContent, secondaryContent: nil, secondaryTransition: 0.0, storySubscriptions: nil, storiesIncludeHidden: true, uploadProgress: [:], + headerPanels: nil, tabsNode: tabsNode, tabsNodeIsSearch: tabsNodeIsSearch, accessoryPanelContainer: nil, @@ -416,7 +415,7 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { private func updateNavigationScrolling(transition: ContainedViewLayoutTransition) { var offset = self.getEffectiveNavigationScrollingOffset() - if self.isSearchDisplayControllerActive { + if self.isSearchDisplayControllerActive != nil { offset = 0.0 } @@ -478,15 +477,15 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { } } - func activateSearch(placeholderNode: SearchBarPlaceholderNode) { + func activateSearch(placeholderNode: SearchBarPlaceholderNode?) { guard let (containerLayout, navigationBarHeight) = self.containerLayout, self.searchDisplayController == nil else { return } - self.isSearchDisplayControllerActive = true + self.isSearchDisplayControllerActive = ChatListNavigationBar.ActiveSearch(isExternal: placeholderNode == nil) self.storiesUnlocked = false - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global, .deviceContacts], addContact: { [weak self] phoneNumber in + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .navigation, contentNode: ContactsSearchContainerNode(context: self.context, glass: true, externalSearchBar: true, onlyWriteable: false, categories: [.cloudContacts, .global, .deviceContacts], addContact: { [weak self] phoneNumber in if let requestAddContact = self?.requestAddContact { requestAddContact(phoneNumber) } @@ -504,14 +503,14 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } - }) + }, fieldStyle: placeholderNode?.fieldStyle ?? .modern, searchBarIsExternal: placeholderNode == nil) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in if let strongSelf = self { if isSearchBar { if let navigationBarComponentView = strongSelf.navigationBarView.view as? ChatListNavigationBar.View { - navigationBarComponentView.addSubnode(subnode) + navigationBarComponentView.searchContentNode?.addSubnode(subnode) } } else { strongSelf.insertSubnode(subnode, aboveSubnode: strongSelf.contactListNode) @@ -520,16 +519,11 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { }, placeholder: placeholderNode) } - func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) { - self.isSearchDisplayControllerActive = false + func deactivateSearch(placeholderNode: SearchBarPlaceholderNode?, animated: Bool) { + self.isSearchDisplayControllerActive = nil if let searchDisplayController = self.searchDisplayController { - let previousFrame = placeholderNode.frame - placeholderNode.frame = previousFrame.offsetBy(dx: 0.0, dy: 54.0) - searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated) self.searchDisplayController = nil - - placeholderNode.frame = previousFrame } } } diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index 67f534a4..c033bca5 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -232,6 +232,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo private let context: AccountContext private let glass: Bool + private let externalSearchBar: Bool private let isPeerEnabled: (ContactListPeer) -> Bool private let addContact: ((String) -> Void)? private let openPeer: (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void @@ -265,6 +266,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo public init( context: AccountContext, glass: Bool = false, + externalSearchBar: Bool = false, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, onlyWriteable: Bool, categories: ContactsSearchCategories, @@ -278,6 +280,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo ) { self.context = context self.glass = glass + self.externalSearchBar = externalSearchBar self.isPeerEnabled = isPeerEnabled self.addContact = addContact self.openPeer = openPeer @@ -290,7 +293,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = glass ? .clear : UIColor.black.withAlphaComponent(0.5) + self.dimNode.backgroundColor = .clear self.backgroundNode = ASDisplayNode() self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor @@ -686,12 +689,26 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo self.containerViewLayout = (layout, navigationBarHeight) let topInset = navigationBarHeight - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))) + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) self.backgroundNode.frame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight)) - self.listNode.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)) - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.listNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)) + let listDuration: Double + let listCurve: ListViewAnimationCurve + switch transition { + case .immediate: + listDuration = 0.0 + listCurve = .Default(duration: nil) + case let .animated(duration, curve): + listDuration = duration + if case .spring = curve { + listCurve = .Spring(duration: duration) + } else { + listCurve = .Default(duration: nil) + } + } + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), duration: listDuration, curve: listCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) let size = layout.size let sideInset = layout.safeInsets.left @@ -714,7 +731,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize)) self.emptyResultsAnimationNode.updateLayout(size: self.emptyResultsAnimationSize) - if self.glass { + if self.glass && !self.externalSearchBar { let searchInputSize = self.searchInput.update( transition: .immediate, component: AnyComponent( @@ -794,14 +811,14 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) } - let containerTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) - containerTransition.updateAlpha(node: strongSelf.listNode, alpha: isSearching ? 1.0 : 0.0) - containerTransition.updateAlpha(node: strongSelf.backgroundNode, alpha: isSearching ? 1.0 : 0.0) + //let containerTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) + ContainedViewLayoutTransition.immediate.updateAlpha(node: strongSelf.listNode, alpha: isSearching ? 1.0 : 0.0) + ContainedViewLayoutTransition.immediate.updateAlpha(node: strongSelf.backgroundNode, alpha: isSearching ? 1.0 : 0.0) strongSelf.dimNode.isHidden = isSearching - containerTransition.updateAlpha(node: strongSelf.emptyResultsAnimationNode, alpha: emptyResults ? 1.0 : 0.0) - containerTransition.updateAlpha(node: strongSelf.emptyResultsTitleNode, alpha: emptyResults ? 1.0 : 0.0) - containerTransition.updateAlpha(node: strongSelf.emptyResultsTextNode, alpha: emptyResults ? 1.0 : 0.0) + ContainedViewLayoutTransition.immediate.updateAlpha(node: strongSelf.emptyResultsAnimationNode, alpha: emptyResults ? 1.0 : 0.0) + ContainedViewLayoutTransition.immediate.updateAlpha(node: strongSelf.emptyResultsTitleNode, alpha: emptyResults ? 1.0 : 0.0) + ContainedViewLayoutTransition.immediate.updateAlpha(node: strongSelf.emptyResultsTextNode, alpha: emptyResults ? 1.0 : 0.0) strongSelf.emptyResultsAnimationNode.visibility = emptyResults }) } diff --git a/submodules/ContactListUI/Sources/InviteContactsController.swift b/submodules/ContactListUI/Sources/InviteContactsController.swift index 00c58c2d..5d42a4a0 100644 --- a/submodules/ContactListUI/Sources/InviteContactsController.swift +++ b/submodules/ContactListUI/Sources/InviteContactsController.swift @@ -36,8 +36,9 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + self._hasGlassStyle = true self.navigationPresentation = .modal self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -86,7 +87,7 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) self.title = self.presentationData.strings.Contacts_InviteFriends self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) diff --git a/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift b/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift index 57124b8f..c75a190a 100644 --- a/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/InviteContactsControllerNode.swift @@ -518,7 +518,7 @@ final class InviteContactsControllerNode: ASDisplayNode { if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in diff --git a/submodules/ContactListUI/Sources/LimitedPermissionItem.swift b/submodules/ContactListUI/Sources/LimitedPermissionItem.swift index c9923da2..0af140e6 100644 --- a/submodules/ContactListUI/Sources/LimitedPermissionItem.swift +++ b/submodules/ContactListUI/Sources/LimitedPermissionItem.swift @@ -103,7 +103,7 @@ public class LimitedPermissionItemNode: ListViewItemNode { self.actionButtonTitleNode = TextNode() self.actionButtonTitleNode.isUserInteractionEnabled = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.textNode) self.addSubnode(self.activateArea) diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index 3e446f63..6b9dded5 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -199,6 +199,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { let searchQuery: String? let isAd: Bool let alwaysShowLastSeparator: Bool + let hideBackground: Bool let action: ((ContactsPeerItemPeer) -> Void)? let disabledAction: ((ContactsPeerItemPeer) -> Void)? let setPeerIdWithRevealedOptions: ((EnginePeer.Id?, EnginePeer.Id?) -> Void)? @@ -247,6 +248,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { searchQuery: String? = nil, isAd: Bool = false, alwaysShowLastSeparator: Bool = false, + hideBackground: Bool = false, action: ((ContactsPeerItemPeer) -> Void)?, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((EnginePeer.Id?, EnginePeer.Id?) -> Void)? = nil, @@ -285,6 +287,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { self.searchQuery = searchQuery self.isAd = isAd self.alwaysShowLastSeparator = alwaysShowLastSeparator + self.hideBackground = hideBackground self.action = action self.disabledAction = disabledAction self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions @@ -584,7 +587,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { self.titleNode = TextNode() self.statusNode = TextNodeWithEntities() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true @@ -1325,9 +1328,13 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { case .plain: strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor - strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor + if !item.hideBackground { + strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor + } case .blocks: - strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor + if !item.hideBackground { + strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor + } strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor } @@ -1857,7 +1864,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil - let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight + var topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight + topHighlightInset -= nodeLayout.insets.top strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) 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)) diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index dbf82367..2bb3470f 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -346,6 +346,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking } let titleColor: UIColor + let linkColor = presentationData.theme.list.itemAccentColor switch self.item.textColor { case .primary: titleColor = presentationData.theme.contextMenu.primaryColor @@ -365,6 +366,8 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking return ChatTextInputStateTextAttribute(type: .bold, range: entity.range) } else if case .Italic = entity.type { return ChatTextInputStateTextAttribute(type: .italic, range: entity.range) + } else if case .Url = entity.type { + return ChatTextInputStateTextAttribute(type: .textUrl(""), range: entity.range) } return nil }) @@ -375,9 +378,12 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking ], range: NSRange(location: 0, length: result.length)) for attribute in inputStateText.attributes { if case .bold = attribute.type { - result.addAttribute(NSAttributedString.Key.font, value: Font.semibold(presentationData.listsFontSize.baseDisplaySize), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) + result.addAttribute(NSAttributedString.Key.font, value: titleBoldFont, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) } else if case .italic = attribute.type { result.addAttribute(NSAttributedString.Key.font, value: Font.semibold(15.0), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) + } else if case .textUrl = attribute.type { + result.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) + result.addAttribute(NSAttributedString.Key.font, value: titleBoldFont, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) } } attributedText = result diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift index f2dfd41c..3b3cdc1f 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift @@ -142,7 +142,7 @@ private final class AuthorizationSequenceCountrySelectionNavigationContentNode: self.cancel = cancel - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme), strings: strings, fieldStyle: .modern) + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme), presentationTheme: theme, strings: strings, fieldStyle: .modern) let placeholderText = strings.Common_Search let searchBarFont = Font.regular(17.0) @@ -169,10 +169,12 @@ private final class AuthorizationSequenceCountrySelectionNavigationContentNode: return 54.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { @@ -332,7 +334,7 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll self.displayCodes = displayCodes self.glass = glass - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme, hideBackground: glass, hideSeparator: glass), strings: NavigationBarStrings(presentationStrings: strings))) + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme, hideBackground: glass, hideSeparator: glass, style: glass ? .glass : .legacy), strings: NavigationBarStrings(presentationStrings: strings))) self._hasGlassStyle = glass @@ -392,13 +394,13 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll id: "close", component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: self.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: self.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -413,13 +415,13 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll id: "search", component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "search", component: AnyComponent( BundleIconComponent( name: "Navigation/Search", - tintColor: self.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: self.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -441,15 +443,17 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll self.closeButtonNode = closeButtonNode self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: closeButtonNode) } - - let searchButtonNode: BarComponentHostNode - if let current = self.searchButtonNode { - searchButtonNode = current - searchButtonNode.component = searchComponent - } else { - searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize) - self.searchButtonNode = searchButtonNode - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode) + + if !self.glass { + let searchButtonNode: BarComponentHostNode + if let current = self.searchButtonNode { + searchButtonNode = current + searchButtonNode.component = searchComponent + } else { + searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize) + self.searchButtonNode = searchButtonNode + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode) + } } } diff --git a/submodules/Display/Source/ImmediateTextNode.swift b/submodules/Display/Source/ImmediateTextNode.swift index 8851e6c2..61a6d4e2 100644 --- a/submodules/Display/Source/ImmediateTextNode.swift +++ b/submodules/Display/Source/ImmediateTextNode.swift @@ -13,7 +13,7 @@ public struct ImmediateTextNodeLayoutInfo { } } -public class ImmediateTextNode: TextNode { +open class ImmediateTextNode: TextNode { public var attributedText: NSAttributedString? public var textAlignment: NSTextAlignment = .natural public var verticalAlignment: TextVerticalAlignment = .top @@ -60,7 +60,7 @@ public class ImmediateTextNode: TextNode { public var trailingLineWidth: CGFloat? - var constrainedSize: CGSize? + public var constrainedSize: CGSize? public var highlightAttributeAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? { didSet { @@ -94,7 +94,7 @@ public class ImmediateTextNode: TextNode { return node } - public func updateLayout(_ constrainedSize: CGSize) -> CGSize { + open func updateLayout(_ constrainedSize: CGSize) -> CGSize { self.constrainedSize = constrainedSize let makeLayout = TextNode.asyncLayout(self) diff --git a/submodules/Display/Source/KeyShortcutsController.swift b/submodules/Display/Source/KeyShortcutsController.swift index c8156afb..e8afab89 100644 --- a/submodules/Display/Source/KeyShortcutsController.swift +++ b/submodules/Display/Source/KeyShortcutsController.swift @@ -9,11 +9,7 @@ public class KeyShortcutsController: UIResponder { private var viewControllerEnumerator: (@escaping (ContainableController) -> Bool) -> Void public static var isAvailable: Bool { - if #available(iOSApplicationExtension 8.0, iOS 8.0, *), UIDevice.current.userInterfaceIdiom == .pad { - return true - } else { - return false - } + return true } public init(enumerator: @escaping (@escaping (ContainableController) -> Bool) -> Void) { diff --git a/submodules/Display/Source/LinkHighlightingNode.swift b/submodules/Display/Source/LinkHighlightingNode.swift index 22e128e0..99fa88c0 100644 --- a/submodules/Display/Source/LinkHighlightingNode.swift +++ b/submodules/Display/Source/LinkHighlightingNode.swift @@ -30,6 +30,232 @@ private func drawFullCorner(context: CGContext, color: UIColor, at point: CGPoin } } +private func drawRectsImageContent(size: CGSize, context: CGContext, color: UIColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat, stroke: Bool, strokeWidth: CGFloat, useModernPathCalculation: Bool, topLeft: CGPoint) { + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + + context.setBlendMode(.copy) + + if useModernPathCalculation { + if rects.count == 1 { + let path = UIBezierPath(roundedRect: rects[0].offsetBy(dx: -topLeft.x, dy: -topLeft.y), cornerRadius: outerRadius).cgPath + context.addPath(path) + + if stroke { + context.setStrokeColor(color.cgColor) + context.setLineWidth(strokeWidth) + context.strokePath() + } else { + context.fillPath() + } + return + } + + var combinedRects: [[CGRect]] = [] + var currentRects: [CGRect] = [] + for rect in rects { + if rect.width.isZero { + if !currentRects.isEmpty { + combinedRects.append(currentRects) + } + currentRects.removeAll() + } else { + currentRects.append(rect) + } + } + if !currentRects.isEmpty { + combinedRects.append(currentRects) + } + + for rects in combinedRects { + var rects = rects.map { $0.insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) } + + let minRadius: CGFloat = 2.0 + + for _ in 0 ..< rects.count * rects.count { + var hadChanges = false + for i in 0 ..< rects.count - 1 { + if rects[i].maxY > rects[i + 1].minY { + let midY = floor((rects[i].maxY + rects[i + 1].minY) * 0.5) + rects[i].size.height = midY - rects[i].minY + rects[i + 1].origin.y = midY + rects[i + 1].size.height = rects[i + 1].maxY - midY + hadChanges = true + } + if rects[i].maxY >= rects[i + 1].minY && rects[i].insetBy(dx: 0.0, dy: 1.0).intersects(rects[i + 1]) { + if abs(rects[i].minX - rects[i + 1].minX) < minRadius { + let commonMinX = min(rects[i].origin.x, rects[i + 1].origin.x) + if rects[i].origin.x != commonMinX { + rects[i].origin.x = commonMinX + hadChanges = true + } + if rects[i + 1].origin.x != commonMinX { + rects[i + 1].origin.x = commonMinX + hadChanges = true + } + } + if abs(rects[i].maxX - rects[i + 1].maxX) < minRadius { + let commonMaxX = max(rects[i].maxX, rects[i + 1].maxX) + if rects[i].maxX != commonMaxX { + rects[i].size.width = commonMaxX - rects[i].minX + hadChanges = true + } + if rects[i + 1].maxX != commonMaxX { + rects[i + 1].size.width = commonMaxX - rects[i + 1].minX + hadChanges = true + } + } + } + } + if !hadChanges { + break + } + } + + context.move(to: CGPoint(x: rects[0].midX, y: rects[0].minY)) + context.addLine(to: CGPoint(x: rects[0].maxX - outerRadius, y: rects[0].minY)) + context.addArc(tangent1End: rects[0].topRight, tangent2End: CGPoint(x: rects[0].maxX, y: rects[0].minY + outerRadius), radius: outerRadius) + context.addLine(to: CGPoint(x: rects[0].maxX, y: rects[0].midY)) + + for i in 0 ..< rects.count - 1 { + let rect = rects[i] + let next = rects[i + 1] + + if rect.maxX == next.maxX { + context.addLine(to: CGPoint(x: next.maxX, y: next.midY)) + } else { + let nextRadius = min(outerRadius, floor(abs(rect.maxX - next.maxX) * 0.5)) + context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - nextRadius)) + if next.maxX > rect.maxX { + context.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY), tangent2End: CGPoint(x: rect.maxX + nextRadius, y: rect.maxY), radius: nextRadius) + context.addLine(to: CGPoint(x: next.maxX - nextRadius, y: next.minY)) + } else { + context.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY), tangent2End: CGPoint(x: rect.maxX - nextRadius, y: rect.maxY), radius: nextRadius) + context.addLine(to: CGPoint(x: next.maxX + nextRadius, y: next.minY)) + } + context.addArc(tangent1End: next.topRight, tangent2End: CGPoint(x: next.maxX, y: next.minY + nextRadius), radius: nextRadius) + context.addLine(to: CGPoint(x: next.maxX, y: next.midY)) + } + } + + let last = rects[rects.count - 1] + context.addLine(to: CGPoint(x: last.maxX, y: last.maxY - outerRadius)) + context.addArc(tangent1End: last.bottomRight, tangent2End: CGPoint(x: last.maxX - outerRadius, y: last.maxY), radius: outerRadius) + context.addLine(to: CGPoint(x: last.minX + outerRadius, y: last.maxY)) + context.addArc(tangent1End: last.bottomLeft, tangent2End: CGPoint(x: last.minX, y: last.maxY - outerRadius), radius: outerRadius) + + for i in (1 ..< rects.count).reversed() { + let rect = rects[i] + let prev = rects[i - 1] + + if rect.minX == prev.minX { + context.addLine(to: CGPoint(x: prev.minX, y: prev.midY)) + } else { + let prevRadius = min(outerRadius, floor(abs(rect.minX - prev.minX) * 0.5)) + context.addLine(to: CGPoint(x: rect.minX, y: rect.minY + prevRadius)) + if rect.minX < prev.minX { + context.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY), tangent2End: CGPoint(x: rect.minX + prevRadius, y: rect.minY), radius: prevRadius) + context.addLine(to: CGPoint(x: prev.minX - prevRadius, y: prev.maxY)) + } else { + context.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY), tangent2End: CGPoint(x: rect.minX - prevRadius, y: rect.minY), radius: prevRadius) + context.addLine(to: CGPoint(x: prev.minX + prevRadius, y: prev.maxY)) + } + context.addArc(tangent1End: prev.bottomLeft, tangent2End: CGPoint(x: prev.minX, y: prev.maxY - prevRadius), radius: prevRadius) + context.addLine(to: CGPoint(x: prev.minX, y: prev.midY)) + } + } + + context.addLine(to: CGPoint(x: rects[0].minX, y: rects[0].minY + outerRadius)) + context.addArc(tangent1End: rects[0].topLeft, tangent2End: CGPoint(x: rects[0].minX + outerRadius, y: rects[0].minY), radius: outerRadius) + context.addLine(to: CGPoint(x: rects[0].midX, y: rects[0].minY)) + + if stroke { + context.setStrokeColor(color.cgColor) + context.setLineWidth(strokeWidth) + context.strokePath() + } else { + context.fillPath() + } + } + return + } + + for i in 0 ..< rects.count { + let rect = rects[i].insetBy(dx: -inset, dy: -inset) + context.fill(rect.offsetBy(dx: -topLeft.x, dy: -topLeft.y)) + } + + for i in 0 ..< rects.count { + let rect = rects[i].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + + var previous: CGRect? + if i != 0 { + previous = rects[i - 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + } + + var next: CGRect? + if i != rects.count - 1 { + next = rects[i + 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + } + + if let previous = previous { + if previous.contains(rect.topLeft) { + if abs(rect.topLeft.x - previous.minX) >= innerRadius { + var radius = innerRadius + if let next = next { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topLeft.x, y: previous.maxY), type: .topLeft, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) + } + if previous.contains(rect.topRight.offsetBy(dx: -1.0, dy: 0.0)) { + if abs(rect.topRight.x - previous.maxX) >= innerRadius { + var radius = innerRadius + if let next = next { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topRight.x, y: previous.maxY), type: .topRight, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) + drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) + } + + if let next = next { + if next.contains(rect.bottomLeft) { + if abs(rect.bottomRight.x - next.maxX) >= innerRadius { + var radius = innerRadius + if let previous = previous { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomLeft.x, y: next.minY), type: .bottomLeft, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) + } + if next.contains(rect.bottomRight.offsetBy(dx: -1.0, dy: 0.0)) { + if abs(rect.bottomRight.x - next.maxX) >= innerRadius { + var radius = innerRadius + if let previous = previous { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomRight.x, y: next.minY), type: .bottomRight, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) + } + } +} + private func drawConnectingCorner(context: CGContext, color: UIColor, at point: CGPoint, type: CornerType, radius: CGFloat) { context.setFillColor(color.cgColor) switch type { @@ -76,230 +302,9 @@ public func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, bottomRight.x += drawingInset * 2.0 bottomRight.y += drawingInset * 2.0 + let capturedTopLeft = topLeft return (topLeft, generateImage(CGSize(width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(color.cgColor) - - context.setBlendMode(.copy) - - if useModernPathCalculation { - if rects.count == 1 { - let path = UIBezierPath(roundedRect: rects[0].offsetBy(dx: -topLeft.x, dy: -topLeft.y), cornerRadius: outerRadius).cgPath - context.addPath(path) - - if stroke { - context.setStrokeColor(color.cgColor) - context.setLineWidth(strokeWidth) - context.strokePath() - } else { - context.fillPath() - } - return - } - - var combinedRects: [[CGRect]] = [] - var currentRects: [CGRect] = [] - for rect in rects { - if rect.width.isZero { - if !currentRects.isEmpty { - combinedRects.append(currentRects) - } - currentRects.removeAll() - } else { - currentRects.append(rect) - } - } - if !currentRects.isEmpty { - combinedRects.append(currentRects) - } - - for rects in combinedRects { - var rects = rects.map { $0.insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) } - - let minRadius: CGFloat = 2.0 - - for _ in 0 ..< rects.count * rects.count { - var hadChanges = false - for i in 0 ..< rects.count - 1 { - if rects[i].maxY > rects[i + 1].minY { - let midY = floor((rects[i].maxY + rects[i + 1].minY) * 0.5) - rects[i].size.height = midY - rects[i].minY - rects[i + 1].origin.y = midY - rects[i + 1].size.height = rects[i + 1].maxY - midY - hadChanges = true - } - if rects[i].maxY >= rects[i + 1].minY && rects[i].insetBy(dx: 0.0, dy: 1.0).intersects(rects[i + 1]) { - if abs(rects[i].minX - rects[i + 1].minX) < minRadius { - let commonMinX = min(rects[i].origin.x, rects[i + 1].origin.x) - if rects[i].origin.x != commonMinX { - rects[i].origin.x = commonMinX - hadChanges = true - } - if rects[i + 1].origin.x != commonMinX { - rects[i + 1].origin.x = commonMinX - hadChanges = true - } - } - if abs(rects[i].maxX - rects[i + 1].maxX) < minRadius { - let commonMaxX = max(rects[i].maxX, rects[i + 1].maxX) - if rects[i].maxX != commonMaxX { - rects[i].size.width = commonMaxX - rects[i].minX - hadChanges = true - } - if rects[i + 1].maxX != commonMaxX { - rects[i + 1].size.width = commonMaxX - rects[i + 1].minX - hadChanges = true - } - } - } - } - if !hadChanges { - break - } - } - - context.move(to: CGPoint(x: rects[0].midX, y: rects[0].minY)) - context.addLine(to: CGPoint(x: rects[0].maxX - outerRadius, y: rects[0].minY)) - context.addArc(tangent1End: rects[0].topRight, tangent2End: CGPoint(x: rects[0].maxX, y: rects[0].minY + outerRadius), radius: outerRadius) - context.addLine(to: CGPoint(x: rects[0].maxX, y: rects[0].midY)) - - for i in 0 ..< rects.count - 1 { - let rect = rects[i] - let next = rects[i + 1] - - if rect.maxX == next.maxX { - context.addLine(to: CGPoint(x: next.maxX, y: next.midY)) - } else { - let nextRadius = min(outerRadius, floor(abs(rect.maxX - next.maxX) * 0.5)) - context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - nextRadius)) - if next.maxX > rect.maxX { - context.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY), tangent2End: CGPoint(x: rect.maxX + nextRadius, y: rect.maxY), radius: nextRadius) - context.addLine(to: CGPoint(x: next.maxX - nextRadius, y: next.minY)) - } else { - context.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY), tangent2End: CGPoint(x: rect.maxX - nextRadius, y: rect.maxY), radius: nextRadius) - context.addLine(to: CGPoint(x: next.maxX + nextRadius, y: next.minY)) - } - context.addArc(tangent1End: next.topRight, tangent2End: CGPoint(x: next.maxX, y: next.minY + nextRadius), radius: nextRadius) - context.addLine(to: CGPoint(x: next.maxX, y: next.midY)) - } - } - - let last = rects[rects.count - 1] - context.addLine(to: CGPoint(x: last.maxX, y: last.maxY - outerRadius)) - context.addArc(tangent1End: last.bottomRight, tangent2End: CGPoint(x: last.maxX - outerRadius, y: last.maxY), radius: outerRadius) - context.addLine(to: CGPoint(x: last.minX + outerRadius, y: last.maxY)) - context.addArc(tangent1End: last.bottomLeft, tangent2End: CGPoint(x: last.minX, y: last.maxY - outerRadius), radius: outerRadius) - - for i in (1 ..< rects.count).reversed() { - let rect = rects[i] - let prev = rects[i - 1] - - if rect.minX == prev.minX { - context.addLine(to: CGPoint(x: prev.minX, y: prev.midY)) - } else { - let prevRadius = min(outerRadius, floor(abs(rect.minX - prev.minX) * 0.5)) - context.addLine(to: CGPoint(x: rect.minX, y: rect.minY + prevRadius)) - if rect.minX < prev.minX { - context.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY), tangent2End: CGPoint(x: rect.minX + prevRadius, y: rect.minY), radius: prevRadius) - context.addLine(to: CGPoint(x: prev.minX - prevRadius, y: prev.maxY)) - } else { - context.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY), tangent2End: CGPoint(x: rect.minX - prevRadius, y: rect.minY), radius: prevRadius) - context.addLine(to: CGPoint(x: prev.minX + prevRadius, y: prev.maxY)) - } - context.addArc(tangent1End: prev.bottomLeft, tangent2End: CGPoint(x: prev.minX, y: prev.maxY - prevRadius), radius: prevRadius) - context.addLine(to: CGPoint(x: prev.minX, y: prev.midY)) - } - } - - context.addLine(to: CGPoint(x: rects[0].minX, y: rects[0].minY + outerRadius)) - context.addArc(tangent1End: rects[0].topLeft, tangent2End: CGPoint(x: rects[0].minX + outerRadius, y: rects[0].minY), radius: outerRadius) - context.addLine(to: CGPoint(x: rects[0].midX, y: rects[0].minY)) - - if stroke { - context.setStrokeColor(color.cgColor) - context.setLineWidth(strokeWidth) - context.strokePath() - } else { - context.fillPath() - } - } - return - } - - for i in 0 ..< rects.count { - let rect = rects[i].insetBy(dx: -inset, dy: -inset) - context.fill(rect.offsetBy(dx: -topLeft.x, dy: -topLeft.y)) - } - - for i in 0 ..< rects.count { - let rect = rects[i].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) - - var previous: CGRect? - if i != 0 { - previous = rects[i - 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) - } - - var next: CGRect? - if i != rects.count - 1 { - next = rects[i + 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) - } - - if let previous = previous { - if previous.contains(rect.topLeft) { - if abs(rect.topLeft.x - previous.minX) >= innerRadius { - var radius = innerRadius - if let next = next { - radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) - } - drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topLeft.x, y: previous.maxY), type: .topLeft, radius: radius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) - } - if previous.contains(rect.topRight.offsetBy(dx: -1.0, dy: 0.0)) { - if abs(rect.topRight.x - previous.maxX) >= innerRadius { - var radius = innerRadius - if let next = next { - radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) - } - drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topRight.x, y: previous.maxY), type: .topRight, radius: radius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) - drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) - } - - if let next = next { - if next.contains(rect.bottomLeft) { - if abs(rect.bottomRight.x - next.maxX) >= innerRadius { - var radius = innerRadius - if let previous = previous { - radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) - } - drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomLeft.x, y: next.minY), type: .bottomLeft, radius: radius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) - } - if next.contains(rect.bottomRight.offsetBy(dx: -1.0, dy: 0.0)) { - if abs(rect.bottomRight.x - next.maxX) >= innerRadius { - var radius = innerRadius - if let previous = previous { - radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) - } - drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomRight.x, y: next.minY), type: .bottomRight, radius: radius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) - } - } else { - drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) - drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) - } - } + drawRectsImageContent(size: size, context: context, color: color, rects: rects, inset: inset, outerRadius: outerRadius, innerRadius: innerRadius, stroke: stroke, strokeWidth: strokeWidth, useModernPathCalculation: useModernPathCalculation, topLeft: capturedTopLeft) })) } diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index bf3f593a..a1ab459d 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -210,7 +210,6 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel private final var displayLink: CADisplayLink! private final var needsAnimations = false - public final var dynamicBounceEnabled = true public final var rotated = false public final var experimentalSnapScrollToItem = false public final var useMainQueueTransactions = false @@ -265,6 +264,9 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel public final var addContentOffset: ((CGFloat, ListViewItemNode?) -> Void)? public final var shouldStopScrolling: ((CGFloat) -> Bool)? public final var onContentsUpdated: ((ContainedViewLayoutTransition) -> Void)? + + public private(set) final var edgeEffectExtension: CGFloat = 0.0 + public final var onEdgeEffectExtensionUpdated: ((ContainedViewLayoutTransition) -> Void)? public final var updateScrollingIndicator: ((ScrollingIndicatorState?, ContainedViewLayoutTransition) -> Void)? @@ -1044,41 +1046,10 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel self.enqueueUpdateVisibleItems(synchronous: false) } - var useScrollDynamics = false - - let anchor: CGFloat - if self.isTracking { - anchor = self.touchesPosition.y - } else if deltaY < 0.0 { - anchor = self.visibleSize.height - } else { - anchor = 0.0 - } - self.didScrollWithOffset?(deltaY, .immediate, nil, self.isTrackingOrDecelerating) for itemNode in self.itemNodes { itemNode.updateFrame(itemNode.frame.offsetBy(dx: 0.0, dy: -deltaY), within: self.visibleSize) - - if self.dynamicBounceEnabled && itemNode.wantsScrollDynamics { - useScrollDynamics = true - - var distance: CGFloat - let itemFrame = itemNode.apparentFrame - if anchor < itemFrame.origin.y { - distance = abs(itemFrame.origin.y - anchor) - } else if anchor > itemFrame.origin.y + itemFrame.size.height { - distance = abs(anchor - (itemFrame.origin.y + itemFrame.size.height)) - } else { - distance = 0.0 - } - - let factor: CGFloat = max(0.08, abs(distance) / self.visibleSize.height) - - let resistance: CGFloat = testSpringFreeResistance - - itemNode.addScrollingOffset(deltaY * factor * resistance) - } } if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom, insetDeltaOffsetFix: 0.0).offset.isZero { @@ -1088,38 +1059,10 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel self.updateItemHeaders(leftInset: self.insets.left, rightInset: self.insets.right, synchronousLoad: false) - for (_, headerNode) in self.itemHeaderNodes { - if self.dynamicBounceEnabled && headerNode.wantsScrollDynamics { - useScrollDynamics = true - - var distance: CGFloat - let itemFrame = headerNode.frame - if anchor < itemFrame.origin.y { - distance = abs(itemFrame.origin.y - anchor) - } else if anchor > itemFrame.origin.y + itemFrame.size.height { - distance = abs(anchor - (itemFrame.origin.y + itemFrame.size.height)) - } else { - distance = 0.0 - } - - let factor: CGFloat = max(0.08, abs(distance) / self.visibleSize.height) - - let resistance: CGFloat = testSpringFreeResistance - - headerNode.addScrollingOffset(deltaY * factor * resistance) - } - } - - if useScrollDynamics { - self.setNeedsAnimations() - } - self.updateVisibleContentOffset() self.updateVisibleItemRange() self.updateItemNodesVisibilities(onlyPositive: false) self.onContentsUpdated?(.immediate) - - //CATransaction.commit() } private func calculateAdditionalTopInverseInset() -> CGFloat { @@ -3914,6 +3857,8 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel let flashing = self.headerItemsAreFlashing() + var maxEdgeEffectExtension: CGFloat = 0.0 + func addHeader(id: VisibleHeaderNodeId, upperBound: CGFloat, upperIndex: Int, upperBoundEdge: CGFloat, lowerBound: CGFloat, lowerIndex: Int, item: ListViewItemHeader, hasValidNodes: Bool) { let itemHeaderHeight: CGFloat = item.height @@ -3928,7 +3873,11 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel switch item.stickDirection { case .top: naturalY = lowerBound - headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBound), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight)) + if item.isSticky { + headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBound), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight)) + } else { + headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(upperBound, lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight)) + } stickLocationDistance = headerFrame.minY - upperBound stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight)) case .topEdge: @@ -4096,6 +4045,11 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel } headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, distance: stickLocationDistance, transition: .immediate) } + + if headerNode.contributesToEdgeEffect && stickLocationDistance > 0.0 { + maxEdgeEffectExtension = max(maxEdgeEffectExtension, upperDisplayBound + headerFrame.height + 8.0) + } + headerNode.offsetByHeaderNodeId = offsetByHeaderNodeId headerNode.naturalOriginY = naturalY var maxIntersectionHeight: (CGFloat, Int)? @@ -4228,6 +4182,11 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel } } } + + if self.edgeEffectExtension != maxEdgeEffectExtension { + self.edgeEffectExtension = maxEdgeEffectExtension + self.onEdgeEffectExtensionUpdated?(transition.0) + } } private func updateItemNodesVisibilities(onlyPositive: Bool) { diff --git a/submodules/Display/Source/ListViewItemHeader.swift b/submodules/Display/Source/ListViewItemHeader.swift index 98a43c73..92bb2f08 100644 --- a/submodules/Display/Source/ListViewItemHeader.swift +++ b/submodules/Display/Source/ListViewItemHeader.swift @@ -12,6 +12,7 @@ public protocol ListViewItemHeader: AnyObject { var id: ListViewItemNode.HeaderId { get } var stackingId: ListViewItemNode.HeaderId? { get } var stickDirection: ListViewItemHeaderStickDirection { get } + var isSticky: Bool { get } var height: CGFloat { get } var stickOverInsets: Bool { get } @@ -21,14 +22,19 @@ public protocol ListViewItemHeader: AnyObject { func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) } +public extension ListViewItemHeader { + var isSticky: Bool { + return true + } +} + open class ListViewItemHeaderNode: ASDisplayNode { - private final var spring: ListViewItemSpring? - let wantsScrollDynamics: Bool let isRotated: Bool final private(set) var internalStickLocationDistanceFactor: CGFloat = 0.0 final var internalStickLocationDistance: CGFloat = 0.0 private var isFlashingOnScrolling = false weak var attachedToItemNode: ListViewItemNode? + public var contributesToEdgeEffect: Bool = false var offsetByHeaderNodeId: ListViewItemNode.HeaderId? var naturalOriginY: CGFloat? @@ -53,12 +59,8 @@ open class ListViewItemHeaderNode: ASDisplayNode { return self.alpha } - public init(layerBacked: Bool = false, dynamicBounce: Bool = false, isRotated: Bool = false, seeThrough: Bool = false) { - self.wantsScrollDynamics = dynamicBounce + public init(layerBacked: Bool = false, isRotated: Bool = false, seeThrough: Bool = false) { self.isRotated = isRotated - if dynamicBounce { - self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) - } super.init() @@ -69,54 +71,10 @@ open class ListViewItemHeaderNode: ASDisplayNode { } final func addScrollingOffset(_ scrollingOffset: CGFloat) { - if self.spring != nil && internalStickLocationDistanceFactor.isZero { - let bounds = self.bounds - self.bounds = CGRect(origin: CGPoint(x: 0.0, y: bounds.origin.y + scrollingOffset), size: bounds.size) - } } public func animate(_ timestamp: Double) -> Bool { - var continueAnimations = false - - if let _ = self.spring { - let bounds = self.bounds - var offset = bounds.origin.y - let currentOffset = offset - - let frictionConstant: CGFloat = testSpringFriction - let springConstant: CGFloat = testSpringConstant - let time: CGFloat = 1.0 / 60.0 - - // friction force = velocity * friction constant - let frictionForce = self.spring!.velocity * frictionConstant - // spring force = (target point - current position) * spring constant - let springForce = -currentOffset * springConstant - // force = spring force - friction force - let force = springForce - frictionForce - - // velocity = current velocity + force * time / mass - self.spring!.velocity = self.spring!.velocity + force * time - // position = current position + velocity * time - offset = currentOffset + self.spring!.velocity * time - - offset = offset.isNaN ? 0.0 : offset - - let epsilon: CGFloat = 0.1 - if abs(offset) < epsilon && abs(self.spring!.velocity) < epsilon { - offset = 0.0 - self.spring!.velocity = 0.0 - } else { - continueAnimations = true - } - - if abs(offset) > 250.0 { - offset = offset < 0.0 ? -250.0 : 250.0 - } - - self.bounds = CGRect(origin: CGPoint(x: 0.0, y: offset), size: bounds.size) - } - - return continueAnimations + return false } open func animateRemoved(duration: Double) { diff --git a/submodules/Display/Source/ListViewItemNode.swift b/submodules/Display/Source/ListViewItemNode.swift index 42c6a995..360e0791 100644 --- a/submodules/Display/Source/ListViewItemNode.swift +++ b/submodules/Display/Source/ListViewItemNode.swift @@ -129,7 +129,6 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode { return nil } - private final var spring: ListViewItemSpring? private final var animations: [(String, ListViewAnimation)] = [] private final var pendingControlledTransitions: [ControlledTransition] = [] private final var controlledTransitions: [ControlledTransitionContext] = [] @@ -142,8 +141,6 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode { open func attachedHeaderNodesUpdated() { } - final let wantsScrollDynamics: Bool - open var preferredAnimationCurve: (CGFloat) -> CGFloat { return listViewAnimationCurveSystem } @@ -236,12 +233,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode { return .complete() } - public init(layerBacked: Bool, dynamicBounce: Bool = true, rotated: Bool = false, seeThrough: Bool = false) { - if dynamicBounce { - self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) - } - self.wantsScrollDynamics = dynamicBounce - + public init(layerBacked: Bool, rotated: Bool = false, seeThrough: Bool = false) { self.rotated = rotated super.init() @@ -338,56 +330,14 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode { } final func addScrollingOffset(_ scrollingOffset: CGFloat) { - if self.spring != nil { - self.contentOffset += scrollingOffset - } } func initializeDynamicsFromSibling(_ itemView: ListViewItemNode, additionalOffset: CGFloat) { - if let itemViewSpring = itemView.spring { - self.contentOffset = itemView.contentOffset + additionalOffset - self.spring?.velocity = itemViewSpring.velocity - } } public func animate(timestamp: Double, invertOffsetDirection: inout Bool) -> Bool { var continueAnimations = false - if let _ = self.spring { - var offset = self.contentOffset - - let frictionConstant: CGFloat = testSpringFriction - let springConstant: CGFloat = testSpringConstant - let time: CGFloat = 1.0 / 60.0 - - // friction force = velocity * friction constant - let frictionForce = self.spring!.velocity * frictionConstant - // spring force = (target point - current position) * spring constant - let springForce = -self.contentOffset * springConstant - // force = spring force - friction force - let force = springForce - frictionForce - - // velocity = current velocity + force * time / mass - self.spring!.velocity = self.spring!.velocity + force * time - // position = current position + velocity * time - offset = self.contentOffset + self.spring!.velocity * time - - offset = offset.isNaN ? 0.0 : offset - - let epsilon: CGFloat = 0.1 - if abs(offset) < epsilon && abs(self.spring!.velocity) < epsilon { - offset = 0.0 - self.spring!.velocity = 0.0 - } else { - continueAnimations = true - } - - if abs(offset) > 250.0 { - offset = offset < 0.0 ? -250.0 : 250.0 - } - self.contentOffset = offset - } - var i = 0 var animationCount = self.animations.count while i < animationCount { diff --git a/submodules/Display/Source/Navigation/NavigationModalFrame.swift b/submodules/Display/Source/Navigation/NavigationModalFrame.swift index 2120a419..91c05d66 100644 --- a/submodules/Display/Source/Navigation/NavigationModalFrame.swift +++ b/submodules/Display/Source/Navigation/NavigationModalFrame.swift @@ -109,7 +109,7 @@ public final class NavigationModalFrame: ASDisplayNode { let contentScale = (layout.size.width - sideInset * 2.0) / layout.size.width let bottomInset: CGFloat = layout.size.height - contentScale * layout.size.height - topInset - let cornerRadius: CGFloat = 28.0 + let cornerRadius: CGFloat = 38.0 let initialCornerRadius: CGFloat if !layout.safeInsets.top.isZero { initialCornerRadius = layout.deviceMetrics.screenCornerRadius diff --git a/submodules/Display/Source/NavigationBackgroundView.swift b/submodules/Display/Source/NavigationBackgroundView.swift new file mode 100644 index 00000000..2fff1fe1 --- /dev/null +++ b/submodules/Display/Source/NavigationBackgroundView.swift @@ -0,0 +1,332 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +private var sharedIsReduceTransparencyEnabled = UIAccessibility.isReduceTransparencyEnabled + +public final class NavigationBackgroundNode: ASDisplayNode { + private var _color: UIColor + + public var color: UIColor { + return self._color + } + + private var enableBlur: Bool + private var enableSaturation: Bool + private var customBlurRadius: CGFloat? + + public var effectView: UIVisualEffectView? + private let backgroundNode: ASDisplayNode + + public var backgroundView: UIView { + return self.backgroundNode.view + } + + private var validLayout: (CGSize, CGFloat)? + + public var backgroundCornerRadius: CGFloat { + if let (_, cornerRadius) = self.validLayout { + return cornerRadius + } else { + return 0.0 + } + } + + public init(color: UIColor, enableBlur: Bool = true, enableSaturation: Bool = true, customBlurRadius: CGFloat? = nil) { + self._color = .clear + self.enableBlur = enableBlur + self.enableSaturation = enableSaturation + self.customBlurRadius = customBlurRadius + + self.backgroundNode = ASDisplayNode() + + super.init() + + self.addSubnode(self.backgroundNode) + + self.updateColor(color: color, transition: .immediate) + } + + + public override func didLoad() { + super.didLoad() + + if self.scheduledUpdate { + self.scheduledUpdate = false + self.updateBackgroundBlur(forceKeepBlur: false) + } + } + + private var scheduledUpdate = false + + private func updateBackgroundBlur(forceKeepBlur: Bool) { + guard self.isNodeLoaded else { + self.scheduledUpdate = true + return + } + if self.enableBlur && !sharedIsReduceTransparencyEnabled && ((self._color.alpha > .ulpOfOne && self._color.alpha < 0.95) || forceKeepBlur) { + if self.effectView == nil { + let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + + for subview in effectView.subviews { + if subview.description.contains("VisualEffectSubview") { + subview.isHidden = true + } + } + + if let sublayer = effectView.layer.sublayers?[0], let filters = sublayer.filters { + sublayer.backgroundColor = nil + sublayer.isOpaque = false + var allowedKeys: [String] = [ + "gaussianBlur" + ] + if self.enableSaturation { + allowedKeys.append("colorSaturate") + } + sublayer.filters = filters.filter { filter in + guard let filter = filter as? NSObject else { + return true + } + let filterName = String(describing: filter) + if !allowedKeys.contains(filterName) { + return false + } + if let customBlurRadius = self.customBlurRadius, filterName == "gaussianBlur" { + filter.setValue(customBlurRadius as NSNumber, forKey: "inputRadius") + } + return true + } + } + + if let (size, cornerRadius) = self.validLayout { + effectView.frame = CGRect(origin: CGPoint(), size: size) + ContainedViewLayoutTransition.immediate.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius) + effectView.clipsToBounds = !cornerRadius.isZero + } + self.effectView = effectView + self.view.insertSubview(effectView, at: 0) + } + } else if let effectView = self.effectView { + self.effectView = nil + effectView.removeFromSuperview() + } + } + + public func updateColor(color: UIColor, enableBlur: Bool? = nil, enableSaturation: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) { + let effectiveEnableBlur = enableBlur ?? self.enableBlur + let effectiveEnableSaturation = enableSaturation ?? self.enableSaturation + + if self._color.isEqual(color) && self.enableBlur == effectiveEnableBlur && self.enableSaturation == effectiveEnableSaturation { + return + } + self._color = color + self.enableBlur = effectiveEnableBlur + self.enableSaturation = effectiveEnableSaturation + + if sharedIsReduceTransparencyEnabled { + transition.updateBackgroundColor(node: self.backgroundNode, color: self._color.withAlphaComponent(1.0)) + } else { + transition.updateBackgroundColor(node: self.backgroundNode, color: self._color) + } + + self.updateBackgroundBlur(forceKeepBlur: forceKeepBlur) + } + + public func update(size: CGSize, cornerRadius: CGFloat = 0.0, transition: ContainedViewLayoutTransition, beginWithCurrentState: Bool = true) { + self.validLayout = (size, cornerRadius) + + let contentFrame = CGRect(origin: CGPoint(), size: size) + transition.updateFrame(node: self.backgroundNode, frame: contentFrame, beginWithCurrentState: true) + if let effectView = self.effectView, effectView.frame != contentFrame { + transition.updateFrame(layer: effectView.layer, frame: contentFrame, beginWithCurrentState: true) + if let sublayers = effectView.layer.sublayers { + for sublayer in sublayers { + transition.updateFrame(layer: sublayer, frame: contentFrame, beginWithCurrentState: true) + } + } + } + + transition.updateCornerRadius(node: self.backgroundNode, cornerRadius: cornerRadius) + if let effectView = self.effectView { + transition.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius) + effectView.clipsToBounds = !cornerRadius.isZero + } + } + + public func update(size: CGSize, cornerRadius: CGFloat = 0.0, animator: ControlledTransitionAnimator) { + self.validLayout = (size, cornerRadius) + + let contentFrame = CGRect(origin: CGPoint(), size: size) + animator.updateFrame(layer: self.backgroundNode.layer, frame: contentFrame, completion: nil) + if let effectView = self.effectView, effectView.frame != contentFrame { + animator.updateFrame(layer: effectView.layer, frame: contentFrame, completion: nil) + if let sublayers = effectView.layer.sublayers { + for sublayer in sublayers { + animator.updateFrame(layer: sublayer, frame: contentFrame, completion: nil) + } + } + } + + animator.updateCornerRadius(layer: self.backgroundNode.layer, cornerRadius: cornerRadius, completion: nil) + if let effectView = self.effectView { + animator.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius, completion: nil) + effectView.clipsToBounds = !cornerRadius.isZero + } + } +} + +open class BlurredBackgroundView: UIView { + private var _color: UIColor? + + private var enableBlur: Bool + private var customBlurRadius: CGFloat? + + public private(set) var effectView: UIVisualEffectView? + private let backgroundView: UIView + + private var validLayout: (CGSize, CGFloat)? + + public var backgroundCornerRadius: CGFloat { + if let (_, cornerRadius) = self.validLayout { + return cornerRadius + } else { + return 0.0 + } + } + + public init(color: UIColor?, enableBlur: Bool = true, customBlurRadius: CGFloat? = nil) { + self._color = nil + self.enableBlur = enableBlur + self.customBlurRadius = customBlurRadius + + self.backgroundView = UIView() + + super.init(frame: CGRect()) + + self.addSubview(self.backgroundView) + + if let color = color { + self.updateColor(color: color, transition: .immediate) + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateBackgroundBlur(forceKeepBlur: Bool) { + if let color = self._color, self.enableBlur && !sharedIsReduceTransparencyEnabled && ((color.alpha > .ulpOfOne && color.alpha < 0.95) || forceKeepBlur) { + if self.effectView == nil { + let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + + for subview in effectView.subviews { + if subview.description.contains("VisualEffectSubview") { + subview.isHidden = true + } + } + + if let sublayer = effectView.layer.sublayers?[0], let filters = sublayer.filters { + sublayer.backgroundColor = nil + sublayer.isOpaque = false + //sublayer.setValue(true as NSNumber, forKey: "allowsInPlaceFiltering") + let allowedKeys: [String] = [ + "colorSaturate", + "gaussianBlur" + ] + sublayer.filters = filters.filter { filter in + guard let filter = filter as? NSObject else { + return true + } + let filterName = String(describing: filter) + if !allowedKeys.contains(filterName) { + return false + } + if let customBlurRadius = self.customBlurRadius, filterName == "gaussianBlur" { + filter.setValue(customBlurRadius as NSNumber, forKey: "inputRadius") + } + return true + } + } + + if let (size, cornerRadius) = self.validLayout { + effectView.frame = CGRect(origin: CGPoint(), size: size) + ContainedViewLayoutTransition.immediate.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius) + effectView.clipsToBounds = !cornerRadius.isZero + } + self.effectView = effectView + self.insertSubview(effectView, at: 0) + } + } else if let effectView = self.effectView { + self.effectView = nil + effectView.removeFromSuperview() + } + } + + public func updateColor(color: UIColor, enableBlur: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) { + let effectiveEnableBlur = enableBlur ?? self.enableBlur + + if self._color == color && self.enableBlur == effectiveEnableBlur { + return + } + self._color = color + self.enableBlur = effectiveEnableBlur + + if sharedIsReduceTransparencyEnabled { + transition.updateBackgroundColor(layer: self.backgroundView.layer, color: color.withAlphaComponent(1.0)) + } else { + transition.updateBackgroundColor(layer: self.backgroundView.layer, color: color) + } + + self.updateBackgroundBlur(forceKeepBlur: forceKeepBlur) + } + + public func update(size: CGSize, cornerRadius: CGFloat = 0.0, maskedCorners: CACornerMask = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner], transition: ContainedViewLayoutTransition) { + self.validLayout = (size, cornerRadius) + + let contentFrame = CGRect(origin: CGPoint(), size: size) + transition.updateFrame(view: self.backgroundView, frame: contentFrame, beginWithCurrentState: true) + if let effectView = self.effectView, effectView.frame != contentFrame { + transition.updateFrame(layer: effectView.layer, frame: contentFrame, beginWithCurrentState: true) + if let sublayers = effectView.layer.sublayers { + for sublayer in sublayers { + transition.updateFrame(layer: sublayer, frame: contentFrame, beginWithCurrentState: true) + } + } + } + + if #available(iOS 11.0, *) { + self.backgroundView.layer.maskedCorners = maskedCorners + } + + transition.updateCornerRadius(layer: self.backgroundView.layer, cornerRadius: cornerRadius) + if let effectView = self.effectView { + transition.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius) + effectView.clipsToBounds = !cornerRadius.isZero + + if #available(iOS 11.0, *) { + effectView.layer.maskedCorners = maskedCorners + } + } + } + + public func update(size: CGSize, cornerRadius: CGFloat = 0.0, animator: ControlledTransitionAnimator) { + self.validLayout = (size, cornerRadius) + + let contentFrame = CGRect(origin: CGPoint(), size: size) + animator.updateFrame(layer: self.backgroundView.layer, frame: contentFrame, completion: nil) + if let effectView = self.effectView, effectView.frame != contentFrame { + animator.updateFrame(layer: effectView.layer, frame: contentFrame, completion: nil) + if let sublayers = effectView.layer.sublayers { + for sublayer in sublayers { + animator.updateFrame(layer: sublayer, frame: contentFrame, completion: nil) + } + } + } + + animator.updateCornerRadius(layer: self.backgroundView.layer, cornerRadius: cornerRadius, completion: nil) + if let effectView = self.effectView { + animator.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius, completion: nil) + effectView.clipsToBounds = !cornerRadius.isZero + } + } +} diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 03f4f718..67ad9cb6 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -4,50 +4,6 @@ import SwiftSignalKit private var backArrowImageCache: [Int32: UIImage] = [:] -open class SparseNode: ASDisplayNode { - override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.alpha.isZero { - return nil - } - for view in self.view.subviews.reversed() { - if let result = view.hitTest(self.view.convert(point, to: view), with: event), result.isUserInteractionEnabled { - return result - } - } - - if !self.bounds.inset(by: self.hitTestSlop).contains(point) { - return nil - } - - let result = super.hitTest(point, with: event) - if result != self.view { - return result - } else { - return nil - } - } -} - -open class SparseContainerView: UIView { - override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.alpha.isZero { - return nil - } - for view in self.subviews.reversed() { - if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled { - return result - } - } - - let result = super.hitTest(point, with: event) - if result != self { - return result - } else { - return nil - } - } -} - public final class NavigationBarTheme { public static func generateBackArrowImage(color: UIColor) -> UIImage? { return generateImage(CGSize(width: 13.0, height: 22.0), rotatedContext: { size, context in @@ -60,6 +16,7 @@ public final class NavigationBarTheme { }) } + public let overallDarkAppearance: Bool public let buttonColor: UIColor public let disabledButtonColor: UIColor public let primaryTextColor: UIColor @@ -70,8 +27,11 @@ public final class NavigationBarTheme { public let badgeBackgroundColor: UIColor public let badgeStrokeColor: UIColor public let badgeTextColor: UIColor + public let edgeEffectColor: UIColor? + public let style: NavigationBar.Style - public init(buttonColor: UIColor, disabledButtonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, opaqueBackgroundColor: UIColor? = nil, enableBackgroundBlur: Bool, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor) { + public init(overallDarkAppearance: Bool, buttonColor: UIColor, disabledButtonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, opaqueBackgroundColor: UIColor? = nil, enableBackgroundBlur: Bool, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor, edgeEffectColor: UIColor? = nil, style: NavigationBar.Style = .legacy) { + self.overallDarkAppearance = overallDarkAppearance self.buttonColor = buttonColor self.disabledButtonColor = disabledButtonColor self.primaryTextColor = primaryTextColor @@ -82,14 +42,16 @@ public final class NavigationBarTheme { self.badgeBackgroundColor = badgeBackgroundColor self.badgeStrokeColor = badgeStrokeColor self.badgeTextColor = badgeTextColor + self.edgeEffectColor = edgeEffectColor + self.style = style } public func withUpdatedBackgroundColor(_ color: UIColor) -> NavigationBarTheme { - return NavigationBarTheme(buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: color, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: false, separatorColor: self.separatorColor, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor) + return NavigationBarTheme(overallDarkAppearance: self.overallDarkAppearance, buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: color, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: false, separatorColor: self.separatorColor, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor, edgeEffectColor: self.edgeEffectColor, style: self.style) } public func withUpdatedSeparatorColor(_ color: UIColor) -> NavigationBarTheme { - return NavigationBarTheme(buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: self.enableBackgroundBlur, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor) + return NavigationBarTheme(overallDarkAppearance: self.overallDarkAppearance, buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: self.enableBackgroundBlur, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor, edgeEffectColor: self.edgeEffectColor, style: self.style) } } @@ -135,1700 +97,101 @@ public enum NavigationPreviousAction: Equatable { } } -private var sharedIsReduceTransparencyEnabled = UIAccessibility.isReduceTransparencyEnabled - -public final class NavigationBackgroundNode: ASDisplayNode { - private var _color: UIColor - - public var color: UIColor { - return self._color - } - - private var enableBlur: Bool - private var enableSaturation: Bool - private var customBlurRadius: CGFloat? - - public var effectView: UIVisualEffectView? - private let backgroundNode: ASDisplayNode - - public var backgroundView: UIView { - return self.backgroundNode.view - } - - private var validLayout: (CGSize, CGFloat)? - - public var backgroundCornerRadius: CGFloat { - if let (_, cornerRadius) = self.validLayout { - return cornerRadius - } else { - return 0.0 - } - } - - public init(color: UIColor, enableBlur: Bool = true, enableSaturation: Bool = true, customBlurRadius: CGFloat? = nil) { - self._color = .clear - self.enableBlur = enableBlur - self.enableSaturation = enableSaturation - self.customBlurRadius = customBlurRadius - - self.backgroundNode = ASDisplayNode() - - super.init() - - self.addSubnode(self.backgroundNode) - - self.updateColor(color: color, transition: .immediate) - } - - - public override func didLoad() { - super.didLoad() - - if self.scheduledUpdate { - self.scheduledUpdate = false - self.updateBackgroundBlur(forceKeepBlur: false) - } - } - - private var scheduledUpdate = false - - private func updateBackgroundBlur(forceKeepBlur: Bool) { - guard self.isNodeLoaded else { - self.scheduledUpdate = true - return - } - if self.enableBlur && !sharedIsReduceTransparencyEnabled && ((self._color.alpha > .ulpOfOne && self._color.alpha < 0.95) || forceKeepBlur) { - if self.effectView == nil { - let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) - - for subview in effectView.subviews { - if subview.description.contains("VisualEffectSubview") { - subview.isHidden = true - } - } - - if let sublayer = effectView.layer.sublayers?[0], let filters = sublayer.filters { - sublayer.backgroundColor = nil - sublayer.isOpaque = false - var allowedKeys: [String] = [ - "gaussianBlur" - ] - if self.enableSaturation { - allowedKeys.append("colorSaturate") - } - sublayer.filters = filters.filter { filter in - guard let filter = filter as? NSObject else { - return true - } - let filterName = String(describing: filter) - if !allowedKeys.contains(filterName) { - return false - } - if let customBlurRadius = self.customBlurRadius, filterName == "gaussianBlur" { - filter.setValue(customBlurRadius as NSNumber, forKey: "inputRadius") - } - return true - } - } - - if let (size, cornerRadius) = self.validLayout { - effectView.frame = CGRect(origin: CGPoint(), size: size) - ContainedViewLayoutTransition.immediate.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius) - effectView.clipsToBounds = !cornerRadius.isZero - } - self.effectView = effectView - self.view.insertSubview(effectView, at: 0) - } - } else if let effectView = self.effectView { - self.effectView = nil - effectView.removeFromSuperview() - } - } - - public func updateColor(color: UIColor, enableBlur: Bool? = nil, enableSaturation: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) { - let effectiveEnableBlur = enableBlur ?? self.enableBlur - let effectiveEnableSaturation = enableSaturation ?? self.enableSaturation - - if self._color.isEqual(color) && self.enableBlur == effectiveEnableBlur && self.enableSaturation == effectiveEnableSaturation { - return - } - self._color = color - self.enableBlur = effectiveEnableBlur - self.enableSaturation = effectiveEnableSaturation - - if sharedIsReduceTransparencyEnabled { - transition.updateBackgroundColor(node: self.backgroundNode, color: self._color.withAlphaComponent(1.0)) - } else { - transition.updateBackgroundColor(node: self.backgroundNode, color: self._color) - } - - self.updateBackgroundBlur(forceKeepBlur: forceKeepBlur) - } - - public func update(size: CGSize, cornerRadius: CGFloat = 0.0, transition: ContainedViewLayoutTransition, beginWithCurrentState: Bool = true) { - self.validLayout = (size, cornerRadius) - - let contentFrame = CGRect(origin: CGPoint(), size: size) - transition.updateFrame(node: self.backgroundNode, frame: contentFrame, beginWithCurrentState: true) - if let effectView = self.effectView, effectView.frame != contentFrame { - transition.updateFrame(layer: effectView.layer, frame: contentFrame, beginWithCurrentState: true) - if let sublayers = effectView.layer.sublayers { - for sublayer in sublayers { - transition.updateFrame(layer: sublayer, frame: contentFrame, beginWithCurrentState: true) - } - } - } - - transition.updateCornerRadius(node: self.backgroundNode, cornerRadius: cornerRadius) - if let effectView = self.effectView { - transition.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius) - effectView.clipsToBounds = !cornerRadius.isZero - } - } - - public func update(size: CGSize, cornerRadius: CGFloat = 0.0, animator: ControlledTransitionAnimator) { - self.validLayout = (size, cornerRadius) - - let contentFrame = CGRect(origin: CGPoint(), size: size) - animator.updateFrame(layer: self.backgroundNode.layer, frame: contentFrame, completion: nil) - if let effectView = self.effectView, effectView.frame != contentFrame { - animator.updateFrame(layer: effectView.layer, frame: contentFrame, completion: nil) - if let sublayers = effectView.layer.sublayers { - for sublayer in sublayers { - animator.updateFrame(layer: sublayer, frame: contentFrame, completion: nil) - } - } - } - - animator.updateCornerRadius(layer: self.backgroundNode.layer, cornerRadius: cornerRadius, completion: nil) - if let effectView = self.effectView { - animator.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius, completion: nil) - effectView.clipsToBounds = !cornerRadius.isZero - } - } +public enum NavigationBarStyle { + case legacy + case glass } -open class BlurredBackgroundView: UIView { - private var _color: UIColor? - - private var enableBlur: Bool - private var customBlurRadius: CGFloat? - - public private(set) var effectView: UIVisualEffectView? - private let backgroundView: UIView - - private var validLayout: (CGSize, CGFloat)? +public func navigationBarBackArrowImage(color: UIColor) -> UIImage? { + var red: CGFloat = 0.0 + var green: CGFloat = 0.0 + var blue: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) - public var backgroundCornerRadius: CGFloat { - if let (_, cornerRadius) = self.validLayout { - return cornerRadius - } else { - return 0.0 - } - } - - public init(color: UIColor?, enableBlur: Bool = true, customBlurRadius: CGFloat? = nil) { - self._color = nil - self.enableBlur = enableBlur - self.customBlurRadius = customBlurRadius - - self.backgroundView = UIView() - - super.init(frame: CGRect()) - - self.addSubview(self.backgroundView) - - if let color = color { - self.updateColor(color: color, transition: .immediate) - } - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func updateBackgroundBlur(forceKeepBlur: Bool) { - if let color = self._color, self.enableBlur && !sharedIsReduceTransparencyEnabled && ((color.alpha > .ulpOfOne && color.alpha < 0.95) || forceKeepBlur) { - if self.effectView == nil { - let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) - - for subview in effectView.subviews { - if subview.description.contains("VisualEffectSubview") { - subview.isHidden = true - } - } - - if let sublayer = effectView.layer.sublayers?[0], let filters = sublayer.filters { - sublayer.backgroundColor = nil - sublayer.isOpaque = false - //sublayer.setValue(true as NSNumber, forKey: "allowsInPlaceFiltering") - let allowedKeys: [String] = [ - "colorSaturate", - "gaussianBlur" - ] - sublayer.filters = filters.filter { filter in - guard let filter = filter as? NSObject else { - return true - } - let filterName = String(describing: filter) - if !allowedKeys.contains(filterName) { - return false - } - if let customBlurRadius = self.customBlurRadius, filterName == "gaussianBlur" { - filter.setValue(customBlurRadius as NSNumber, forKey: "inputRadius") - } - return true - } - } - - if let (size, cornerRadius) = self.validLayout { - effectView.frame = CGRect(origin: CGPoint(), size: size) - ContainedViewLayoutTransition.immediate.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius) - effectView.clipsToBounds = !cornerRadius.isZero - } - self.effectView = effectView - self.insertSubview(effectView, at: 0) - } - } else if let effectView = self.effectView { - self.effectView = nil - effectView.removeFromSuperview() - } - } - - public func updateColor(color: UIColor, enableBlur: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) { - let effectiveEnableBlur = enableBlur ?? self.enableBlur - - if self._color == color && self.enableBlur == effectiveEnableBlur { - return - } - self._color = color - self.enableBlur = effectiveEnableBlur - - if sharedIsReduceTransparencyEnabled { - transition.updateBackgroundColor(layer: self.backgroundView.layer, color: color.withAlphaComponent(1.0)) - } else { - transition.updateBackgroundColor(layer: self.backgroundView.layer, color: color) - } - - self.updateBackgroundBlur(forceKeepBlur: forceKeepBlur) - } - - public func update(size: CGSize, cornerRadius: CGFloat = 0.0, maskedCorners: CACornerMask = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner], transition: ContainedViewLayoutTransition) { - self.validLayout = (size, cornerRadius) - - let contentFrame = CGRect(origin: CGPoint(), size: size) - transition.updateFrame(view: self.backgroundView, frame: contentFrame, beginWithCurrentState: true) - if let effectView = self.effectView, effectView.frame != contentFrame { - transition.updateFrame(layer: effectView.layer, frame: contentFrame, beginWithCurrentState: true) - if let sublayers = effectView.layer.sublayers { - for sublayer in sublayers { - transition.updateFrame(layer: sublayer, frame: contentFrame, beginWithCurrentState: true) - } - } - } - - if #available(iOS 11.0, *) { - self.backgroundView.layer.maskedCorners = maskedCorners - } - - transition.updateCornerRadius(layer: self.backgroundView.layer, cornerRadius: cornerRadius) - if let effectView = self.effectView { - transition.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius) - effectView.clipsToBounds = !cornerRadius.isZero - - if #available(iOS 11.0, *) { - effectView.layer.maskedCorners = maskedCorners - } - } - } - - public func update(size: CGSize, cornerRadius: CGFloat = 0.0, animator: ControlledTransitionAnimator) { - self.validLayout = (size, cornerRadius) - - let contentFrame = CGRect(origin: CGPoint(), size: size) - animator.updateFrame(layer: self.backgroundView.layer, frame: contentFrame, completion: nil) - if let effectView = self.effectView, effectView.frame != contentFrame { - animator.updateFrame(layer: effectView.layer, frame: contentFrame, completion: nil) - if let sublayers = effectView.layer.sublayers { - for sublayer in sublayers { - animator.updateFrame(layer: sublayer, frame: contentFrame, completion: nil) - } - } - } - - animator.updateCornerRadius(layer: self.backgroundView.layer, cornerRadius: cornerRadius, completion: nil) - if let effectView = self.effectView { - animator.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius, completion: nil) - effectView.clipsToBounds = !cornerRadius.isZero - } - } -} - -public protocol NavigationBarHeaderView: UIView { -} - -private final class NavigationBackgroundCutoutView: UIView { - private let topLayer: SimpleLayer - private let rightLayer: SimpleLayer - - weak var targetNode: ASDisplayNode? - - override init(frame: CGRect) { - self.topLayer = SimpleLayer() - self.topLayer.backgroundColor = UIColor.white.cgColor - - self.rightLayer = SimpleLayer() - self.rightLayer.backgroundColor = UIColor.white.cgColor - - super.init(frame: frame) - - self.layer.addSublayer(self.topLayer) - self.layer.addSublayer(self.rightLayer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(size: CGSize, cutout: CGSize?, transition: ContainedViewLayoutTransition) { - let cutout = cutout ?? CGSize() - - let topFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: cutout.height)) - let rightFrame = CGRect(origin: CGPoint(x: cutout.width, y: 0.0), size: CGSize(width: max(0.0, size.width - cutout.width), height: size.height)) - - if self.topLayer.frame != topFrame || self.rightLayer.frame != rightFrame { - if cutout.width != 0.0 && cutout.height != 0.0 { - self.targetNode?.view.mask = self - //self.targetNode?.view.addSubview(self) - } - - transition.updateFrame(layer: self.topLayer, frame: topFrame) - transition.updateFrame(layer: self.rightLayer, frame: rightFrame, completion: { [weak self] completed in - guard let self, completed else { - return - } - - if !(cutout.width != 0.0 && cutout.height != 0.0) { - self.targetNode?.view.mask = nil - //self.removeFromSuperview() - } - }) - } - } -} - -open class NavigationBar: ASDisplayNode { - public static var defaultSecondaryContentHeight: CGFloat { - return 38.0 - } - - public static func backArrowImage(color: UIColor) -> UIImage? { - var red: CGFloat = 0.0 - var green: CGFloat = 0.0 - var blue: CGFloat = 0.0 - var alpha: CGFloat = 0.0 - color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) - - let key = (Int32(alpha * 255.0) << 24) | (Int32(red * 255.0) << 16) | (Int32(green * 255.0) << 8) | Int32(blue * 255.0) - if let image = backArrowImageCache[key] { + let key = (Int32(alpha * 255.0) << 24) | (Int32(red * 255.0) << 16) | (Int32(green * 255.0) << 8) | Int32(blue * 255.0) + if let image = backArrowImageCache[key] { + return image + } else { + if let image = NavigationBarTheme.generateBackArrowImage(color: color) { + backArrowImageCache[key] = image return image - } else { - if let image = NavigationBarTheme.generateBackArrowImage(color: color) { - backArrowImageCache[key] = image - return image - } else { - return nil - } - } - } - - public static let thinBackArrowImage = generateTintedImage(image: UIImage(bundleImageName: "Navigation/BackArrow"), color: .white)?.withRenderingMode(.alwaysTemplate) - - public static let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]) - - var presentationData: NavigationBarPresentationData - - private var validLayout: (size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool)? - private var requestedLayout: Bool = false - var requestContainerLayout: (ContainedViewLayoutTransition) -> Void = { _ in } - - public var backPressed: () -> () = { } - - public var userInfo: Any? - public var makeCustomTransitionNode: ((NavigationBar, Bool) -> CustomNavigationTransitionNode?)? - public var allowsCustomTransition: (() -> Bool)? - - public var customSetContentNode: ((NavigationBarContentNode?, Bool) -> Void)? - - private var collapsed: Bool { - get { - return self.frame.size.height.isLess(than: 44.0) - } - } - - public let stripeNode: ASDisplayNode - public let clippingNode: SparseNode - private let buttonsContainerNode: ASDisplayNode - - public private(set) var contentNode: NavigationBarContentNode? - public private(set) var secondaryContentNode: ASDisplayNode? - public var secondaryContentNodeDisplayFraction: CGFloat = 1.0 - - private var itemTitleListenerKey: Int? - private var itemTitleViewListenerKey: Int? - - private var itemLeftButtonListenerKey: Int? - private var itemLeftButtonSetEnabledListenerKey: Int? - - private var itemRightButtonListenerKey: Int? - private var itemRightButtonsListenerKey: Int? - - private var itemBadgeListenerKey: Int? - - private var hintAnimateTitleNodeOnNextLayout: Bool = false - - private var _item: UINavigationItem? - public var item: UINavigationItem? { - get { - return self._item - } set(value) { - if let previousValue = self._item { - if let itemTitleListenerKey = self.itemTitleListenerKey { - previousValue.removeSetTitleListener(itemTitleListenerKey) - self.itemTitleListenerKey = nil - } - - if let itemLeftButtonListenerKey = self.itemLeftButtonListenerKey { - previousValue.removeSetLeftBarButtonItemListener(itemLeftButtonListenerKey) - self.itemLeftButtonListenerKey = nil - } - - if let itemLeftButtonSetEnabledListenerKey = self.itemLeftButtonSetEnabledListenerKey { - previousValue.leftBarButtonItem?.removeSetEnabledListener(itemLeftButtonSetEnabledListenerKey) - self.itemLeftButtonSetEnabledListenerKey = nil - } - - if let itemRightButtonListenerKey = self.itemRightButtonListenerKey { - previousValue.removeSetRightBarButtonItemListener(itemRightButtonListenerKey) - self.itemRightButtonListenerKey = nil - } - - if let itemRightButtonsListenerKey = self.itemRightButtonsListenerKey { - previousValue.removeSetMultipleRightBarButtonItemsListener(itemRightButtonsListenerKey) - self.itemRightButtonsListenerKey = nil - } - - if let itemBadgeListenerKey = self.itemBadgeListenerKey { - previousValue.removeSetBadgeListener(itemBadgeListenerKey) - self.itemBadgeListenerKey = nil - } - } - self._item = value - - self.leftButtonNode.removeFromSupernode() - self.rightButtonNode.removeFromSupernode() - - if let item = value { - self.title = item.title - self.itemTitleListenerKey = item.addSetTitleListener { [weak self] text, animated in - if let strongSelf = self { - let animateIn = animated && (strongSelf.title?.isEmpty ?? true) - strongSelf.title = text - if animateIn { - strongSelf.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - } - } - } - - self.titleView = item.titleView - self.itemTitleViewListenerKey = item.addSetTitleViewListener { [weak self] titleView in - if let strongSelf = self { - strongSelf.titleView = titleView - } - } - - self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] previousItem, _, animated in - if let strongSelf = self { - if let itemLeftButtonSetEnabledListenerKey = strongSelf.itemLeftButtonSetEnabledListenerKey { - previousItem?.removeSetEnabledListener(itemLeftButtonSetEnabledListenerKey) - strongSelf.itemLeftButtonSetEnabledListenerKey = nil - } - - strongSelf.updateLeftButton(animated: animated) - strongSelf.invalidateCalculatedLayout() - strongSelf.requestLayout() - } - } - - self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] previousItem, currentItem, animated in - if let strongSelf = self { - strongSelf.updateRightButton(animated: animated) - strongSelf.invalidateCalculatedLayout() - strongSelf.requestLayout() - } - } - - self.itemRightButtonsListenerKey = item.addSetMultipleRightBarButtonItemsListener { [weak self] items, animated in - if let strongSelf = self { - strongSelf.updateRightButton(animated: animated) - strongSelf.invalidateCalculatedLayout() - strongSelf.requestLayout() - } - } - - self.itemBadgeListenerKey = item.addSetBadgeListener { [weak self] text in - if let strongSelf = self { - strongSelf.updateBadgeText(text: text) - } - } - self.updateBadgeText(text: item.badge) - - self.updateLeftButton(animated: false) - self.updateRightButton(animated: false) - } else { - self.title = nil - self.updateLeftButton(animated: false) - self.updateRightButton(animated: false) - } - self.invalidateCalculatedLayout() - self.requestLayout() - } - } - - public var customBackButtonText: String? - - private var title: String? { - didSet { - if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.primaryTextColor) - self.titleNode.accessibilityLabel = title - if self.titleNode.supernode == nil { - self.buttonsContainerNode.addSubnode(self.titleNode) - } - } else { - self.titleNode.removeFromSupernode() - } - - self.updateAccessibilityElements() - self.invalidateCalculatedLayout() - self.requestLayout() - } - } - - public private(set) var titleView: UIView? { - didSet { - if let oldValue = oldValue { - oldValue.removeFromSuperview() - } - - if let titleView = self.titleView { - self.buttonsContainerNode.view.addSubview(titleView) - } - - self.invalidateCalculatedLayout() - self.requestLayout() - } - } - - public var customHeaderContentView: NavigationBarHeaderView? { - didSet { - if self.customHeaderContentView !== oldValue { - self.customHeaderContentView?.removeFromSuperview() - - if let customHeaderContentView = self.customHeaderContentView { - self.buttonsContainerNode.view.addSubview(customHeaderContentView) - self.backButtonNode.isHidden = true - self.backButtonArrow.isHidden = true - } else { - self.backButtonNode.isHidden = false - self.backButtonArrow.isHidden = false - } - } - } - } - - public var layoutSuspended: Bool = false - - private let titleNode: ImmediateTextNode - - var previousItemListenerKey: Int? - var previousItemBackListenerKey: Int? - - private func updateAccessibilityElements() { - } - - override open var accessibilityElements: [Any]? { - get { - var accessibilityElements: [Any] = [] - if self.backButtonNode.supernode != nil { - addAccessibilityChildren(of: self.backButtonNode, container: self, to: &accessibilityElements) - } - if self.leftButtonNode.supernode != nil { - addAccessibilityChildren(of: self.leftButtonNode, container: self, to: &accessibilityElements) - } - if self.titleNode.supernode != nil { - addAccessibilityChildren(of: self.titleNode, container: self, to: &accessibilityElements) - accessibilityElements.append(self.titleNode) - } - if let titleView = self.titleView, titleView.superview != nil { - titleView.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(titleView.bounds, in: titleView) - accessibilityElements.append(titleView) - } - if self.rightButtonNode.supernode != nil { - addAccessibilityChildren(of: self.rightButtonNode, container: self, to: &accessibilityElements) - } - if let customHeaderContentView = self.customHeaderContentView, customHeaderContentView.superview != nil { - customHeaderContentView.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(customHeaderContentView.bounds, in: customHeaderContentView) - accessibilityElements.append(customHeaderContentView) - } - if let contentNode = self.contentNode { - addAccessibilityChildren(of: contentNode, container: self, to: &accessibilityElements) - } - if let secondaryContentNode = self.secondaryContentNode { - addAccessibilityChildren(of: secondaryContentNode, container: self, to: &accessibilityElements) - } - return accessibilityElements - } set(value) { - } - } - - override open func didLoad() { - super.didLoad() - - self.updateAccessibilityElements() - } - - public var enableAutomaticBackButton: Bool = true - - var _previousItem: NavigationPreviousAction? - public internal(set) var previousItem: NavigationPreviousAction? { - get { - if !self.enableAutomaticBackButton { - return nil - } - return self._previousItem - } set(value) { - if !self.enableAutomaticBackButton { - self._previousItem = nil - return - } - if self._previousItem != value { - if let previousValue = self._previousItem, case let .item(itemValue) = previousValue { - if let previousItemListenerKey = self.previousItemListenerKey { - itemValue.removeSetTitleListener(previousItemListenerKey) - self.previousItemListenerKey = nil - } - if let previousItemBackListenerKey = self.previousItemBackListenerKey { - itemValue.removeSetBackBarButtonItemListener(previousItemBackListenerKey) - self.previousItemBackListenerKey = nil - } - } - self._previousItem = value - - if let previousItem = value { - switch previousItem { - case let .item(itemValue): - self.previousItemListenerKey = itemValue.addSetTitleListener { [weak self] _, _ in - if let strongSelf = self, let previousItem = strongSelf.previousItem, case let .item(itemValue) = previousItem { - if let customBackButtonText = strongSelf.customBackButtonText { - strongSelf.backButtonNode.updateManualText(customBackButtonText, isBack: true) - } else if let backBarButtonItem = itemValue.backBarButtonItem { - strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "", isBack: true) - } else { - strongSelf.backButtonNode.updateManualText(itemValue.title ?? "", isBack: true) - } - strongSelf.invalidateCalculatedLayout() - strongSelf.requestLayout() - } - } - - self.previousItemBackListenerKey = itemValue.addSetBackBarButtonItemListener { [weak self] _, _, _ in - if let strongSelf = self, let previousItem = strongSelf.previousItem, case let .item(itemValue) = previousItem { - if let customBackButtonText = strongSelf.customBackButtonText { - strongSelf.backButtonNode.updateManualText(customBackButtonText, isBack: true) - } else if let backBarButtonItem = itemValue.backBarButtonItem { - strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "", isBack: true) - } else { - strongSelf.backButtonNode.updateManualText(itemValue.title ?? "", isBack: true) - } - strongSelf.invalidateCalculatedLayout() - strongSelf.requestLayout() - } - } - case .close: - break - } - } - self.updateLeftButton(animated: false) - - self.invalidateCalculatedLayout() - self.requestLayout() - } - } - } - - private func updateBadgeText(text: String?) { - let actualText = text ?? "" - if self.badgeNode.text != actualText { - self.badgeNode.text = actualText - self.badgeNode.isHidden = actualText.isEmpty - self.backButtonNode.manualAlpha = self.badgeNode.isHidden ? 1.0 : 0.0 - - self.invalidateCalculatedLayout() - self.requestLayout() - } - } - - private func updateLeftButton(animated: Bool) { - if let item = self.item { - var needsLeftButton = false - if let leftBarButtonItem = item.leftBarButtonItem, !leftBarButtonItem.backButtonAppearance { - needsLeftButton = true - } else if let previousItem = self.previousItem, case .close = previousItem { - needsLeftButton = true - } - - if needsLeftButton { - if animated { - if self.backButtonNode.view.superview != nil { - if let snapshotView = self.backButtonNode.view.snapshotContentTree() { - snapshotView.frame = self.backButtonNode.frame - self.backButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.backButtonNode.view) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - } - - if self.backButtonArrow.view.superview != nil { - if let snapshotView = self.backButtonArrow.view.snapshotContentTree() { - snapshotView.frame = self.backButtonArrow.frame - self.backButtonArrow.view.superview?.insertSubview(snapshotView, aboveSubview: self.backButtonArrow.view) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - } - - if self.badgeNode.view.superview != nil { - if let snapshotView = self.badgeNode.view.snapshotContentTree() { - snapshotView.frame = self.badgeNode.frame - self.badgeNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.badgeNode.view) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - } - } - - self.backButtonNode.removeFromSupernode() - self.backButtonArrow.removeFromSupernode() - self.badgeNode.removeFromSupernode() - - if let leftBarButtonItem = item.leftBarButtonItem { - self.leftButtonNode.updateItems([], animated: animated) - self.leftButtonNode.updateItems([leftBarButtonItem], animated: animated) - } else { - self.leftButtonNode.updateItems([], animated: animated) - self.leftButtonNode.updateItems([UIBarButtonItem(title: self.presentationData.strings.close, style: .plain, target: nil, action: nil)], animated: animated) - } - - if self.leftButtonNode.supernode == nil { - self.buttonsContainerNode.addSubnode(self.leftButtonNode) - } - - if animated { - self.leftButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - } - } else { - if animated, self.leftButtonNode.view.superview != nil { - if let snapshotView = self.leftButtonNode.view.snapshotContentTree() { - snapshotView.frame = self.leftButtonNode.frame - self.leftButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.leftButtonNode.view) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - } - self.leftButtonNode.removeFromSupernode() - - var backTitle: String? - if let customBackButtonText = self.customBackButtonText { - backTitle = customBackButtonText - } else if let leftBarButtonItem = item.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { - backTitle = leftBarButtonItem.title - } else if let previousItem = self.previousItem { - switch previousItem { - case let .item(itemValue): - if let backBarButtonItem = itemValue.backBarButtonItem { - backTitle = backBarButtonItem.title ?? self.presentationData.strings.back - } else { - backTitle = itemValue.title ?? self.presentationData.strings.back - } - case .close: - backTitle = nil - } - } - - if let backTitle = backTitle { - self.backButtonNode.updateManualText(backTitle, isBack: true) - if self.backButtonNode.supernode == nil { - self.buttonsContainerNode.addSubnode(self.backButtonNode) - self.buttonsContainerNode.addSubnode(self.backButtonArrow) - self.buttonsContainerNode.addSubnode(self.badgeNode) - } - - if animated { - self.backButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - self.backButtonArrow.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - self.badgeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - } - } else { - self.backButtonNode.removeFromSupernode() - } - } - } else { - self.leftButtonNode.removeFromSupernode() - self.backButtonNode.removeFromSupernode() - self.backButtonArrow.removeFromSupernode() - self.badgeNode.removeFromSupernode() - } - - self.updateAccessibilityElements() - } - - private func updateRightButton(animated: Bool) { - if let item = self.item { - var items: [UIBarButtonItem] = [] - if let rightBarButtonItems = item.rightBarButtonItems, !rightBarButtonItems.isEmpty { - items = rightBarButtonItems - } else if let rightBarButtonItem = item.rightBarButtonItem { - items = [rightBarButtonItem] - } - - self.rightButtonNodeUpdated = true - - if !items.isEmpty { - self.rightButtonNode.updateItems([], animated: animated) - self.rightButtonNode.updateItems(items, animated: animated) - if self.rightButtonNode.supernode == nil { - self.buttonsContainerNode.addSubnode(self.rightButtonNode) - } - } else { - if animated, self.rightButtonNode.view.superview != nil { - if let snapshotView = self.rightButtonNode.view.snapshotContentTree() { - snapshotView.frame = self.rightButtonNode.frame - self.rightButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.rightButtonNode.view) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - } - self.rightButtonNode.removeFromSupernode() - } - } else { - if animated, self.rightButtonNode.view.superview != nil { - if let snapshotView = self.rightButtonNode.view.snapshotContentTree() { - snapshotView.frame = self.rightButtonNode.frame - self.rightButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.rightButtonNode.view) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - } - self.rightButtonNode.removeFromSupernode() - } - - self.updateAccessibilityElements() - } - - public let backgroundNode: NavigationBackgroundNode - public let backButtonNode: NavigationButtonNode - public let badgeNode: NavigationBarBadgeNode - public let backButtonArrow: ASImageNode - public let leftButtonNode: NavigationButtonNode - public let rightButtonNode: NavigationButtonNode - private var rightButtonNodeUpdated: Bool = false - public let additionalContentNode: SparseNode - - private let navigationBackgroundCutoutView: NavigationBackgroundCutoutView - - public func reattachAdditionalContentNode() { - if self.additionalContentNode.supernode !== self { - self.insertSubnode(self.additionalContentNode, aboveSubnode: self.clippingNode) - } - } - - private var _transitionState: NavigationBarTransitionState? - var transitionState: NavigationBarTransitionState? { - get { - return self._transitionState - } set(value) { - let updateNodes = self._transitionState?.navigationBar !== value?.navigationBar - - self._transitionState = value - - if updateNodes { - if let transitionTitleNode = self.transitionTitleNode { - transitionTitleNode.removeFromSupernode() - self.transitionTitleNode = nil - } - - if let transitionBackButtonNode = self.transitionBackButtonNode { - transitionBackButtonNode.removeFromSupernode() - self.transitionBackButtonNode = nil - } - - if let transitionBackArrowNode = self.transitionBackArrowNode { - transitionBackArrowNode.removeFromSupernode() - self.transitionBackArrowNode = nil - } - - if let transitionBadgeNode = self.transitionBadgeNode { - transitionBadgeNode.removeFromSupernode() - self.transitionBadgeNode = nil - } - - if let value = value { - switch value.role { - case .top: - if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.presentationData.theme.primaryTextColor) { - self.transitionTitleNode = transitionTitleNode - if self.leftButtonNode.supernode != nil { - self.buttonsContainerNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode) - } else if self.backButtonNode.supernode != nil { - self.buttonsContainerNode.insertSubnode(transitionTitleNode, belowSubnode: self.backButtonNode) - } else { - self.buttonsContainerNode.addSubnode(transitionTitleNode) - } - } - case .bottom: - if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.presentationData.theme.buttonColor) { - self.transitionBackButtonNode = transitionBackButtonNode - self.buttonsContainerNode.addSubnode(transitionBackButtonNode) - } - if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.presentationData.theme.buttonColor) { - self.transitionBackArrowNode = transitionBackArrowNode - self.buttonsContainerNode.addSubnode(transitionBackArrowNode) - } - if let transitionBadgeNode = value.navigationBar?.makeTransitionBadgeNode() { - self.transitionBadgeNode = transitionBadgeNode - self.buttonsContainerNode.addSubnode(transitionBadgeNode) - } - } - } - } - - self.requestedLayout = true - self.layout() - } - } - - private var transitionTitleNode: ASDisplayNode? - private var transitionBackButtonNode: NavigationButtonNode? - private var transitionBackArrowNode: ASDisplayNode? - private var transitionBadgeNode: ASDisplayNode? - - public var secondaryContentHeight: CGFloat - - public init(presentationData: NavigationBarPresentationData) { - self.presentationData = presentationData - self.stripeNode = ASDisplayNode() - - self.titleNode = ImmediateTextNode() - self.titleNode.isAccessibilityElement = true - self.titleNode.accessibilityTraits = .header - - self.backButtonNode = NavigationButtonNode() - self.backButtonNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -20.0, bottom: 0.0, right: 0.0) - - self.badgeNode = NavigationBarBadgeNode(fillColor: self.presentationData.theme.buttonColor, strokeColor: self.presentationData.theme.buttonColor, textColor: self.presentationData.theme.badgeTextColor) - self.badgeNode.isUserInteractionEnabled = false - self.badgeNode.isHidden = true - self.backButtonArrow = ASImageNode() - self.backButtonArrow.displayWithoutProcessing = true - self.backButtonArrow.displaysAsynchronously = false - self.backButtonArrow.isUserInteractionEnabled = false - self.leftButtonNode = NavigationButtonNode() - self.rightButtonNode = NavigationButtonNode() - self.rightButtonNode.hitTestSlop = UIEdgeInsets(top: -4.0, left: -4.0, bottom: -4.0, right: -10.0) - - self.clippingNode = SparseNode() - self.clippingNode.clipsToBounds = true - - self.buttonsContainerNode = ASDisplayNode() - self.buttonsContainerNode.clipsToBounds = true - - self.backButtonNode.color = self.presentationData.theme.buttonColor - self.backButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor - self.leftButtonNode.color = self.presentationData.theme.buttonColor - self.leftButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor - self.rightButtonNode.color = self.presentationData.theme.buttonColor - self.rightButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor - self.rightButtonNode.rippleColor = self.presentationData.theme.primaryTextColor.withAlphaComponent(0.05) - self.backButtonArrow.image = NavigationBar.backArrowImage(color: self.presentationData.theme.buttonColor) - if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.primaryTextColor) - self.titleNode.accessibilityLabel = title - } - self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor - - self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.backgroundColor, enableBlur: self.presentationData.theme.enableBackgroundBlur) - self.additionalContentNode = SparseNode() - - self.secondaryContentHeight = NavigationBar.defaultSecondaryContentHeight - - self.navigationBackgroundCutoutView = NavigationBackgroundCutoutView(frame: CGRect()) - self.navigationBackgroundCutoutView.targetNode = self.backgroundNode - - super.init() - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.buttonsContainerNode) - self.addSubnode(self.clippingNode) - self.addSubnode(self.additionalContentNode) - - self.backgroundColor = nil - self.isOpaque = false - - self.stripeNode.isLayerBacked = true - self.stripeNode.displaysAsynchronously = false - self.addSubnode(self.stripeNode) - - self.titleNode.displaysAsynchronously = false - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.truncationType = .end - self.titleNode.isOpaque = false - - self.backButtonNode.highlightChanged = { [weak self] index, highlighted in - if let strongSelf = self, index == 0 { - strongSelf.backButtonArrow.alpha = (highlighted ? 0.4 : 1.0) - strongSelf.badgeNode.alpha = (highlighted ? 0.4 : 1.0) - } - } - self.backButtonNode.pressed = { [weak self] index in - if let strongSelf = self, index == 0 { - if let leftBarButtonItem = strongSelf.item?.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { - leftBarButtonItem.performActionOnTarget() - } else { - strongSelf.backPressed() - } - } - } - - self.leftButtonNode.pressed = { [weak self] index in - if let item = self?.item { - if index == 0 { - if let leftBarButtonItem = item.leftBarButtonItem { - leftBarButtonItem.performActionOnTarget() - } else if let previousItem = self?.previousItem, case .close = previousItem { - self?.backPressed() - } - } - } - } - - self.rightButtonNode.pressed = { [weak self] index in - if let item = self?.item { - if let rightBarButtonItems = item.rightBarButtonItems, !rightBarButtonItems.isEmpty { - if index < rightBarButtonItems.count { - rightBarButtonItems[index].performActionOnTarget() - } - } else if let rightBarButtonItem = item.rightBarButtonItem { - rightBarButtonItem.performActionOnTarget() - } - } - } - } - - public var isBackgroundVisible: Bool { - return self.backgroundNode.alpha == 1.0 - } - - public func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { - let alpha = max(0.0, min(1.0, alpha)) - transition.updateAlpha(node: self.backgroundNode, alpha: alpha, delay: 0.15) - transition.updateAlpha(node: self.stripeNode, alpha: alpha, delay: 0.15) - } - - public func updatePresentationData(_ presentationData: NavigationBarPresentationData, transition: ContainedViewLayoutTransition = .immediate) { - if presentationData.theme !== self.presentationData.theme || presentationData.strings !== self.presentationData.strings { - self.presentationData = presentationData - - self.backgroundNode.updateColor(color: self.presentationData.theme.backgroundColor, transition: transition) - - self.backButtonNode.color = self.presentationData.theme.buttonColor - self.backButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor - self.leftButtonNode.color = self.presentationData.theme.buttonColor - self.leftButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor - self.rightButtonNode.color = self.presentationData.theme.buttonColor - self.rightButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor - self.rightButtonNode.rippleColor = self.presentationData.theme.primaryTextColor.withAlphaComponent(0.05) - self.backButtonArrow.image = NavigationBar.backArrowImage(color: self.presentationData.theme.buttonColor) - if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.primaryTextColor) - self.titleNode.accessibilityLabel = title - } - self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor - - self.badgeNode.updateTheme(fillColor: self.presentationData.theme.buttonColor, strokeColor: self.presentationData.theme.buttonColor, textColor: self.presentationData.theme.badgeTextColor) - - self.updateLeftButton(animated: false) - self.requestLayout() - } - } - - private func requestLayout() { - self.requestedLayout = true - self.setNeedsLayout() - } - - override open func layout() { - super.layout() - - if let validLayout = self.validLayout, self.requestedLayout { - self.requestedLayout = false - self.updateLayout(size: validLayout.size, defaultHeight: validLayout.defaultHeight, additionalTopHeight: validLayout.additionalTopHeight, additionalContentHeight: validLayout.additionalContentHeight, additionalBackgroundHeight: validLayout.additionalBackgroundHeight, additionalCutout: validLayout.additionalCutout, leftInset: validLayout.leftInset, rightInset: validLayout.rightInset, appearsHidden: validLayout.appearsHidden, isLandscape: validLayout.isLandscape, transition: .immediate) - } - } - - func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool, transition: ContainedViewLayoutTransition) { - if self.layoutSuspended { - return - } - - self.validLayout = (size, defaultHeight, additionalTopHeight, additionalContentHeight, additionalBackgroundHeight, additionalCutout, leftInset, rightInset, appearsHidden, isLandscape) - - let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + additionalBackgroundHeight)) - if self.backgroundNode.frame != backgroundFrame { - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - self.backgroundNode.update(size: backgroundFrame.size, transition: transition) - - transition.updateFrame(view: self.navigationBackgroundCutoutView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - } - self.navigationBackgroundCutoutView.update(size: backgroundFrame.size, cutout: additionalCutout, transition: transition) - - let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? (self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction) : 0.0 - - let leftButtonInset: CGFloat = leftInset + 16.0 - let backButtonInset: CGFloat = leftInset + 27.0 - - transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: size)) - transition.updateFrame(node: self.additionalContentNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + additionalBackgroundHeight))) - transition.updateFrame(node: self.buttonsContainerNode, frame: CGRect(origin: CGPoint(), size: size)) - var expansionHeight: CGFloat = 0.0 - if let contentNode = self.contentNode { - var contentNodeFrame: CGRect - switch contentNode.mode { - case .replacement: - expansionHeight = contentNode.height - defaultHeight - contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - additionalContentHeight)) - case .expansion: - expansionHeight = contentNode.height - - let additionalExpansionHeight: CGFloat = self.secondaryContentNode != nil && appearsHidden ? (self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction) : 0.0 - contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - (appearsHidden ? 0.0 : additionalContentHeight) - expansionHeight - apparentAdditionalHeight - additionalExpansionHeight), size: CGSize(width: size.width, height: expansionHeight)) - if appearsHidden { - if self.secondaryContentNode != nil { - contentNodeFrame.origin.y += self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction - } - } - } - transition.updateFrame(node: contentNode, frame: contentNodeFrame) - contentNode.updateLayout(size: contentNodeFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) - } - - transition.updateFrame(node: self.stripeNode, frame: CGRect(x: (additionalCutout?.width ?? 0.0), y: size.height + additionalBackgroundHeight, width: size.width - (additionalCutout?.width ?? 0.0), height: UIScreenPixel)) - - let nominalHeight: CGFloat = defaultHeight - let contentVerticalOrigin = additionalTopHeight - - var leftTitleInset: CGFloat = leftInset + 1.0 - var rightTitleInset: CGFloat = rightInset + 1.0 - if self.backButtonNode.supernode != nil { - let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape, isLeftAligned: true) - leftTitleInset = backButtonSize.width + backButtonInset + 1.0 - - let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 - self.backButtonNode.hitTestSlop = UIEdgeInsets(top: -topHitTestSlop, left: -27.0, bottom: -topHitTestSlop, right: -8.0) - - if let transitionState = self.transitionState { - let progress = transitionState.progress - - switch transitionState.role { - case .top: - let initialX: CGFloat = backButtonInset - let finalX: CGFloat = floor((size.width - backButtonSize.width) / 2.0) - size.width - - 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)) - - let initialX: CGFloat = backButtonInset + floor((backButtonSize.width - transitionTitleSize.width) / 2.0) - let finalX: CGFloat = floor((size.width - transitionTitleSize.width) / 2.0) - size.width - - transitionTitleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - transitionTitleSize.height) / 2.0)), size: transitionTitleSize) - transitionTitleNode.alpha = progress * progress - } - - self.backButtonArrow.frame = CGRect(origin: CGPoint(x: leftInset + 8.0 - progress * size.width, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) - self.backButtonArrow.alpha = max(0.0, 1.0 - progress * 1.3) - self.badgeNode.alpha = max(0.0, 1.0 - progress * 1.3) - case .bottom: - self.backButtonNode.alpha = 1.0 - self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) - self.backButtonArrow.alpha = 1.0 - self.backButtonArrow.frame = CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) - self.badgeNode.alpha = 1.0 - } - } else { - self.backButtonNode.alpha = 1.0 - transition.updateFrame(node: self.backButtonNode, frame: CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize)) - - self.backButtonArrow.alpha = 1.0 - transition.updateFrame(node: self.backButtonArrow, frame: CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0))) - self.badgeNode.alpha = 1.0 - } - } else if self.leftButtonNode.supernode != nil { - let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape, isLeftAligned: true) - leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0 - - var transition = transition - if self.leftButtonNode.frame.width.isZero { - transition = .immediate - } - - self.leftButtonNode.alpha = 1.0 - transition.updateFrame(node: self.leftButtonNode, frame: CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize)) - } - - let badgeSize = self.badgeNode.measure(CGSize(width: 200.0, height: 100.0)) - let backButtonArrowFrame = self.backButtonArrow.frame - transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 16.0, dy: 2.0), size: badgeSize)) - - if self.rightButtonNode.supernode != nil { - let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape, isLeftAligned: false) - rightTitleInset = rightButtonSize.width + leftButtonInset + 1.0 - self.rightButtonNode.alpha = 1.0 - - var transition = transition - if self.rightButtonNode.frame.width.isZero || self.rightButtonNodeUpdated { - transition = .immediate - } - - transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize)) - } - self.rightButtonNodeUpdated = false - - if let transitionState = self.transitionState { - let progress = transitionState.progress - - switch transitionState.role { - case .top: - break - case .bottom: - if let transitionBackButtonNode = self.transitionBackButtonNode { - let transitionBackButtonSize = transitionBackButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape, isLeftAligned: true) - let initialX: CGFloat = backButtonInset + size.width * 0.3 - let finalX: CGFloat = floor((size.width - transitionBackButtonSize.width) / 2.0) - - transitionBackButtonNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - transitionBackButtonSize.height) / 2.0)), size: transitionBackButtonSize) - transitionBackButtonNode.alpha = (1.0 - progress) * (1.0 - progress) - } - - if let transitionBackArrowNode = self.transitionBackArrowNode { - let initialX: CGFloat = leftInset + 8.0 + size.width * 0.3 - let finalX: CGFloat = leftInset + 8.0 - - transitionBackArrowNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) - transitionBackArrowNode.alpha = max(0.0, 1.0 - progress * 1.3) - - if let transitionBadgeNode = self.transitionBadgeNode { - transitionBadgeNode.frame = CGRect(origin: transitionBackArrowNode.frame.origin.offsetBy(dx: 16.0, dy: 2.0), size: transitionBadgeNode.bounds.size) - transitionBadgeNode.alpha = transitionBackArrowNode.alpha - } - } - } - } - - leftTitleInset = floor(leftTitleInset) - if Int(leftTitleInset) % 2 != 0 { - leftTitleInset -= 1.0 - } - - if let customHeaderContentView = self.customHeaderContentView { - let headerSize = CGSize(width: size.width, height: nominalHeight) - var customHeaderFrame = CGRect(origin: CGPoint(x: 0.0, y: contentVerticalOrigin), size: headerSize) - if appearsHidden { - customHeaderFrame.origin.y = -customHeaderFrame.height - } - transition.updateFrame(view: customHeaderContentView, frame: customHeaderFrame) - } - - if self.titleNode.supernode != nil { - 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 - - switch transitionState.role { - case .top: - let initialX = floor((size.width - titleSize.width) / 2.0) - let finalX: CGFloat = leftButtonInset - - self.titleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) - self.titleNode.alpha = (1.0 - progress) * (1.0 - progress) - case .bottom: - var initialX: CGFloat = backButtonInset - if otherNavigationBar.backButtonNode.supernode != nil { - initialX += floor((otherNavigationBar.backButtonNode.frame.size.width - titleSize.width) / 2.0) - } - initialX += size.width * 0.3 - let finalX: CGFloat = floor((size.width - titleSize.width) / 2.0) - - self.titleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) - self.titleNode.alpha = progress * progress - } - } else { - var transition = transition - if self.titleNode.frame.width.isZero { - transition = .immediate - } - self.titleNode.alpha = 1.0 - - var titleOffset: CGFloat = 0.0 - if self.presentationData.theme.backgroundColor == .clear && self.presentationData.theme.separatorColor == .clear { - titleOffset += 3.0 - } - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + titleOffset + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)) - } - } - - if let titleView = self.titleView { - let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight) - let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) - var titleViewTransition = transition - if titleView.frame.isEmpty { - titleViewTransition = .immediate - titleView.frame = titleFrame - } - - titleViewTransition.updateFrame(view: titleView, frame: titleFrame) - - if let titleView = titleView as? NavigationBarTitleView { - let titleWidth = size.width - (leftTitleInset > 0.0 ? leftTitleInset : rightTitleInset) - (rightTitleInset > 0.0 ? rightTitleInset : leftTitleInset) - - let _ = titleView.updateLayout(size: titleFrame.size, clearBounds: CGRect(origin: CGPoint(x: leftTitleInset - titleFrame.minX, y: 0.0), size: CGSize(width: titleWidth, height: titleFrame.height)), transition: titleViewTransition) - } - - if let transitionState = self.transitionState, let otherNavigationBar = transitionState.navigationBar { - let progress = transitionState.progress - - switch transitionState.role { - case .top: - let initialX = floor((size.width - titleSize.width) / 2.0) - let finalX: CGFloat = leftButtonInset - - titleView.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) - titleView.alpha = (1.0 - progress) * (1.0 - progress) - case .bottom: - var initialX: CGFloat = backButtonInset - if otherNavigationBar.backButtonNode.supernode != nil { - initialX += floor((otherNavigationBar.backButtonNode.frame.size.width - titleSize.width) / 2.0) - } - initialX += size.width * 0.3 - let finalX: CGFloat = floor((size.width - titleSize.width) / 2.0) - - titleView.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) - titleView.alpha = progress * progress - } - } else { - if self.hintAnimateTitleNodeOnNextLayout { - self.hintAnimateTitleNodeOnNextLayout = false - if let titleView = titleView as? NavigationBarTitleView { - titleView.animateLayoutTransition() - } - } - titleView.alpha = 1.0 - transition.updateFrame(view: titleView, frame: titleFrame) - } - } - } - - public func makeTransitionTitleNode(foregroundColor: UIColor) -> ASDisplayNode? { - if let titleView = self.titleView { - if let transitionView = titleView as? NavigationBarTitleTransitionNode { - return transitionView.makeTransitionMirrorNode() - } else { - return nil - } - } else if let title = self.title { - let node = ImmediateTextNode() - node.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: foregroundColor) - return node } else { return nil } } - - public func makeTransitionBackButtonNode(accentColor: UIColor) -> NavigationButtonNode? { - if self.backButtonNode.supernode != nil { - let node = NavigationButtonNode() - node.manualAlpha = self.backButtonNode.manualAlpha - node.updateManualText(self.backButtonNode.manualText) - node.color = accentColor - if let validLayout = self.validLayout { - let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape, isLeftAligned: true) - node.frame = self.backButtonNode.frame - } - return node - } else { - return nil - } - } - - public func makeTransitionBackButtonView(accentColor: UIColor) -> UIView? { - if self.backButtonNode.supernode != nil { - let node = NavigationButtonNode() - node.manualAlpha = self.backButtonNode.manualAlpha - node.updateManualText(self.backButtonNode.manualText) - node.color = accentColor - if let validLayout = self.validLayout { - let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape, isLeftAligned: true) - node.frame = self.backButtonNode.frame - } - return node.view - } else { - return nil - } - } - - public func makeTransitionRightButtonNode(accentColor: UIColor) -> NavigationButtonNode? { - if self.rightButtonNode.supernode != nil { - let node = NavigationButtonNode() - var items: [UIBarButtonItem] = [] - if let item = self.item { - if let rightBarButtonItems = item.rightBarButtonItems, !rightBarButtonItems.isEmpty { - items = rightBarButtonItems - } else if let rightBarButtonItem = item.rightBarButtonItem { - items = [rightBarButtonItem] - } - } - node.updateItems(items, animated: false) - node.color = accentColor - if let validLayout = self.validLayout { - let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape, isLeftAligned: false) - node.frame = self.backButtonNode.frame - } - return node - } else { - return nil - } - } - - public func makeTransitionBackArrowNode(accentColor: UIColor) -> ASDisplayNode? { - if self.backButtonArrow.supernode != nil { - let node = ASImageNode() - node.image = NavigationBar.backArrowImage(color: accentColor) - node.frame = self.backButtonArrow.frame - node.displayWithoutProcessing = true - node.displaysAsynchronously = false - return node - } else { - return nil - } - } - - public func makeTransitionBackArrowView(accentColor: UIColor) -> UIView? { - if self.backButtonArrow.supernode != nil { - let view = UIImageView() - view.image = NavigationBar.backArrowImage(color: accentColor) - view.frame = self.backButtonArrow.frame - return view - } else { - return nil - } - } - - public func makeTransitionBadgeNode() -> ASDisplayNode? { - if self.badgeNode.supernode != nil && !self.badgeNode.isHidden { - let node = NavigationBarBadgeNode(fillColor: self.presentationData.theme.buttonColor, strokeColor: self.presentationData.theme.buttonColor, textColor: self.presentationData.theme.badgeTextColor) - node.text = self.badgeNode.text - let nodeSize = node.measure(CGSize(width: 200.0, height: 100.0)) - node.frame = CGRect(origin: CGPoint(), size: nodeSize) - return node - } else { - return nil - } - } - - public var intrinsicCanTransitionInline: Bool = true - public var shouldTransitionInline: (() -> Bool)? - - public var passthroughTouches = true - - public var canTransitionInline: Bool { - if let contentNode = self.contentNode, case .replacement = contentNode.mode { - return false - } else { - if let shouldTransitionInline = self.shouldTransitionInline { - if !shouldTransitionInline() { - return false - } - } - return self.intrinsicCanTransitionInline - } - } - - public func contentHeight(defaultHeight: CGFloat) -> CGFloat { - var result: CGFloat = 0.0 - if let contentNode = self.contentNode { - switch contentNode.mode { - case .expansion: - result += defaultHeight + contentNode.height - case .replacement: - result += contentNode.height - } - } else { - result += defaultHeight - } - - if let _ = self.secondaryContentNode { - result += self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction - } - - return result - } - - public func setContentNode(_ contentNode: NavigationBarContentNode?, animated: Bool) { - if let customSetContentNode = self.customSetContentNode { - customSetContentNode(contentNode, animated) - return - } - - if self.contentNode !== contentNode { - if let previous = self.contentNode { - if animated { - previous.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self, weak previous] _ in - if let strongSelf = self, let previous = previous { - if previous !== strongSelf.contentNode { - previous.removeFromSupernode() - } - } - }) - } else { - previous.removeFromSupernode() - } - } - self.contentNode = contentNode - self.contentNode?.requestContainerLayout = { [weak self] transition in - self?.requestContainerLayout(transition) - } - if let contentNode = contentNode { - contentNode.clipsToBounds = true - contentNode.layer.removeAnimation(forKey: "opacity") - self.insertSubnode(contentNode, belowSubnode: self.stripeNode) - if animated { - contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - - if case .replacement = contentNode.mode, !self.buttonsContainerNode.alpha.isZero { - self.buttonsContainerNode.alpha = 0.0 - if animated { - self.buttonsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) - } - } - - if !self.bounds.size.width.isZero { - self.requestedLayout = true - self.layout() - } else { - self.requestLayout() - } - } else if self.buttonsContainerNode.alpha.isZero { - self.buttonsContainerNode.alpha = 1.0 - if animated { - self.buttonsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - } - } - - public func setSecondaryContentNode(_ secondaryContentNode: ASDisplayNode?, animated: Bool = false) { - if self.secondaryContentNode !== secondaryContentNode { - if let previous = self.secondaryContentNode, previous.supernode === self.clippingNode { - if animated { - previous.layer.animateAlpha(from: previous.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previous] finished in - if finished { - previous?.removeFromSupernode() - previous?.layer.removeAllAnimations() - } - }) - } else { - previous.removeFromSupernode() - } - } - self.secondaryContentNode = secondaryContentNode - if let secondaryContentNode = secondaryContentNode { - self.clippingNode.addSubnode(secondaryContentNode) - - if animated { - secondaryContentNode.layer.animateAlpha(from: 0.0, to: secondaryContentNode.alpha, duration: 0.3) - } - } - } - } - - public func executeBack() -> Bool { - if self.backButtonNode.isInHierarchy { - self.backButtonNode.pressed(0) - } else if self.leftButtonNode.isInHierarchy { - self.leftButtonNode.pressed(0) - } else { - self.backButtonNode.pressed(0) - } - return true - } - - public func setHidden(_ hidden: Bool, animated: Bool) { - if let contentNode = self.contentNode, case .replacement = contentNode.mode { - } else { - let targetAlpha: CGFloat = hidden ? 0.0 : 1.0 - let previousAlpha = self.buttonsContainerNode.alpha - if previousAlpha != targetAlpha { - self.buttonsContainerNode.alpha = targetAlpha - if animated { - self.buttonsContainerNode.layer.animateAlpha(from: previousAlpha, to: targetAlpha, duration: 0.2) - } - } - } - } - - override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let result = self.additionalContentNode.view.hitTest(self.view.convert(point, to: self.additionalContentNode.view), with: event) { - return result - } - - if self.frame.minY > -10.0, let customHeaderContentView = self.customHeaderContentView, let result = customHeaderContentView.hitTest(self.view.convert(point, to: customHeaderContentView), with: event) { - return result - } - - guard let result = super.hitTest(point, with: event) else { - return nil - } - - if self.passthroughTouches && (result == self.view || result == self.buttonsContainerNode.view) { - return nil - } - - return result - } } + +public protocol NavigationButtonCustomDisplayNode { + var isHighlightable: Bool { get } +} + +public protocol NavigationButtonNode: ASDisplayNode { + func updateManualAlpha(alpha: CGFloat, transition: ContainedViewLayoutTransition) + var mainContentNode: ASDisplayNode? { get } + var contentsColor: UIColor? { get set } +} + +public protocol NavigationBar: ASDisplayNode { + typealias Style = NavigationBarStyle + + var backPressed: () -> Void { get set } + + var userInfo: Any? { get set } + var makeCustomTransitionNode: ((NavigationBar, Bool) -> CustomNavigationTransitionNode?)? { get set } + var allowsCustomTransition: (() -> Bool)? { get set } + + var stripeNode: ASDisplayNode { get } + var clippingNode: SparseNode { get } + + var backgroundView: UIView { get } + var customOverBackgroundContentView: UIView { get } + var contentNode: NavigationBarContentNode? { get } + var secondaryContentNode: ASDisplayNode? { get } + var secondaryContentNodeDisplayFraction: CGFloat { get set } + + var item: UINavigationItem? { get set } + var customBackButtonText: String? { get } + var titleView: UIView? { get } + var layoutSuspended: Bool { get set } + + var enableAutomaticBackButton: Bool { get set } + var previousItem: NavigationPreviousAction? { get set } + + var backgroundNode: NavigationBackgroundNode { get } + var backButtonNode: NavigationButtonNode { get } + var badgeNode: NavigationBarBadgeNode { get } + var backButtonArrow: ASImageNode { get } + var leftButtonNode: NavigationButtonNode { get } + var rightButtonNode: NavigationButtonNode { get } + var additionalContentNode: SparseNode { get } + + func reattachAdditionalContentNode() + + var secondaryContentHeight: CGFloat { get set } + + var isBackgroundVisible: Bool { get } + + func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) + func updatePresentationData(_ presentationData: NavigationBarPresentationData, transition: ContainedViewLayoutTransition) + + var intrinsicCanTransitionInline: Bool { get set } + + var passthroughTouches: Bool { get set } + + var canTransitionInline: Bool { get } + + func contentHeight(defaultHeight: CGFloat) -> CGFloat + func setContentNode(_ contentNode: NavigationBarContentNode?, animated: Bool) + func setSecondaryContentNode(_ secondaryContentNode: ASDisplayNode?, animated: Bool) + func executeBack() -> Bool + func setHidden(_ hidden: Bool, animated: Bool) + + var requestContainerLayout: ((ContainedViewLayoutTransition) -> Void)? { get set } + + func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool, transition: ContainedViewLayoutTransition) + + func updateEdgeEffectExtension(value: CGFloat, transition: ContainedViewLayoutTransition) +} + +public var defaultNavigationBarImpl: ((NavigationBarPresentationData) -> NavigationBar)? diff --git a/submodules/Display/Source/NavigationBarBadge.swift b/submodules/Display/Source/NavigationBarBadge.swift index f1236dcc..734eda03 100644 --- a/submodules/Display/Source/NavigationBarBadge.swift +++ b/submodules/Display/Source/NavigationBarBadge.swift @@ -12,7 +12,7 @@ public final class NavigationBarBadgeNode: ASDisplayNode { private let font: UIFont = Font.regular(13.0) - var text: String = "" { + public var text: String = "" { didSet { self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor) self.invalidateCalculatedLayout() @@ -39,7 +39,7 @@ public final class NavigationBarBadgeNode: ASDisplayNode { self.addSubnode(self.textNode) } - func updateTheme(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) { + public func updateTheme(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) { self.fillColor = fillColor self.strokeColor = strokeColor self.textColor = textColor diff --git a/submodules/Display/Source/NavigationBarContentNode.swift b/submodules/Display/Source/NavigationBarContentNode.swift index 76862873..6a165a64 100644 --- a/submodules/Display/Source/NavigationBarContentNode.swift +++ b/submodules/Display/Source/NavigationBarContentNode.swift @@ -26,6 +26,7 @@ open class NavigationBarContentNode: ASDisplayNode { return .replacement } - open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + return size } } diff --git a/submodules/Display/Source/NavigationBarTitleView.swift b/submodules/Display/Source/NavigationBarTitleView.swift index 408cfb2f..c843151d 100644 --- a/submodules/Display/Source/NavigationBarTitleView.swift +++ b/submodules/Display/Source/NavigationBarTitleView.swift @@ -1,8 +1,10 @@ import Foundation import UIKit -public protocol NavigationBarTitleView { +public protocol NavigationBarTitleView: UIView { + var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? { get set } + func animateLayoutTransition() - func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) -> CGRect + func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize } diff --git a/submodules/Display/Source/NavigationTransitionCoordinator.swift b/submodules/Display/Source/NavigationTransitionCoordinator.swift index c6167746..707bd537 100644 --- a/submodules/Display/Source/NavigationTransitionCoordinator.swift +++ b/submodules/Display/Source/NavigationTransitionCoordinator.swift @@ -201,10 +201,9 @@ final class NavigationTransitionCoordinator { position = progress } - transition.animateView { - topNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: bottomNavigationBar, transition: self.transition, role: .top, progress: position) - bottomNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: topNavigationBar, transition: self.transition, role: .bottom, progress: position) - } + let _ = position + let _ = topNavigationBar + let _ = bottomNavigationBar } } @@ -218,15 +217,16 @@ final class NavigationTransitionCoordinator { position = progress } - topNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: bottomNavigationBar, transition: self.transition, role: .top, progress: position) - bottomNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: topNavigationBar, transition: self.transition, role: .bottom, progress: position) + let _ = position + let _ = topNavigationBar + let _ = bottomNavigationBar } } func endNavigationBarTransition() { if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar, self.inlineNavigationBarTransition { - topNavigationBar.transitionState = nil - bottomNavigationBar.transitionState = nil + let _ = topNavigationBar + let _ = bottomNavigationBar } } diff --git a/submodules/Display/Source/SparseContainerView.swift b/submodules/Display/Source/SparseContainerView.swift new file mode 100644 index 00000000..e9f29977 --- /dev/null +++ b/submodules/Display/Source/SparseContainerView.swift @@ -0,0 +1,47 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +open class SparseNode: ASDisplayNode { + override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.alpha.isZero { + return nil + } + for view in self.view.subviews.reversed() { + if let result = view.hitTest(self.view.convert(point, to: view), with: event), result.isUserInteractionEnabled { + return result + } + } + + if !self.bounds.inset(by: self.hitTestSlop).contains(point) { + return nil + } + + let result = super.hitTest(point, with: event) + if result != self.view { + return result + } else { + return nil + } + } +} + +open class SparseContainerView: UIView { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.alpha.isZero { + return nil + } + for view in self.subviews.reversed() { + if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled { + return result + } + } + + let result = super.hitTest(point, with: event) + if result != self { + return result + } else { + return nil + } + } +} diff --git a/submodules/Display/Source/UIKitUtils.swift b/submodules/Display/Source/UIKitUtils.swift index 130accaf..23a90c4c 100644 --- a/submodules/Display/Source/UIKitUtils.swift +++ b/submodules/Display/Source/UIKitUtils.swift @@ -108,9 +108,13 @@ public extension UIColor { var green: CGFloat = 0.0 var blue: CGFloat = 0.0 if self.getRed(&red, green: &green, blue: &blue, alpha: nil) { - return (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, green) * 255.0) << 8) | (UInt32(max(0.0, blue) * 255.0)) + let r: UInt32 = UInt32(max(0.0, red) * 255.0) + let g: UInt32 = UInt32(max(0.0, green) * 255.0) + let b: UInt32 = UInt32(max(0.0, blue) * 255.0) + return (r << 16) | (g << 8) | b } else if self.getWhite(&red, alpha: nil) { - return (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, red) * 255.0) << 8) | (UInt32(max(0.0, red) * 255.0)) + let w: UInt32 = UInt32(max(0.0, red) * 255.0) + return (w << 16) | (w << 8) | w } else { return 0 } @@ -122,9 +126,15 @@ public extension UIColor { var blue: CGFloat = 0.0 var alpha: CGFloat = 0.0 if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) { - return (UInt32(alpha * 255.0) << 24) | (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, green) * 255.0) << 8) | (UInt32(max(0.0, blue) * 255.0)) + let a: UInt32 = UInt32(alpha * 255.0) + let r: UInt32 = UInt32(max(0.0, red) * 255.0) + let g: UInt32 = UInt32(max(0.0, green) * 255.0) + let b: UInt32 = UInt32(max(0.0, blue) * 255.0) + return (a << 24) | (r << 16) | (g << 8) | b } else if self.getWhite(&red, alpha: &alpha) { - return (UInt32(max(0.0, alpha) * 255.0) << 24) | (UInt32(max(0.0, red) * 255.0) << 16) | (UInt32(max(0.0, red) * 255.0) << 8) | (UInt32(max(0.0, red) * 255.0)) + let a: UInt32 = UInt32(max(0.0, alpha) * 255.0) + let w: UInt32 = UInt32(max(0.0, red) * 255.0) + return (a << 24) | (w << 16) | (w << 8) | w } else { return 0 } diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 593005a7..a1ca2547 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -217,6 +217,18 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { open var navigationBarRequiresEntireLayoutUpdate: Bool { return true } + + public struct TabBarSearchState: Equatable { + public var isActive: Bool + + public init(isActive: Bool) { + self.isActive = isActive + } + } + + public private(set) var tabBarSearchState: TabBarSearchState? + public var tabBarSearchStateUpdated: ((ContainedViewLayoutTransition) -> Void)? + public var currentTabBarSearchNode: (() -> ASDisplayNode?)? private weak var activeInputViewCandidate: UIResponder? private weak var activeInputView: UIResponder? @@ -237,16 +249,24 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { open var interactiveNavivationGestureEdgeWidth: InteractiveTransitionGestureRecognizerEdgeWidth? { return nil } + + open var navigationEdgeEffectExtension: CGFloat { + return 0.0 + } + + public func updateNavigationEdgeEffectExtension(transition: ContainedViewLayoutTransition) { + if let navigationBar = self.navigationBar { + navigationBar.updateEdgeEffectExtension(value: max(0.0, self.navigationEdgeEffectExtension - navigationBar.frame.maxY), transition: transition) + } + } open func navigationLayout(layout: ContainerViewLayout) -> NavigationLayout { let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 var defaultNavigationBarHeight: CGFloat if self._presentedInModal && self._hasGlassStyle { - defaultNavigationBarHeight = 66.0 - } else if self._presentedInModal && layout.orientation == .portrait { - defaultNavigationBarHeight = 56.0 + defaultNavigationBarHeight = 68.0 } else { - defaultNavigationBarHeight = 44.0 + defaultNavigationBarHeight = 60.0 } let navigationBarHeight: CGFloat = statusBarHeight + (self.navigationBar?.contentHeight(defaultHeight: defaultNavigationBarHeight) ?? defaultNavigationBarHeight) @@ -375,7 +395,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { public init(navigationBarPresentationData: NavigationBarPresentationData?) { self.statusBar = StatusBar() if let navigationBarPresentationData = navigationBarPresentationData { - self.navigationBar = NavigationBar(presentationData: navigationBarPresentationData) + self.navigationBar = defaultNavigationBarImpl!(navigationBarPresentationData) } else { self.navigationBar = nil } @@ -447,14 +467,29 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { } if let navigationBar = self.navigationBar { if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode, !self.displayNavigationBar { - navigationBarFrame.origin.y -= navigationLayout.defaultContentHeight - navigationBarFrame.size.height += contentNode.height + navigationLayout.defaultContentHeight + statusBarHeight + navigationBarFrame.origin.y -= navigationLayout.defaultContentHeight + statusBarHeight + navigationBarFrame.size.height += contentNode.height + navigationLayout.defaultContentHeight + statusBarHeight * 2.0 + if self._presentedInModal && self._hasGlassStyle { + navigationBarFrame.size.height += 8.0 + } } + //navigationBar.backgroundColor = .blue if let _ = navigationBar.contentNode, let _ = navigationBar.secondaryContentNode, !self.displayNavigationBar { navigationBarFrame.size.height += navigationBar.secondaryContentHeight } - navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: navigationLayout.defaultContentHeight, additionalTopHeight: statusBarHeight, additionalContentHeight: self.additionalNavigationBarHeight, additionalBackgroundHeight: additionalBackgroundHeight, additionalCutout: additionalCutout, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, isLandscape: isLandscape, transition: transition) + var additionalTopHeight = statusBarHeight + if !self.displayNavigationBar { + additionalTopHeight -= statusBarHeight + if statusBarHeight != 0.0 { + additionalTopHeight += 6.0 + } + } + if self._presentedInModal && self._hasGlassStyle { + additionalTopHeight += 8.0 + } + + navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: navigationLayout.defaultContentHeight, additionalTopHeight: additionalTopHeight, additionalContentHeight: self.additionalNavigationBarHeight, additionalBackgroundHeight: additionalBackgroundHeight, additionalCutout: additionalCutout, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, isLandscape: isLandscape, transition: transition) if !transition.isAnimated { navigationBar.layer.removeAnimation(forKey: "bounds") navigationBar.layer.removeAnimation(forKey: "position") @@ -542,14 +577,14 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { UIView.transition(with: navigationBar.view, duration: 0.3, options: [.transitionCrossDissolve], animations: { }, completion: nil) } - self.navigationBar?.updatePresentationData(presentationData) + self.navigationBar?.updatePresentationData(presentationData, transition: .immediate) if let parent = self.parent as? TabBarController { if parent.currentController === self { if animated, let navigationBar = parent.navigationBar { UIView.transition(with: navigationBar.view, duration: 0.3, options: [.transitionCrossDissolve], animations: { }, completion: nil) } - parent.navigationBar?.updatePresentationData(presentationData) + parent.navigationBar?.updatePresentationData(presentationData, transition: .immediate) } } } @@ -705,9 +740,22 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { open func tabBarDisabledAction() { } + + open func tabBarActivateSearch() { + } + + open func tabBarDeactivateSearch() { + } open func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) { } + + public func updateTabBarSearchState(_ tabBarSearchState: TabBarSearchState?, transition: ContainedViewLayoutTransition) { + if self.tabBarSearchState != tabBarSearchState { + self.tabBarSearchState = tabBarSearchState + self.tabBarSearchStateUpdated?(transition) + } + } open func updatePossibleControllerDropContent(content: NavigationControllerDropContent?) { } diff --git a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift index 25d2f27a..9ed63103 100644 --- a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift +++ b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift @@ -919,7 +919,7 @@ public final class FeaturedStickersScreen: ViewController { private func updatePresentationData() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData), transition: .immediate) self.searchNavigationNode?.updatePresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings) @@ -980,7 +980,7 @@ private final class SearchNavigationContentNode: NavigationBarContentNode { self.cancel = cancel - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme), strings: strings, fieldStyle: .modern, cancelText: strings.Common_Done) + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme), presentationTheme: theme, strings: strings, fieldStyle: .modern, cancelText: strings.Common_Done) let placeholderText = placeholder?(strings) ?? strings.Common_Search let searchBarFont = Font.regular(17.0) @@ -1004,7 +1004,7 @@ private final class SearchNavigationContentNode: NavigationBarContentNode { self.theme = theme self.strings = strings - self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: theme), strings: strings) + self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: theme), presentationTheme: theme, strings: strings) } func setQueryUpdated(_ f: @escaping (String, String?) -> Void) { @@ -1019,10 +1019,12 @@ private final class SearchNavigationContentNode: NavigationBarContentNode { return 54.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: 1.0 + size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index d8af3d8f..e36181f6 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -576,8 +576,8 @@ private func galleryEntriesForMessageHistoryEntries(_ entries: [MessageHistoryEn } public class GalleryController: ViewController, StandalonePresentableController, KeyShortcutResponder, GalleryControllerProtocol { - public static let darkNavigationTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) - public static let lightNavigationTheme = NavigationBarTheme(buttonColor: UIColor(rgb: 0x0088ff), disabledButtonColor: UIColor(rgb: 0xd0d0d0), primaryTextColor: .black, backgroundColor: UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0), enableBackgroundBlur: false, separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) + public static let darkNavigationTheme = NavigationBarTheme(overallDarkAppearance: true, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) + public static let lightNavigationTheme = NavigationBarTheme(overallDarkAppearance: false, buttonColor: UIColor(rgb: 0x0088ff), disabledButtonColor: UIColor(rgb: 0xd0d0d0), primaryTextColor: .black, backgroundColor: UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0), enableBackgroundBlur: false, separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) private var galleryNode: GalleryControllerNode { return self.displayNode as! GalleryControllerNode @@ -942,12 +942,12 @@ public class GalleryController: ViewController, StandalonePresentableController, switch style { case .dark: strongSelf.statusBar.statusBarStyle = .White - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))) + strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)), transition: .immediate) strongSelf.galleryNode.backgroundNode.backgroundColor = UIColor.black strongSelf.galleryNode.isBackgroundExtendedOverNavigationBar = true case .light: strongSelf.statusBar.statusBarStyle = .Black - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))) + strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)), transition: .immediate) strongSelf.galleryNode.backgroundNode.backgroundColor = UIColor(rgb: 0xbdbdc2) strongSelf.galleryNode.isBackgroundExtendedOverNavigationBar = false } diff --git a/submodules/GalleryUI/Sources/GalleryTitleView.swift b/submodules/GalleryUI/Sources/GalleryTitleView.swift index 8fd42e7c..bfae2810 100644 --- a/submodules/GalleryUI/Sources/GalleryTitleView.swift +++ b/submodules/GalleryUI/Sources/GalleryTitleView.swift @@ -14,6 +14,8 @@ final class GalleryTitleView: UIView, NavigationBarTitleView { private let authorNameNode: ASTextNode private let dateNode: ASTextNode + var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? + override init(frame: CGRect) { self.authorNameNode = ASTextNode() self.authorNameNode.displaysAsynchronously = false @@ -41,7 +43,9 @@ final class GalleryTitleView: UIView, NavigationBarTitleView { self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white) } - func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) -> CGRect { + func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let size = availableSize + let leftInset: CGFloat = 0.0 let rightInset: CGFloat = 0.0 @@ -56,7 +60,7 @@ final class GalleryTitleView: UIView, NavigationBarTitleView { self.dateNode.frame = CGRect(origin: CGPoint(x: floor((size.width - dateSize.width) / 2.0), y: floor((size.height - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize) } - return CGRect() + return availableSize } func animateLayoutTransition() { diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index 43b4fc92..48fea8c8 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -1651,7 +1651,7 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode { if isHidden && !self.buttonNode.isSelected { buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - 59.0, y: -50.0) } else { - buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - (self.buttonNode.isSelected ? 24.0 : 59.0), y: insets.top - 50.0) + buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - (self.buttonNode.isSelected ? 24.0 : 59.0), y: insets.top - 58.0) } transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: buttonPosition, size: CGSize(width: buttonSize.width + 24.0, height: buttonSize.height + 24.0))) diff --git a/submodules/HashtagSearchUI/BUILD b/submodules/HashtagSearchUI/BUILD index eecdc4a8..a7010192 100644 --- a/submodules/HashtagSearchUI/BUILD +++ b/submodules/HashtagSearchUI/BUILD @@ -32,6 +32,8 @@ swift_library( "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/Components/BlurredBackgroundComponent", "//submodules/UIKitRuntimeUtils", + "//submodules/TelegramUI/Components/HorizontalTabsComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index 81195028..bc86c96b 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -59,7 +59,7 @@ public final class HashtagSearchController: TelegramBaseController { } self.presentationData = presentationData - super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none, groupCallPanelSource: .none) + super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -87,7 +87,7 @@ public final class HashtagSearchController: TelegramBaseController { if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) self.controllerNode.updatePresentationData(self.presentationData) } } diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift index 1a7d6f3e..8390c824 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift @@ -8,7 +8,8 @@ import TelegramPresentationData import SearchBarNode import ComponentFlow import ComponentDisplayAdapters -import TabSelectorComponent +import HorizontalTabsComponent +import GlassBackgroundComponent private let searchBarFont = Font.regular(17.0) @@ -23,6 +24,9 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { var onReturn: (String) -> Void = { _ in } private let searchBar: SearchBarNode + + private let tabsBackgroundContainer: GlassBackgroundContainerView + private let tabsBackgroundView: GlassBackgroundView private let tabSelector = ComponentView() private var queryUpdated: ((String) -> Void)? @@ -31,7 +35,7 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { var selectedIndex: Int = 0 { didSet { if let (size, leftInset, rightInset) = self.validLayout { - self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .animated(duration: 0.35, curve: .spring)) + let _ = self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .animated(duration: 0.35, curve: .spring)) } } } @@ -40,7 +44,7 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { didSet { if self.transitionFraction != oldValue { if let (size, leftInset, rightInset) = self.validLayout { - self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: self.transitionFraction == nil ? .animated(duration: 0.35, curve: .spring) : .immediate) + let _ = self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: self.transitionFraction == nil ? .animated(duration: 0.35, curve: .spring) : .immediate) } } } @@ -79,12 +83,18 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { var initialQuery = initialQuery initialQuery.removeFirst() - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, icon: icon, displayBackground: false) + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), presentationTheme: theme, strings: strings, fieldStyle: .glass, icon: icon, displayBackground: false) self.searchBar.text = initialQuery self.searchBar.placeholderString = NSAttributedString(string: strings.HashtagSearch_SearchPlaceholder, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) + self.tabsBackgroundContainer = GlassBackgroundContainerView() + self.tabsBackgroundView = GlassBackgroundView() + super.init() + self.tabsBackgroundContainer.contentView.addSubview(self.tabsBackgroundView) + self.view.addSubview(self.tabsBackgroundContainer) + self.searchBar.autocapitalization = .none if hasCurrentChat { @@ -111,7 +121,7 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { func updateTheme(_ theme: PresentationTheme) { self.theme = theme - self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: self.strings) + self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), presentationTheme: theme, strings: self.strings) } func setQueryUpdated(_ f: @escaping (String) -> Void) { @@ -120,7 +130,7 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { override var nominalHeight: CGFloat { if self.hasCurrentChat { - return 54.0 + 44.0 + return 64.0 + 44.0 } else { return 45.0 } @@ -128,58 +138,98 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode { private var validLayout: (CGSize, CGFloat, CGFloat)? - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { self.validLayout = (size, leftInset, rightInset) let sideInset: CGFloat = 6.0 - let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight + 5.0), size: CGSize(width: size.width, height: 54.0)) + let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: 6.0), size: CGSize(width: size.width, height: 44.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset + sideInset, rightInset: rightInset + sideInset, transition: transition) if self.hasTabs { - var items: [TabSelectorComponent.Item] = [] + var items: [HorizontalTabsComponent.Tab] = [] if self.hasCurrentChat { - items.append(TabSelectorComponent.Item(id: AnyHashable(0), title: self.strings.HashtagSearch_ThisChat)) + items.append(HorizontalTabsComponent.Tab( + id: AnyHashable(0), + content: .title(HorizontalTabsComponent.Tab.Title(text: self.strings.HashtagSearch_ThisChat, entities: [], enableAnimations: false)), + badge: nil, + action: { [weak self] in + guard let self else { + return + } + self.indexUpdated?(0) + }, + contextAction: nil, + deleteAction: nil + )) } - items.append(TabSelectorComponent.Item(id: AnyHashable(1), title: self.strings.HashtagSearch_MyMessages)) - items.append(TabSelectorComponent.Item(id: AnyHashable(2), title: self.strings.HashtagSearch_PublicPosts)) + + items.append(HorizontalTabsComponent.Tab( + id: AnyHashable(1), + content: .title(HorizontalTabsComponent.Tab.Title(text: self.strings.HashtagSearch_MyMessages, entities: [], enableAnimations: false)), + badge: nil, + action: { [weak self] in + guard let self else { + return + } + self.indexUpdated?(1) + }, + contextAction: nil, + deleteAction: nil + )) + + items.append(HorizontalTabsComponent.Tab( + id: AnyHashable(2), + content: .title(HorizontalTabsComponent.Tab.Title(text: self.strings.HashtagSearch_PublicPosts, entities: [], enableAnimations: false)), + badge: nil, + action: { [weak self] in + guard let self else { + return + } + self.indexUpdated?(2) + }, + contextAction: nil, + deleteAction: nil + )) let tabSelectorSize = self.tabSelector.update( transition: ComponentTransition(transition), - component: AnyComponent(TabSelectorComponent( - colors: TabSelectorComponent.Colors( - foreground: self.theme.list.itemSecondaryTextColor, - selection: self.theme.list.itemAccentColor - ), + component: AnyComponent(HorizontalTabsComponent( + context: nil, theme: self.theme, - customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(14.0), - spacing: self.hasCurrentChat ? 24.0 : 8.0, - lineSelection: true - ), - items: items, - selectedId: AnyHashable(self.selectedIndex), - setSelectedId: { [weak self] id in - guard let self, let index = id.base as? Int else { - return - } - self.indexUpdated?(index) - }, - transitionFraction: self.transitionFraction + tabs: items, + selectedTab: AnyHashable(self.selectedIndex), + isEditing: false )), environment: {}, - containerSize: CGSize(width: size.width, height: 44.0) + containerSize: CGSize(width: size.width - (leftInset + 16.0) * 2.0, height: 44.0) ) let tabSelectorFrameOriginX = floorToScreenPixels((size.width - tabSelectorSize.width) / 2.0) - let tabSelectorFrame = CGRect(origin: CGPoint(x: tabSelectorFrameOriginX, y: size.height - tabSelectorSize.height - 10.0), size: tabSelectorSize) - if let tabSelectorView = self.tabSelector.view { + let tabSelectorFrame = CGRect(origin: CGPoint(x: tabSelectorFrameOriginX, y: searchBarFrame.maxY + 10.0), size: tabSelectorSize) + + transition.updateFrame(view: self.tabsBackgroundContainer, frame: tabSelectorFrame) + self.tabsBackgroundContainer.update(size: tabSelectorFrame.size, isDark: self.theme.overallDarkAppearance, transition: ComponentTransition(transition)) + + transition.updateFrame(view: self.tabsBackgroundView, frame: CGRect(origin: CGPoint(), size: tabSelectorFrame.size)) + self.tabsBackgroundView.update(size: tabSelectorFrame.size, cornerRadius: tabSelectorFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: ComponentTransition(transition)) + + if let tabSelectorView = self.tabSelector.view as? HorizontalTabsComponent.View { if tabSelectorView.superview == nil { - self.view.addSubview(tabSelectorView) + self.tabsBackgroundView.contentView.addSubview(tabSelectorView) + tabSelectorView.setOverlayContainerView(overlayContainerView: self.view) } - transition.updateFrame(view: tabSelectorView, frame: tabSelectorFrame) + transition.updateFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(), size: tabSelectorFrame.size)) + + var transitionFraction: CGFloat = 0.0 + if let transitionFractionValue = self.transitionFraction { + transitionFraction = -transitionFractionValue + } + tabSelectorView.updateTabSwitchFraction(fraction: transitionFraction, isDragging: false, transition: ComponentTransition(transition)) } } + + return size } func activate() { diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift index b6238082..d2d29fda 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift @@ -200,7 +200,7 @@ final class HashtagSearchRecentQueryItemNode: ItemListRevealOptionsItemNode { self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.separatorNode) diff --git a/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift b/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift index 0fb1d85b..d7c20dfd 100644 --- a/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift +++ b/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift @@ -138,7 +138,7 @@ public final class HorizontalPeerItemNode: ListViewItemNode { self.onlineNode = PeerOnlineMarkerNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.peerNode) self.addSubnode(self.badgeBackgroundNode) diff --git a/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift b/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift index c66e876f..474cded4 100644 --- a/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift @@ -170,7 +170,7 @@ public class AdditionalLinkItemNode: ListViewItemNode, ItemListItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true diff --git a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift index 2dd9b16a..3c39e1cd 100644 --- a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift @@ -451,7 +451,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese let state = stateValue.with({ $0 }) - let promptController = promptController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, text: presentationData.strings.FolderLinkScreen_NameLink_Title, titleFont: .bold, value: state.title ?? "", characterLimit: 32, apply: { value in + let promptController = promptController(context: context, updatedPresentationData: updatedPresentationData, text: presentationData.strings.FolderLinkScreen_NameLink_Title, titleFont: .bold, value: state.title ?? "", characterLimit: 32, apply: { value in if let value { updateState { state in var state = state @@ -763,15 +763,21 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese if hasChanges { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.FolderLinkScreen_SaveAlertTitle, text: presentationData.strings.FolderLinkScreen_SaveAlertText, actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.FolderLinkScreen_SaveAlertActionDiscard, action: { - f() - dismissImpl?() - }), - TextAlertAction(type: .defaultAction, title: state.selectedPeerIds.isEmpty ? presentationData.strings.FolderLinkScreen_SaveAlertActionApply : presentationData.strings.FolderLinkScreen_SaveAlertActionContinue, action: { - applyChangesImpl?() - }) - ]), nil) + let alertController = textAlertController( + context: context, + title: presentationData.strings.FolderLinkScreen_SaveAlertTitle, + text: presentationData.strings.FolderLinkScreen_SaveAlertText, + actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.FolderLinkScreen_SaveAlertActionDiscard, action: { + f() + dismissImpl?() + }), + TextAlertAction(type: .defaultAction, title: state.selectedPeerIds.isEmpty ? presentationData.strings.FolderLinkScreen_SaveAlertActionApply : presentationData.strings.FolderLinkScreen_SaveAlertActionContinue, action: { + applyChangesImpl?() + }) + ] + ) + presentControllerImpl?(alertController, nil) return false } else { f() diff --git a/submodules/InviteLinksUI/Sources/InviteLinkHeaderItem.swift b/submodules/InviteLinksUI/Sources/InviteLinkHeaderItem.swift index d51c7950..6cf26088 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkHeaderItem.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkHeaderItem.swift @@ -94,7 +94,7 @@ class InviteLinkHeaderItemNode: ListViewItemNode { self.animationNode = DefaultAnimatedStickerNodeImpl() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.textNode.textNode) diff --git a/submodules/InviteLinksUI/Sources/InviteLinkInviteHeaderItem.swift b/submodules/InviteLinksUI/Sources/InviteLinkInviteHeaderItem.swift index 22ec3233..733ee8b9 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkInviteHeaderItem.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkInviteHeaderItem.swift @@ -86,7 +86,7 @@ class InviteLinkInviteHeaderItemNode: ListViewItemNode { self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.textNode) diff --git a/submodules/InviteLinksUI/Sources/InviteLinkInviteManageItem.swift b/submodules/InviteLinksUI/Sources/InviteLinkInviteManageItem.swift index 64b7f3b3..925c6454 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkInviteManageItem.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkInviteManageItem.swift @@ -74,7 +74,7 @@ class InviteLinkInviteManageItemNode: ListViewItemNode { self.backgroundNode = ASDisplayNode() self.buttonNode = HighlightableButtonNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.buttonNode) diff --git a/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift b/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift index 60dd4a8a..28f3932f 100644 --- a/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift +++ b/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift @@ -38,7 +38,7 @@ final class SearchNavigationContentNode: NavigationBarContentNode, ItemListContr self.cancel = cancel - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: false) + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), presentationTheme: theme, strings: strings, fieldStyle: .modern, displayBackground: false) super.init() @@ -66,7 +66,7 @@ final class SearchNavigationContentNode: NavigationBarContentNode, ItemListContr func updateTheme(_ theme: PresentationTheme) { self.theme = theme - self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: self.theme), strings: self.strings) + self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: self.theme), presentationTheme: self.theme, strings: self.strings) self.updatePlaceholder() } @@ -78,10 +78,12 @@ final class SearchNavigationContentNode: NavigationBarContentNode, ItemListContr return 56.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift index 70cdea58..5382dfed 100644 --- a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift @@ -203,7 +203,7 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.fieldNode) self.addSubnode(self.addressNode) diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift index d34dab06..6ccdd7c2 100644 --- a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift @@ -210,7 +210,7 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteLinkDateLimitItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteLinkDateLimitItem.swift index f329c40b..3555521e 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteLinkDateLimitItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteLinkDateLimitItem.swift @@ -170,7 +170,7 @@ private final class ItemListInviteLinkTimeLimitItemNode: ListViewItemNode { self.customTextNode.isUserInteractionEnabled = false self.customTextNode.displaysAsynchronously = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.lowTextNode) self.addSubnode(self.mediumTextNode) diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift index d4846030..a00ee4df 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift @@ -235,7 +235,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteLinkUsageLimitItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteLinkUsageLimitItem.swift index db379ed7..3bc59945 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteLinkUsageLimitItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteLinkUsageLimitItem.swift @@ -186,7 +186,7 @@ private final class ItemListInviteLinkUsageLimitItemNode: ListViewItemNode { self.customTextNode.isUserInteractionEnabled = false self.customTextNode.displaysAsynchronously = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.lowTextNode) self.addSubnode(self.mediumTextNode) diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift index 5a66bf6e..a233c6cd 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift @@ -223,7 +223,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode { self.contentWrapperNode = ASDisplayNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true diff --git a/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift index 7ff09939..1761e1f6 100644 --- a/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift @@ -210,7 +210,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.fieldNode) self.addSubnode(self.addressNode) @@ -403,8 +403,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem } let fieldHeight: CGFloat = 52.0 - let fieldSpacing: CGFloat = 16.0 - let buttonHeight: CGFloat = 50.0 + let fieldSpacing: CGFloat = 10.0 + let buttonHeight: CGFloat = 52.0 let justCreatedCallSeparatorSpacing: CGFloat = 16.0 let justCreatedCallTextSpacing: CGFloat = 45.0 diff --git a/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift b/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift index 595695b6..b4d71c20 100644 --- a/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift +++ b/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift @@ -142,7 +142,7 @@ public class ItemListAddressItemNode: ListViewItemNode { self.iconNode = ASImageNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.labelNode) self.addSubnode(self.textNode) diff --git a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift index 3e4bece6..b2843f1b 100644 --- a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift +++ b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift @@ -327,7 +327,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo self.callButton = HighlightableButtonNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.isAccessibilityElement = true diff --git a/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift b/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift index 49b9f75a..3f877671 100644 --- a/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift +++ b/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift @@ -160,7 +160,7 @@ public final class ItemListPeerActionItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.iconNode) self.addSubnode(self.titleNode) @@ -257,12 +257,16 @@ public final class ItemListPeerActionItemNode: ListViewItemNode { case .blocks: strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor - strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor + if !item.alwaysPlain { + strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor + } strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor case .plain: strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor - strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor + if !item.alwaysPlain { + strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor + } strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor } } diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index d9517501..036b86ab 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -855,7 +855,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true diff --git a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift index 81f65a8c..149742ff 100644 --- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift +++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift @@ -263,7 +263,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) diff --git a/submodules/ItemListUI/BUILD b/submodules/ItemListUI/BUILD index 71afc199..cbd9e076 100644 --- a/submodules/ItemListUI/BUILD +++ b/submodules/ItemListUI/BUILD @@ -34,6 +34,8 @@ swift_library( "//submodules/Components/ComponentDisplayAdapters", "//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/TelegramUI/Components/ListItemComponentAdaptor", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/HorizontalTabsComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index 9149f3f6..8eda7718 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -281,11 +281,12 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable self.presentationData = presentationData self.hideNavigationBarBackground = hideNavigationBarBackground - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideNavigationBarBackground, hideSeparator: hideNavigationBarBackground), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideNavigationBarBackground, hideSeparator: hideNavigationBarBackground, style: .glass), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) self.isOpaqueWhenInOverlay = true self.blocksBackgroundWhenInOverlay = true self.automaticallyControlPresentationContextLayout = false + self._hasGlassStyle = true self.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style @@ -326,13 +327,23 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable Queue.mainQueue().async { if let strongSelf = self { let previousState = previousControllerState.swap(controllerState) + let isFirstTime = previousState == nil if previousState?.title != controllerState.title { + var previousHadContentNode = false + switch previousState?.title { + case .textWithTabs: + previousHadContentNode = true + default: + break + } switch controllerState.title { case let .text(text): strongSelf.title = text strongSelf.navigationItem.titleView = nil strongSelf.segmentedTitleView = nil - strongSelf.navigationBar?.setContentNode(nil, animated: false) + if previousHadContentNode { + strongSelf.navigationBar?.setContentNode(nil, animated: false) + } if strongSelf.isNodeLoaded { strongSelf.controllerNode.panRecognizer?.isEnabled = false } @@ -340,7 +351,9 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable strongSelf.title = "" strongSelf.navigationItem.titleView = ItemListTextWithSubtitleTitleView(theme: controllerState.presentationData.theme, title: title, subtitle: subtitle) strongSelf.segmentedTitleView = nil - strongSelf.navigationBar?.setContentNode(nil, animated: false) + if previousHadContentNode { + strongSelf.navigationBar?.setContentNode(nil, animated: false) + } if strongSelf.isNodeLoaded { strongSelf.controllerNode.panRecognizer?.isEnabled = false } @@ -358,7 +371,9 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable } } } - strongSelf.navigationBar?.setContentNode(nil, animated: false) + if previousHadContentNode { + strongSelf.navigationBar?.setContentNode(nil, animated: false) + } if strongSelf.isNodeLoaded { strongSelf.controllerNode.panRecognizer?.isEnabled = false } @@ -515,10 +530,10 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable } } - if strongSelf.presentationData != controllerState.presentationData { + if strongSelf.presentationData != controllerState.presentationData || isFirstTime { strongSelf.presentationData = controllerState.presentationData - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme, hideBackground: strongSelf.hideNavigationBarBackground, hideSeparator: strongSelf.hideNavigationBarBackground), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))) + strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme, hideBackground: strongSelf.hideNavigationBarBackground, hideSeparator: strongSelf.hideNavigationBarBackground, edgeEffectColor: state.0.style == .blocks ? strongSelf.presentationData.theme.list.blocksBackgroundColor : strongSelf.presentationData.theme.list.plainBackgroundColor, style: .glass), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)), transition: .immediate) strongSelf.statusBar.updateStatusBarStyle(strongSelf.presentationData.theme.rootController.statusBarStyle.style, animated: true) strongSelf.segmentedTitleView?.theme = controllerState.presentationData.theme @@ -675,7 +690,8 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl private let titleNode: ImmediateTextNode private let subtitleNode: ImmediateTextNode - private var validLayout: (CGSize, CGRect)? + private var validLayout: CGSize? + var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? init(theme: PresentationTheme, title: String, subtitle: String) { self.titleNode = ImmediateTextNode() @@ -705,21 +721,23 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl func updateTheme(theme: PresentationTheme) { self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.rootController.navigationBar.primaryTextColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor) - if let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: .immediate) } } override func layoutSubviews() { super.layoutSubviews() - if let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: .immediate) } } - func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) -> CGRect { - self.validLayout = (size, clearBounds) + func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let size = availableSize + + self.validLayout = size let titleSize = self.titleNode.updateLayout(size) let subtitleSize = self.subtitleNode.updateLayout(size) @@ -731,7 +749,7 @@ private final class ItemListTextWithSubtitleTitleView: UIView, NavigationBarTitl self.titleNode.frame = titleFrame self.subtitleNode.frame = subtitleFrame - return titleFrame + return availableSize } func animateLayoutTransition() { diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 46657a20..1babb422 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -910,11 +910,7 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { if let validLayout = self.validLayout { updatedNode.updateLayout(layout: validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate) } - if updatedNode.addedUnderNavigationBar { - self.insertSubnode(updatedNode, belowSubnode: self.navigationBar) - } else { - self.addSubnode(updatedNode) - } + self.insertSubnode(updatedNode, belowSubnode: self.navigationBar) updatedNode.activate() } } else { diff --git a/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift b/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift index f2d0b36f..bce38315 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift @@ -3,9 +3,12 @@ import UIKit import Display import TelegramPresentationData import ComponentFlow -import TabSelectorComponent +import GlassBackgroundComponent +import HorizontalTabsComponent public final class ItemListControllerSegmentedTitleView: UIView { + private let backgroundContainer: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView private let tabSelector = ComponentView() public var theme: PresentationTheme { @@ -42,7 +45,13 @@ public final class ItemListControllerSegmentedTitleView: UIView { self.segments = segments self.index = selectedIndex + self.backgroundContainer = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + super.init(frame: CGRect()) + + self.addSubview(self.backgroundContainer) + self.backgroundContainer.contentView.addSubview(self.backgroundView) } required public init?(coder aDecoder: NSCoder) { @@ -62,10 +71,19 @@ public final class ItemListControllerSegmentedTitleView: UIView { return } - let mappedItems = zip(0 ..< self.segments.count, self.segments).map { index, segment in - return TabSelectorComponent.Item( + let mappedItems: [HorizontalTabsComponent.Tab] = zip(0 ..< self.segments.count, self.segments).map { index, segment in + return HorizontalTabsComponent.Tab( id: AnyHashable(index), - title: segment + content: .title(HorizontalTabsComponent.Tab.Title(text: segment, entities: [], enableAnimations: false)), + badge: nil, + action: { [weak self] in + guard let self else { + return + } + self.indexUpdated?(index) + }, + contextAction: nil, + deleteAction: nil ) } @@ -77,34 +95,32 @@ public final class ItemListControllerSegmentedTitleView: UIView { let tabSelectorSize = self.tabSelector.update( transition: transition, - component: AnyComponent(TabSelectorComponent( - colors: TabSelectorComponent.Colors( - foreground: self.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.8), - selection: self.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05) - ), + component: AnyComponent(HorizontalTabsComponent( + context: nil, theme: self.theme, - customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(15.0), - spacing: 8.0 - ), - items: mappedItems, - selectedId: AnyHashable(self.index), - setSelectedId: { [weak self] id in - guard let self, let index = id.base as? Int else { - return - } - self.indexUpdated?(index) - } + tabs: mappedItems, + selectedTab: AnyHashable(self.index), + isEditing: false, + layout: .fit )), environment: {}, containerSize: CGSize(width: size.width, height: 44.0) ) + let tabSelectorFrame = CGRect(origin: CGPoint(x: floor((size.width - tabSelectorSize.width) / 2.0), y: floor((size.height - tabSelectorSize.height) / 2.0)), size: tabSelectorSize) - if let tabSelectorView = self.tabSelector.view { + + transition.setFrame(view: self.backgroundContainer, frame: tabSelectorFrame) + self.backgroundContainer.update(size: tabSelectorFrame.size, isDark: self.theme.overallDarkAppearance, transition: transition) + + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: tabSelectorFrame.size)) + self.backgroundView.update(size: tabSelectorFrame.size, cornerRadius: tabSelectorFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: transition) + + if let tabSelectorView = self.tabSelector.view as? HorizontalTabsComponent.View { if tabSelectorView.superview == nil { - self.addSubview(tabSelectorView) + self.backgroundView.contentView.addSubview(tabSelectorView) + tabSelectorView.setOverlayContainerView(overlayContainerView: self) } - transition.setFrame(view: tabSelectorView, frame: tabSelectorFrame) + transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(), size: tabSelectorFrame.size)) } } } diff --git a/submodules/ItemListUI/Sources/ItemListControllerTabsContentNode.swift b/submodules/ItemListUI/Sources/ItemListControllerTabsContentNode.swift index 0e2526e2..71f3c5fa 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerTabsContentNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerTabsContentNode.swift @@ -63,10 +63,10 @@ final class ItemListControllerTabsContentNode: NavigationBarContentNode { guard let (size, leftInset, rightInset) = self.validLayout else { return } - self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: transition) + let _ = self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: transition) } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let isFirstTime = self.validLayout == nil self.validLayout = (size, leftInset, rightInset) @@ -116,6 +116,8 @@ final class ItemListControllerTabsContentNode: NavigationBarContentNode { if isFirstTime { self.requestContainerLayout(.immediate) } + + return size } override var height: CGFloat { diff --git a/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift b/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift index a9e6d0dd..c60eccae 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift @@ -125,7 +125,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) diff --git a/submodules/ItemListUI/Sources/Items/ItemListActivityTextItem.swift b/submodules/ItemListUI/Sources/Items/ItemListActivityTextItem.swift index 434b524c..9a0a5eff 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListActivityTextItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListActivityTextItem.swift @@ -85,7 +85,7 @@ public class ItemListActivityTextItemNode: ListViewItemNode { self.activityIndicator = ActivityIndicator(type: ActivityIndicatorType.custom(.black, 16.0, 2.0, false)) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.activityIndicator) diff --git a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift index 667a471d..8743226e 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift @@ -168,7 +168,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.contentParentNode) self.contentParentNode.addSubnode(self.contentContainerNode) diff --git a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift index 7b03132a..5d518b5f 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift @@ -254,7 +254,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode.textNode) self.addSubnode(self.labelNode) diff --git a/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift b/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift index 3ba05e2b..df2372c1 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift @@ -83,8 +83,8 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD return !self.isDisplayingRevealedOptions } - override public init(layerBacked: Bool, dynamicBounce: Bool, rotated: Bool, seeThrough: Bool) { - super.init(layerBacked: layerBacked, dynamicBounce: dynamicBounce, rotated: rotated, seeThrough: seeThrough) + override public init(layerBacked: Bool, rotated: Bool, seeThrough: Bool) { + super.init(layerBacked: layerBacked, rotated: rotated, seeThrough: seeThrough) } open var controlsContainer: ASDisplayNode { diff --git a/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift index a7f52e9e..6f95c58d 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift @@ -267,7 +267,7 @@ public class ItemListExpandableSwitchItemNode: ListViewItemNode, ItemListItemNod self.subItemContainer = ASDisplayNode() self.subItemContainer.clipsToBounds = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.titleValueNode) diff --git a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift index f39f8b3f..bf113249 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift @@ -181,7 +181,7 @@ public class InfoItemNode: ListViewItemNode { self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) self.closeButton.displaysAsynchronously = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.badgeNode) self.addSubnode(self.labelNode) diff --git a/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift b/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift index c2465dec..e0e98a5c 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift @@ -152,7 +152,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod self.limitTextNode = TextNode() self.limitTextNode.displaysAsynchronously = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.textClippingNode.addSubnode(self.textNode) self.addSubnode(self.textClippingNode) diff --git a/submodules/ItemListUI/Sources/Items/ItemListMultilineTextItem.swift b/submodules/ItemListUI/Sources/Items/ItemListMultilineTextItem.swift index fc92b444..98865359 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListMultilineTextItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListMultilineTextItem.swift @@ -127,7 +127,7 @@ public class ItemListMultilineTextItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.textNode) self.addSubnode(self.activateArea) diff --git a/submodules/ItemListUI/Sources/Items/ItemListPlaceholderItem.swift b/submodules/ItemListUI/Sources/Items/ItemListPlaceholderItem.swift index 4a87d011..4bbd49de 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListPlaceholderItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListPlaceholderItem.swift @@ -93,7 +93,7 @@ public class ItemListPlaceholderItemNode: ListViewItemNode, ItemListItemNode { self.textNode = TextNode() self.textNode.isUserInteractionEnabled = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.textNode) } diff --git a/submodules/ItemListUI/Sources/Items/ItemListSectionHeaderItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSectionHeaderItem.swift index 61c141fd..f19047c2 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListSectionHeaderItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListSectionHeaderItem.swift @@ -139,7 +139,7 @@ public class ItemListSectionHeaderItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() self.activateArea.accessibilityTraits = [.staticText, .header] - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.accessoryTextNode) diff --git a/submodules/ItemListUI/Sources/Items/ItemListSingleLineInputItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSingleLineInputItem.swift index f2bdd08d..0b012154 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListSingleLineInputItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListSingleLineInputItem.swift @@ -177,7 +177,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg self.labelNode = TextNode() self.labelNode.isUserInteractionEnabled = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode.textNode) self.addSubnode(self.textNode) diff --git a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift index 18a0027c..b9d897c1 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift @@ -207,7 +207,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.switchNode) diff --git a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift index ad988d97..5c5c50a2 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift @@ -132,7 +132,7 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { self.activateArea = AccessibilityAreaNode() self.activateArea.accessibilityTraits = .staticText - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.textNode.textNode) self.addSubnode(self.activateArea) diff --git a/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift b/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift index 2b27d407..9e3232df 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift @@ -134,7 +134,7 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode { self.textNode.contentMode = .left self.textNode.contentsScale = UIScreen.main.scale - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.isAccessibilityElement = true diff --git a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift index 0cff7f0a..b8492924 100644 --- a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift +++ b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift @@ -171,7 +171,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true diff --git a/submodules/ListMessageItem/Sources/ListMessageHoleItem.swift b/submodules/ListMessageItem/Sources/ListMessageHoleItem.swift index d712fed0..f4960900 100644 --- a/submodules/ListMessageItem/Sources/ListMessageHoleItem.swift +++ b/submodules/ListMessageItem/Sources/ListMessageHoleItem.swift @@ -66,7 +66,7 @@ final class ListMessageHoleItemNode: ListViewItemNode { private var activityIndicator: UIActivityIndicatorView? init() { - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) } override func didLoad() { diff --git a/submodules/ListMessageItem/Sources/ListMessageNode.swift b/submodules/ListMessageItem/Sources/ListMessageNode.swift index af9ba2c7..19d1b2ee 100644 --- a/submodules/ListMessageItem/Sources/ListMessageNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageNode.swift @@ -10,7 +10,7 @@ public class ListMessageNode: ListViewItemNode { var interaction: ListMessageItemInteraction? required init() { - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) } func setupItem(_ item: ListMessageItem) { diff --git a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift index de538291..fd7134aa 100644 --- a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift @@ -18,9 +18,17 @@ import TelegramStringFormatting import WallpaperResources import UrlEscaping -private let iconFont = Font.with(size: 30.0, design: .round, weight: .bold) +private let iconFont = Font.with(size: 28.0, design: .round, weight: .bold) private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radius: 6.0, color: UIColor(rgb: 0xFF9500)) +private let iconTextGlassBackgroundImage = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor(rgb: 0xFF9500).cgColor) + + let path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 12.0) + context.addPath(path.cgPath) + context.fillPath() +}) public final class ListMessageSnippetItemNode: ListMessageNode { private let contextSourceNode: ContextExtractedContentContainingNode @@ -273,7 +281,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { var iconImageReferenceAndRepresentation: (AnyMediaReference, TelegramMediaImageRepresentation)? var updateIconImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - let applyIconTextBackgroundImage = iconTextBackgroundImage + let applyIconTextBackgroundImage = item.systemStyle == .glass ? iconTextGlassBackgroundImage : iconTextBackgroundImage var primaryUrl: String? @@ -637,7 +645,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { var iconImageApply: (() -> Void)? if let iconImageReferenceAndRepresentation = iconImageReferenceAndRepresentation { let iconSize = CGSize(width: 40.0, height: 40.0) - let imageCorners = ImageCorners(radius: 6.0) + let imageCorners = ImageCorners(radius: item.systemStyle == .glass ? 12.0 : 6.0, curve: item.systemStyle == .glass ? .continuous : .circular) let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconImageReferenceAndRepresentation.1.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) } @@ -754,7 +762,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { } let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + leftOffset + 12.0, y: 12.0), size: CGSize(width: 40.0, height: 40.0)) - transition.updateFrame(node: strongSelf.iconTextNode, frame: CGRect(origin: CGPoint(x: iconFrame.minX + floorToScreenPixels((iconFrame.width - iconTextLayout.size.width) / 2.0), y: iconFrame.minY + floorToScreenPixels((iconFrame.height - iconTextLayout.size.height) / 2.0) + 2.0), size: iconTextLayout.size)) + transition.updateFrame(node: strongSelf.iconTextNode, frame: CGRect(origin: CGPoint(x: iconFrame.minX + floorToScreenPixels((iconFrame.width - iconTextLayout.size.width) / 2.0) + 1.0 - UIScreenPixel, y: iconFrame.minY + floorToScreenPixels((iconFrame.height - iconTextLayout.size.height) / 2.0) + 2.0), size: iconTextLayout.size)) let _ = iconTextApply() diff --git a/submodules/ListSectionHeaderNode/BUILD b/submodules/ListSectionHeaderNode/BUILD index 38262bee..11c8ff2c 100644 --- a/submodules/ListSectionHeaderNode/BUILD +++ b/submodules/ListSectionHeaderNode/BUILD @@ -10,9 +10,10 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/ImageBlur", ], visibility = [ "//visibility:public", diff --git a/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift b/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift index c781d07f..e933bbd6 100644 --- a/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift +++ b/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift @@ -3,6 +3,7 @@ import UIKit import AsyncDisplayKit import Display import TelegramPresentationData +import ImageBlur private let titleFont = Font.regular(13.0) private let actionFont = Font.regular(13.0) @@ -15,6 +16,7 @@ public enum ListSectionHeaderActionType { public final class ListSectionHeaderNode: ASDisplayNode { private let backgroundLayer: SimpleLayer private let label: ImmediateTextNode + private let labelBackgroundView: UIImageView private var actionButtonLabel: ImmediateTextNode? private var actionButton: HighlightableButtonNode? private var theme: PresentationTheme @@ -89,13 +91,16 @@ public final class ListSectionHeaderNode: ASDisplayNode { self.label.isAccessibilityElement = true self.label.displaysAsynchronously = false + self.labelBackgroundView = UIImageView() + super.init() self.layer.addSublayer(self.backgroundLayer) + //self.view.addSubview(self.labelBackgroundView) self.addSubnode(self.label) - self.backgroundLayer.backgroundColor = theme.chatList.sectionHeaderFillColor.cgColor + //self.backgroundLayer.backgroundColor = theme.chatList.sectionHeaderFillColor.cgColor } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -135,7 +140,7 @@ public final class ListSectionHeaderNode: ASDisplayNode { if self.theme !== theme { self.theme = theme - self.backgroundLayer.backgroundColor = theme.chatList.sectionHeaderFillColor.cgColor + //self.backgroundLayer.backgroundColor = theme.chatList.sectionHeaderFillColor.cgColor self.label.attributedText = NSAttributedString(string: self.title ?? "", font: titleFont, textColor: self.theme.chatList.sectionHeaderTextColor) @@ -150,7 +155,22 @@ public final class ListSectionHeaderNode: ASDisplayNode { public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, showBackground: Bool = true) { self.validLayout = (size, leftInset, rightInset) let labelSize = self.label.updateLayout(CGSize(width: max(0.0, size.width - leftInset - rightInset - 18.0), height: size.height)) - self.label.frame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0 + UIScreenPixel), size: CGSize(width: labelSize.width, height: size.height)) + let labelFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0 + UIScreenPixel), size: CGSize(width: labelSize.width, height: labelSize.height)) + self.label.frame = labelFrame + + let labelBackgroundSize: CGFloat = labelSize.height + let labelBackgroundInnerInset: CGFloat = 2.0 + let labelBackgroundInset: CGFloat = 10.0 + labelBackgroundInnerInset + if self.labelBackgroundView.image?.size.width != labelBackgroundSize + labelBackgroundInset * 2.0 { + self.labelBackgroundView.image = blurredImage(generateImage(CGSize(width: labelBackgroundSize + labelBackgroundInset * 2.0, height: labelBackgroundSize + labelBackgroundInset * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: labelBackgroundInset - labelBackgroundInnerInset, y: labelBackgroundInset - labelBackgroundInnerInset), size: CGSize(width: labelBackgroundSize + labelBackgroundInnerInset * 2.0, height: labelBackgroundSize + labelBackgroundInnerInset * 2.0))) + })!, radius: 17, iterations: 3)?.stretchableImage(withLeftCapWidth: Int(labelBackgroundInset + labelBackgroundSize * 0.5), topCapHeight: Int(labelBackgroundInset + labelBackgroundSize * 0.5)).withRenderingMode(.alwaysTemplate) + } + self.labelBackgroundView.tintColor = self.theme.list.plainBackgroundColor.withAlphaComponent(self.theme.overallDarkAppearance ? 0.5 : 0.7) + + self.labelBackgroundView.frame = labelFrame.insetBy(dx: -labelBackgroundInset - 4.0, dy: -labelBackgroundInset) if let actionButton = self.actionButton, let actionButtonLabel = self.actionButtonLabel { let buttonSize = actionButtonLabel.updateLayout(CGSize(width: size.width, height: size.height)) diff --git a/submodules/LocationUI/Sources/LocationActionListItem.swift b/submodules/LocationUI/Sources/LocationActionListItem.swift index e84192d1..ea37651e 100644 --- a/submodules/LocationUI/Sources/LocationActionListItem.swift +++ b/submodules/LocationUI/Sources/LocationActionListItem.swift @@ -224,7 +224,7 @@ final class LocationActionListItemNode: ListViewItemNode { self.venueIconNode = TransformImageNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.separatorNode) diff --git a/submodules/LocationUI/Sources/LocationAttributionItem.swift b/submodules/LocationUI/Sources/LocationAttributionItem.swift index d090c91b..b38b7e90 100644 --- a/submodules/LocationUI/Sources/LocationAttributionItem.swift +++ b/submodules/LocationUI/Sources/LocationAttributionItem.swift @@ -65,7 +65,7 @@ private class LocationAttributionItemNode: ListViewItemNode { self.imageNode.displaysAsynchronously = false self.imageNode.displayWithoutProcessing = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.imageNode) } diff --git a/submodules/LocationUI/Sources/LocationInfoListItem.swift b/submodules/LocationUI/Sources/LocationInfoListItem.swift index 4c3bca6e..fda4a00e 100644 --- a/submodules/LocationUI/Sources/LocationInfoListItem.swift +++ b/submodules/LocationUI/Sources/LocationInfoListItem.swift @@ -98,7 +98,7 @@ public final class LocationInfoListItemNode: ListViewItemNode { self.venueIconNode = TransformImageNode() self.venueIconNode.isUserInteractionEnabled = false - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.buttonNode) diff --git a/submodules/LocationUI/Sources/LocationLiveListItem.swift b/submodules/LocationUI/Sources/LocationLiveListItem.swift index 7df33503..5aaaf511 100644 --- a/submodules/LocationUI/Sources/LocationLiveListItem.swift +++ b/submodules/LocationUI/Sources/LocationLiveListItem.swift @@ -119,7 +119,7 @@ final class LocationLiveListItemNode: ListViewItemNode { self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode.isLayerBacked = !smartInvertColorsEnabled() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.separatorNode) diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index 9942427e..571b440f 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -129,7 +129,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab let navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme, hideBackground: style == .glass, hideSeparator: true), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)) - strongSelf.navigationBar?.updatePresentationData(navigationBarPresentationData) + strongSelf.navigationBar?.updatePresentationData(navigationBarPresentationData, transition: .immediate) strongSelf.searchNavigationContentNode?.updatePresentationData(strongSelf.presentationData) strongSelf.updateBarButtons() diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index fef407f8..1cab3b16 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -1382,7 +1382,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM transition.updateFrame(view: titleView, frame: titleFrame) } - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let cancelButtonSize = self.cancelButton.update( transition: ComponentTransition(transition), component: AnyComponent(GlassBarButtonComponent( @@ -1393,7 +1393,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM component: AnyComponentWithIdentity(id: isPickingLocation ? "back" : "close", component: AnyComponent( BundleIconComponent( name: isPickingLocation ? "Navigation/Back" : "Navigation/Close", - tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -1428,7 +1428,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM component: AnyComponentWithIdentity(id: "search", component: AnyComponent( BundleIconComponent( name: "Navigation/Search", - tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in diff --git a/submodules/LocationUI/Sources/LocationSearchNavigationContentNode.swift b/submodules/LocationUI/Sources/LocationSearchNavigationContentNode.swift index 8d2baf3c..2ece25f6 100644 --- a/submodules/LocationUI/Sources/LocationSearchNavigationContentNode.swift +++ b/submodules/LocationUI/Sources/LocationSearchNavigationContentNode.swift @@ -18,7 +18,7 @@ final class LocationSearchNavigationContentNode: NavigationBarContentNode { self.presentationData = presentationData self.interaction = interaction - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings, fieldStyle: .modern) + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), presentationTheme: presentationData.theme, strings: presentationData.strings, fieldStyle: .modern) self.searchBar.placeholderString = NSAttributedString(string: presentationData.strings.Map_Search, font: searchBarFont, textColor: presentationData.theme.rootController.navigationSearchBar.inputPlaceholderTextColor) super.init() @@ -38,10 +38,12 @@ final class LocationSearchNavigationContentNode: NavigationBarContentNode { return 56.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { @@ -58,6 +60,6 @@ final class LocationSearchNavigationContentNode: NavigationBarContentNode { func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData - self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings) + self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), presentationTheme: presentationData.theme, strings: presentationData.strings) } } diff --git a/submodules/LocationUI/Sources/LocationSectionHeaderItem.swift b/submodules/LocationUI/Sources/LocationSectionHeaderItem.swift index 42f8bd29..3aeae080 100644 --- a/submodules/LocationUI/Sources/LocationSectionHeaderItem.swift +++ b/submodules/LocationUI/Sources/LocationSectionHeaderItem.swift @@ -58,7 +58,7 @@ private class LocationSectionHeaderItemNode: ListViewItemNode { private var layoutParams: ListViewItemLayoutParams? required init() { - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) } override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { diff --git a/submodules/LocationUI/Sources/LocationViewController.swift b/submodules/LocationUI/Sources/LocationViewController.swift index 173b9356..1a19c949 100644 --- a/submodules/LocationUI/Sources/LocationViewController.swift +++ b/submodules/LocationUI/Sources/LocationViewController.swift @@ -109,7 +109,7 @@ public final class LocationViewController: ViewController { } strongSelf.presentationData = presentationData - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))) + strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)), transition: .immediate) strongSelf.updateRightBarButton() diff --git a/submodules/MediaPickerUI/BUILD b/submodules/MediaPickerUI/BUILD index 3a979c45..046b14d0 100644 --- a/submodules/MediaPickerUI/BUILD +++ b/submodules/MediaPickerUI/BUILD @@ -57,6 +57,7 @@ swift_library( "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/AlertComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift index 0f9a4763..dfeba384 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift @@ -130,7 +130,7 @@ private final class MediaGroupsGridAlbumItemNode : ListViewItemNode { self.countNode = TextNode() self.countNode.isUserInteractionEnabled = false - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.imageNode) @@ -283,7 +283,7 @@ private class MediaGroupsAlbumGridItemNode: ListViewItemNode { self.listNode = ListView() self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.listNode) } diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift index 7cc61779..d086a038 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift @@ -172,7 +172,7 @@ class MediaGroupsAlbumItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.iconNode) self.addSubnode(self.titleNode) diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsHeaderItem.swift b/submodules/MediaPickerUI/Sources/MediaGroupsHeaderItem.swift index b934d07d..409f895d 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsHeaderItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsHeaderItem.swift @@ -59,7 +59,7 @@ private class MediaGroupsHeaderItemNode: ListViewItemNode { init() { self.titleNode = TextNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.titleNode) } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index b59c8fe0..a561b2df 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -33,6 +33,7 @@ import ComponentFlow import BundleIconComponent import LottieComponent import GlassBarButtonComponent +import AlertComponent final class MediaPickerInteraction { let downloadManager: AssetDownloadManager @@ -1436,7 +1437,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } if asFile && hasHeic { - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.MediaPicker_JpegConversionText, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.MediaPicker_KeepHeic, action: { + controller.present(textAlertController(context: controller.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.MediaPicker_JpegConversionText, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.MediaPicker_KeepHeic, action: { proceed(false) }), TextAlertAction(type: .genericAction, title: self.presentationData.strings.MediaPicker_ConvertToJpeg, action: { proceed(true) @@ -2042,26 +2043,26 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att if let _ = item as? TGMediaPickerGalleryPhotoItem { if self.bannedSendPhotos != nil { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_SendNotAllowedPhoto, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_SendNotAllowedPhoto, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } } else if let _ = item as? TGMediaPickerGalleryVideoItem { if self.bannedSendVideos != nil { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_SendNotAllowedVideo, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_SendNotAllowedVideo, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } } else if let asset = item as? TGMediaAsset { if asset.isVideo { if self.bannedSendVideos != nil { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_SendNotAllowedVideo, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_SendNotAllowedVideo, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } } else { if self.bannedSendPhotos != nil { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_SendNotAllowedPhoto, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_SendNotAllowedPhoto, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } @@ -2112,6 +2113,10 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } } + self.selectedButtonNode.action = { [weak self] in + self?.selectedPressed() + } + self.navigationItem.titleView = self.titleView if case let .assets(collection, mode) = self.subject, mode != .default { @@ -2184,8 +2189,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att // } // } - self.selectedButtonNode.addTarget(self, action: #selector(self.selectedPressed), forControlEvents: .touchUpInside) - self.scrollToTop = { [weak self] in if let strongSelf = self { if let webSearchController = strongSelf.webSearchController { @@ -2220,26 +2223,26 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att if let self = self, let selectionState = self.interaction?.selectionState { if let _ = item as? TGMediaPickerGalleryPhotoItem { if self.bannedSendPhotos != nil { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_SendNotAllowedPhoto, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_SendNotAllowedPhoto, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } } else if let _ = item as? TGMediaPickerGalleryVideoItem { if self.bannedSendVideos != nil { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_SendNotAllowedVideo, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_SendNotAllowedVideo, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } } else if let asset = item as? TGMediaAsset { if asset.isVideo { if self.bannedSendVideos != nil { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_SendNotAllowedVideo, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_SendNotAllowedVideo, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } } else { if self.bannedSendPhotos != nil { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_SendNotAllowedPhoto, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_SendNotAllowedPhoto, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } @@ -2548,7 +2551,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att let useGlassButtons = (isBack || !self.controllerNode.scrolledToTop) && !self.controllerNode.isSwitchingAssetGroup let barButtonSideInset: CGFloat = 16.0 - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) var buttonTransition = ComponentTransition.easeInOut(duration: 0.25) if case let .animated(duration, _) = transition, duration > 0.25 { @@ -2580,13 +2583,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att transition: buttonTransition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, - state: useGlassButtons ? .glass : .generic, + state: .glass, component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent( BundleIconComponent( name: isBack ? "Navigation/Back" : "Navigation/Close", - tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -2611,15 +2614,15 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att transition: buttonTransition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, - state: useGlassButtons ? .glass : .generic, + state: .glass, component: AnyComponentWithIdentity(id: "more", component: AnyComponent( LottieComponent( content: LottieComponent.AppBundleContent( name: "anim_morewide" ), - color: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor, + color: self.presentationData.theme.chat.inputPanel.panelControlColor, size: CGSize(width: 34.0, height: 34.0), playOnce: self.moreButtonPlayOnce ) @@ -2673,7 +2676,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } else { navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) } - self.navigationBar?.updatePresentationData(navigationBarPresentationData) + self.navigationBar?.updatePresentationData(navigationBarPresentationData, transition: .immediate) self.titleView.theme = self.presentationData.theme self.cancelButtonNode.theme = self.presentationData.theme self.moreButtonNode.theme = self.presentationData.theme @@ -2717,16 +2720,22 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } else { text = self.presentationData.strings.Attachment_CancelSelectionAlertText } - - let controller = textAlertController(context: self.context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertNo, action: { - }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertYes, action: { [weak self] in - self?.dismissAllTooltips() - completion() - })]) - controller.dismissed = { [weak self] _ in + let alertController = AlertScreen( + context: self.context, + title: nil, + text: text, + actions: [ + .init(title: self.presentationData.strings.Attachment_CancelSelectionAlertNo), + .init(title: self.presentationData.strings.Attachment_CancelSelectionAlertYes, type: .default, action: { [weak self] in + self?.dismissAllTooltips() + completion() + }), + ] + ) + alertController.dismissed = { [weak self] _ in self?.isDismissing = false } - self.present(controller, in: .window(.root)) + self.present(alertController, in: .window(.root)) } else { completion() } @@ -3777,126 +3786,188 @@ public func avatarMediaPickerController( performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void -) -> ViewController { - let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) - let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) - let controller = AttachmentController( - context: context, - updatedPresentationData: updatedPresentationData, - style: .glass, - chatLocation: nil, - buttons: [.standalone], - initialButton: .standalone, - fromMenu: false, - hasTextInput: false, - makeEntityInputView: { - return nil - }) - controller.forceSourceRect = true - controller.getSourceRect = getSourceRect - controller.requestController = { [weak controller] _, present in - var mainButtonState: AttachmentMainButtonState? - - if canDelete { - mainButtonState = AttachmentMainButtonState(text: presentationData.strings.MediaPicker_RemovePhoto, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false) +) -> (controller: ViewController?, holder: Any?) { + if #available(iOS 14.0, *), PHPhotoLibrary.authorizationStatus(for: .readWrite) != .authorized { + final class PickerDelegate: NSObject, PHPickerViewControllerDelegate { + var completion: ((Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void)? + + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + picker.dismiss(animated: true) + + for item in results { + if item.itemProvider.canLoadObject(ofClass: UIImage.self) { + item.itemProvider.loadObject(ofClass: UIImage.self) { image, error in + if let uiImage = image as? UIImage { + Queue.mainQueue().async { + self.completion?(uiImage, nil, CGRect(), nil, false, { _ in return nil }, {}) + } + } + } + } + } + } } - let mediaPickerController = MediaPickerScreenImpl( + let holder = PickerDelegate() + holder.completion = completion + + let openMediaPicker = { + var configuration = PHPickerConfiguration(photoLibrary: .shared()) + configuration.filter = .images + configuration.selectionLimit = 1 + + let picker = PHPickerViewController(configuration: configuration) + picker.delegate = holder + (context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController?.present(picker, animated: true, completion: nil) + } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controller = ActionSheetController(presentationData: presentationData) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + + var items: [ActionSheetButtonItem] = [ + ActionSheetButtonItem(title: presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: { + dismissAction() + openMediaPicker() + }), + ActionSheetButtonItem(title: presentationData.strings.ProfilePhoto_SetEmoji, color: .accent, action: { + dismissAction() + completion(nil, nil, CGRect(), nil, false, { _ in return nil }, {}) + }) + ] + if canDelete { + items.append(ActionSheetButtonItem(title: presentationData.strings.MediaPicker_RemovePhoto, color: .destructive, action: { + dismissAction() + performDelete() + })) + } + controller.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + return (controller, holder) + } else { + let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) + let controller = AttachmentController( context: context, updatedPresentationData: updatedPresentationData, style: .glass, - peer: nil, - threadTitle: nil, chatLocation: nil, - bannedSendPhotos: nil, - bannedSendVideos: nil, - subject: .assets(nil, .createAvatar), - mainButtonState: mainButtonState, - mainButtonAction: { [weak controller] in - controller?.dismiss(animated: true) - performDelete() - } - ) - mediaPickerController.customSelection = { controller, result in - if let result = result as? PHAsset { - controller.updateHiddenMediaId(result.localIdentifier) - if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { - let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in - if let isNew { - if isNew { - controller.updateHiddenMediaId(nil) - if let transitionView = controller.defaultTransitionView() { - return (transitionView, transitionView.bounds) - } - } else if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { - return (transitionView, transitionView.bounds) - } - } - return nil - } - completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), false, transitionOut, { [weak controller] in - controller?.updateHiddenMediaId(nil) - }) - } - } - } - mediaPickerController.openAvatarEditor = { [weak controller] in - completion(nil, nil, .zero, nil, false, { _ in return nil }, { + buttons: [.standalone], + initialButton: .standalone, + fromMenu: false, + hasTextInput: false, + makeEntityInputView: { + return nil }) - controller?.dismiss(animated: true) - } - mediaPickerController.openCamera = { [weak controller] cameraHolder in - let _ = controller - guard let cameraHolder = cameraHolder as? CameraHolder else { - return + controller.forceSourceRect = true + controller.getSourceRect = getSourceRect + controller.requestController = { [weak controller] _, present in + var mainButtonState: AttachmentMainButtonState? + + if canDelete { + mainButtonState = AttachmentMainButtonState(text: presentationData.strings.MediaPicker_RemovePhoto, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.destructiveActionTextColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false) } - var returnToCameraImpl: (() -> Void)? - - let cameraScreen = context.sharedContext.makeCameraScreen( + let mediaPickerController = MediaPickerScreenImpl( context: context, - mode: .avatar, - cameraHolder: cameraHolder, - transitionIn: CameraScreenTransitionIn( - sourceView: cameraHolder.parentView, - sourceRect: cameraHolder.parentView.bounds, - sourceCornerRadius: 0.0, - useFillAnimation: false - ), - transitionOut: { _ in - return CameraScreenTransitionOut( - destinationView: cameraHolder.parentView, - destinationRect: cameraHolder.parentView.bounds, - destinationCornerRadius: 0.0 - ) - }, - completion: { result, commit in - completion(result, nil, .zero, nil, true, { _ in return nil }, { - returnToCameraImpl?() - }) - }, - transitionedOut: { [weak cameraHolder] in - if let cameraHolder { - cameraHolder.restore() - } + updatedPresentationData: updatedPresentationData, + style: .glass, + peer: nil, + threadTitle: nil, + chatLocation: nil, + bannedSendPhotos: nil, + bannedSendVideos: nil, + subject: .assets(nil, .createAvatar), + mainButtonState: mainButtonState, + mainButtonAction: { [weak controller] in + controller?.dismiss(animated: true) + performDelete() } ) - controller?.push(cameraScreen) - - returnToCameraImpl = { [weak cameraScreen] in - if let cameraScreen = cameraScreen as? CameraScreen { - cameraScreen.returnFromEditor() + mediaPickerController.customSelection = { controller, result in + if let result = result as? PHAsset { + controller.updateHiddenMediaId(result.localIdentifier) + if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { + let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in + if let isNew { + if isNew { + controller.updateHiddenMediaId(nil) + if let transitionView = controller.defaultTransitionView() { + return (transitionView, transitionView.bounds) + } + } else if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) { + return (transitionView, transitionView.bounds) + } + } + return nil + } + completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), false, transitionOut, { [weak controller] in + controller?.updateHiddenMediaId(nil) + }) + } } } + mediaPickerController.openAvatarEditor = { [weak controller] in + completion(nil, nil, .zero, nil, false, { _ in return nil }, { + }) + controller?.dismiss(animated: true) + } + mediaPickerController.openCamera = { [weak controller] cameraHolder in + let _ = controller + guard let cameraHolder = cameraHolder as? CameraHolder else { + return + } + + var returnToCameraImpl: (() -> Void)? + + let cameraScreen = context.sharedContext.makeCameraScreen( + context: context, + mode: .avatar, + cameraHolder: cameraHolder, + transitionIn: CameraScreenTransitionIn( + sourceView: cameraHolder.parentView, + sourceRect: cameraHolder.parentView.bounds, + sourceCornerRadius: 0.0, + useFillAnimation: false + ), + transitionOut: { _ in + return CameraScreenTransitionOut( + destinationView: cameraHolder.parentView, + destinationRect: cameraHolder.parentView.bounds, + destinationCornerRadius: 0.0 + ) + }, + completion: { result, commit in + completion(result, nil, .zero, nil, true, { _ in return nil }, { + returnToCameraImpl?() + }) + }, + transitionedOut: { [weak cameraHolder] in + if let cameraHolder { + cameraHolder.restore() + } + } + ) + controller?.push(cameraScreen) + + returnToCameraImpl = { [weak cameraScreen] in + if let cameraScreen = cameraScreen as? CameraScreen { + cameraScreen.returnFromEditor() + } + } + } + present(mediaPickerController, mediaPickerController.mediaPickerContext) } - present(mediaPickerController, mediaPickerController.mediaPickerContext) + controller.willDismiss = { + dismissed() + } + controller.navigationPresentation = .flatModal + controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + return (controller, nil) } - controller.willDismiss = { - dismissed() - } - controller.navigationPresentation = .flatModal - controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - return controller } @@ -3961,12 +4032,13 @@ public func coverMediaPickerController( return controller } -private class SelectedButtonNode: HighlightTrackingButtonNode { +private class SelectedButtonNode: ASDisplayNode { private let containerView: UIView private let backgroundView: GlassBackgroundView? private let background: ASImageNode? private let icon = ASImageNode() private let label = ImmediateAnimatedCountLabelNode() + private let button = HighlightTrackingButton() private let glass: Bool @@ -3982,6 +4054,8 @@ private class SelectedButtonNode: HighlightTrackingButtonNode { private var count: Int32 = 0 + var action: () -> Void = {} + init(theme: PresentationTheme, glass: Bool) { self.theme = theme self.glass = glass @@ -4007,6 +4081,9 @@ private class SelectedButtonNode: HighlightTrackingButtonNode { self.view.addSubview(self.containerView) if let backgroundView = self.backgroundView { + backgroundView.contentView.addSubnode(self.icon) + backgroundView.contentView.addSubnode(self.label) + backgroundView.contentView.addSubview(self.button) self.containerView.addSubview(backgroundView) } if let background = self.background { @@ -4014,35 +4091,19 @@ private class SelectedButtonNode: HighlightTrackingButtonNode { self.containerView.addSubnode(background) } - self.containerView.addSubnode(self.icon) - self.containerView.addSubnode(self.label) + - self.highligthedChanged = { [weak self] highlighted in - if let self { - if glass { - let transition = ComponentTransition(animation: .curve(duration: highlighted ? 0.25 : 0.35, curve: .spring)) - if highlighted { - transition.setScale(view: self.containerView, scale: 1.2) - } else { - transition.setScale(view: self.containerView, scale: 1.0) - } - } else { - if highlighted { - self.containerView.layer.removeAnimation(forKey: "opacity") - self.containerView.alpha = 0.4 - } else { - self.containerView.alpha = 1.0 - self.containerView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } + self.button.addTarget(self, action: #selector(self.tapped), for: .touchUpInside) + } + + @objc private func tapped() { + self.action() } func update(count: Int32) -> CGSize { self.count = count - let diameter: CGFloat = self.glass ? 40.0 : 21.0 + let diameter: CGFloat = self.glass ? 44.0 : 21.0 let font = self.glass ? Font.with(size: 17.0, weight: .medium, traits: [.monospacedNumbers]) : Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]) let stringValue = "\(max(1, count))" @@ -4078,11 +4139,13 @@ private class SelectedButtonNode: HighlightTrackingButtonNode { self.containerView.frame = backgroundFrame if let backgroundView = self.backgroundView { backgroundView.frame = backgroundFrame - backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.size.height * 0.5, isDark: false, tintColor: .init(kind: .custom, color: self.theme.list.itemCheckColors.fillColor), transition: .immediate) + backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.size.height * 0.5, isDark: false, tintColor: .init(kind: .custom, color: self.theme.list.itemCheckColors.fillColor), isInteractive: true, transition: .immediate) } if let background = self.background { background.frame = backgroundFrame } + + self.button.frame = CGRect(origin: .zero, size: size) return size } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift b/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift index aaaba5dc..cbb89912 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift @@ -18,7 +18,7 @@ final class MediaPickerTitleView: UIView { public var theme: PresentationTheme { didSet { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.isDark ? .white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.secondaryTextColor) self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme)) if self.glass { @@ -31,7 +31,7 @@ final class MediaPickerTitleView: UIView { public var isDark: Bool = false { didSet { if self.isDark != oldValue { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.isDark ? .white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.secondaryTextColor) if self.glass { self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/TitleExpand"), color: self.isDark ? UIColor.white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.4)) @@ -44,7 +44,7 @@ final class MediaPickerTitleView: UIView { public var title: String = "" { didSet { if self.title != oldValue { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor) self.setNeedsLayout() } } @@ -260,7 +260,7 @@ final class MediaPickerTitleView: UIView { totalHeight += subtitleSize.height } - let verticalOffset: CGFloat = self.glass ? 3.0 : 0.0 + let verticalOffset: CGFloat = self.glass ? -1.0 : 0.0 let arrowOffset: CGFloat = self.glass ? 1.0 : 5.0 var totalWidth = titleSize.width diff --git a/submodules/MtProtoKit/Sources/MTApiEnvironment.m b/submodules/MtProtoKit/Sources/MTApiEnvironment.m index e4d60104..14197ec5 100644 --- a/submodules/MtProtoKit/Sources/MTApiEnvironment.m +++ b/submodules/MtProtoKit/Sources/MTApiEnvironment.m @@ -1,7 +1,7 @@ #import #if TARGET_OS_IPHONE -# import +#import #else #endif @@ -10,888 +10,996 @@ #import -static NSData * _Nullable parseHexString(NSString * _Nonnull hex) { - if ([hex length] % 2 != 0) { - return nil; +static NSData *_Nullable parseHexString(NSString *_Nonnull hex) { + if ([hex length] % 2 != 0) { + return nil; + } + char buf[3]; + buf[2] = '\0'; + uint8_t *bytes = (uint8_t *)malloc(hex.length / 2); + uint8_t *bp = bytes; + for (CFIndex i = 0; i < [hex length]; i += 2) { + buf[0] = [hex characterAtIndex:i]; + buf[1] = [hex characterAtIndex:i + 1]; + char *b2 = NULL; + *bp++ = strtol(buf, &b2, 16); + if (b2 != buf + 2) { + return nil; } - char buf[3]; - buf[2] = '\0'; - uint8_t *bytes = (uint8_t *)malloc(hex.length / 2); - uint8_t *bp = bytes; - for (CFIndex i = 0; i < [hex length]; i += 2) { - buf[0] = [hex characterAtIndex:i]; - buf[1] = [hex characterAtIndex:i+1]; - char *b2 = NULL; - *bp++ = strtol(buf, &b2, 16); - if (b2 != buf + 2) { - return nil; - } - } - - return [NSData dataWithBytesNoCopy:bytes length:[hex length]/2 freeWhenDone:YES]; + } + + return [NSData dataWithBytesNoCopy:bytes + length:[hex length] / 2 + freeWhenDone:YES]; } -static NSString * _Nonnull dataToHexString(NSData * _Nonnull data) { - const unsigned char *dataBuffer = (const unsigned char *)[data bytes]; - if (dataBuffer == NULL) { - return @""; - } - - NSUInteger dataLength = [data length]; - NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)]; - - for (int i = 0; i < (int)dataLength; ++i) { - [hexString appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]]; - } - - return hexString; +static NSString *_Nonnull dataToHexString(NSData *_Nonnull data) { + const unsigned char *dataBuffer = (const unsigned char *)[data bytes]; + if (dataBuffer == NULL) { + return @""; + } + + NSUInteger dataLength = [data length]; + NSMutableString *hexString = + [NSMutableString stringWithCapacity:(dataLength * 2)]; + + for (int i = 0; i < (int)dataLength; ++i) { + [hexString + appendString:[NSString stringWithFormat:@"%02lx", + (unsigned long)dataBuffer[i]]]; + } + + return hexString; } static NSData *base64_decode(NSString *str) { - if ([NSData instancesRespondToSelector:@selector(initWithBase64EncodedString:options:)]) { - NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters]; - return data; - } else { + if ([NSData instancesRespondToSelector:@selector + (initWithBase64EncodedString:options:)]) { + NSData *data = [[NSData alloc] + initWithBase64EncodedString:str + options: + NSDataBase64DecodingIgnoreUnknownCharacters]; + return data; + } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [[NSData alloc] initWithBase64Encoding:[str stringByReplacingOccurrencesOfString:@"[^A-Za-z0-9+/=]" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, [str length])]]; + return [[NSData alloc] + initWithBase64Encoding: + [str stringByReplacingOccurrencesOfString:@"[^A-Za-z0-9+/=]" + withString:@"" + options:NSRegularExpressionSearch + range:NSMakeRange( + 0, [str length])]]; #pragma clang diagnostic pop - } + } } @implementation MTProxySecret -- (instancetype _Nullable)initWithSecret:(NSData * _Nonnull)secret { - self = [super init]; - if (self != nil) { - _secret = secret; - } - return self; +- (instancetype _Nullable)initWithSecret:(NSData *_Nonnull)secret { + self = [super init]; + if (self != nil) { + _secret = secret; + } + return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - if (self != nil) { - _secret = [aDecoder decodeObjectForKey:@"secret"]; - } - return self; + self = [super init]; + if (self != nil) { + _secret = [aDecoder decodeObjectForKey:@"secret"]; + } + return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:_secret forKey:@"secret"]; + [aCoder encodeObject:_secret forKey:@"secret"]; } -+ (MTProxySecret * _Nullable)parse:(NSString * _Nonnull)string { - NSData *hexData = parseHexString(string); - if (hexData == nil) { - NSString *finalString = @""; - finalString = [finalString stringByAppendingString:[string stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]]]; - finalString = [finalString stringByReplacingOccurrencesOfString:@"-" withString:@"+"]; - finalString = [finalString stringByReplacingOccurrencesOfString:@"_" withString:@"/"]; - while (finalString.length % 4 != 0) { - finalString = [finalString stringByAppendingString:@"="]; - } - - hexData = base64_decode(finalString); ++ (MTProxySecret *_Nullable)parse:(NSString *_Nonnull)string { + NSData *hexData = parseHexString(string); + if (hexData == nil) { + NSString *finalString = @""; + finalString = [finalString + stringByAppendingString: + [string + stringByTrimmingCharactersInSet: + [NSCharacterSet characterSetWithCharactersInString:@"="]]]; + finalString = [finalString stringByReplacingOccurrencesOfString:@"-" + withString:@"+"]; + finalString = [finalString stringByReplacingOccurrencesOfString:@"_" + withString:@"/"]; + while (finalString.length % 4 != 0) { + finalString = [finalString stringByAppendingString:@"="]; } - if (hexData != nil) { - return [self parseData:hexData]; - } else { - return nil; - } -} -+ (MTProxySecret * _Nullable)parseData:(NSData * _Nonnull)data { - if (data == nil || data.length < 16) { - return nil; - } - - uint8_t firstByte = 0; - [data getBytes:&firstByte length:1]; - - if (data.length == 16) { - return [[MTProxySecretType0 alloc] initWithSecret:data]; - } else if (data.length == 17) { - if (firstByte == 0xdd) { - return [[MTProxySecretType1 alloc] initWithSecret:[data subdataWithRange:NSMakeRange(1, 16)]]; - } else { - return nil; - } - } else if (data.length >= 18 && firstByte == 0xee) { - NSString *domain = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(1 + 16, data.length - (1 + 16))] encoding:NSUTF8StringEncoding]; - if (domain == nil) { - return nil; - } - return [[MTProxySecretType2 alloc] initWithSecret:[data subdataWithRange:NSMakeRange(1, 16)] domain:domain]; - } else { - return nil; - } -} - -- (NSData * _Nonnull)serialize { - assert(false); + hexData = base64_decode(finalString); + } + if (hexData != nil) { + return [self parseData:hexData]; + } else { return nil; + } } -- (NSString * _Nonnull)serializeToString { - assert(false); ++ (MTProxySecret *_Nullable)parseData:(NSData *_Nonnull)data { + if (data == nil || data.length < 16) { return nil; + } + + uint8_t firstByte = 0; + [data getBytes:&firstByte length:1]; + + if (data.length == 16) { + return [[MTProxySecretType0 alloc] initWithSecret:data]; + } else if (data.length == 17) { + if (firstByte == 0xdd) { + return [[MTProxySecretType1 alloc] + initWithSecret:[data subdataWithRange:NSMakeRange(1, 16)]]; + } else { + return nil; + } + } else if (data.length >= 18 && firstByte == 0xee) { + NSString *domain = [[NSString alloc] + initWithData:[data subdataWithRange:NSMakeRange(1 + 16, + data.length - (1 + 16))] + encoding:NSUTF8StringEncoding]; + if (domain == nil) { + return nil; + } + return [[MTProxySecretType2 alloc] + initWithSecret:[data subdataWithRange:NSMakeRange(1, 16)] + domain:domain]; + } else { + return nil; + } +} + +- (NSData *_Nonnull)serialize { + assert(false); + return nil; +} + +- (NSString *_Nonnull)serializeToString { + assert(false); + return nil; } - (NSString *)description { - return dataToHexString([self serialize]); + return dataToHexString([self serialize]); } @end @implementation MTProxySecretType0 -- (instancetype _Nullable)initWithSecret:(NSData * _Nonnull)secret { - self = [super initWithSecret:secret]; - if (self != nil) { - } - return self; +- (instancetype _Nullable)initWithSecret:(NSData *_Nonnull)secret { + self = [super initWithSecret:secret]; + if (self != nil) { + } + return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { - self = [super initWithCoder:aDecoder]; - if (self != nil) { - } - return self; + self = [super initWithCoder:aDecoder]; + if (self != nil) { + } + return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { - [super encodeWithCoder:aCoder]; + [super encodeWithCoder:aCoder]; } -- (NSData * _Nonnull)serialize { - return self.secret; +- (NSData *_Nonnull)serialize { + return self.secret; } -- (NSString * _Nonnull)serializeToString { - return dataToHexString(self.serialize); +- (NSString *_Nonnull)serializeToString { + return dataToHexString(self.serialize); } - (BOOL)isEqual:(id)object { - if (![object isKindOfClass:[MTProxySecretType0 class]]) { - return false; - } - MTProxySecretType0 *other = object; - if (![self.secret isEqual:other.secret]) { - return false; - } - return true; + if (![object isKindOfClass:[MTProxySecretType0 class]]) { + return false; + } + MTProxySecretType0 *other = object; + if (![self.secret isEqual:other.secret]) { + return false; + } + return true; } @end @implementation MTProxySecretType1 -- (instancetype _Nullable)initWithSecret:(NSData * _Nonnull)secret { - self = [super initWithSecret:secret]; - if (self != nil) { - } - return self; +- (instancetype _Nullable)initWithSecret:(NSData *_Nonnull)secret { + self = [super initWithSecret:secret]; + if (self != nil) { + } + return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { - self = [super initWithCoder:aDecoder]; - if (self != nil) { - } - return self; + self = [super initWithCoder:aDecoder]; + if (self != nil) { + } + return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { - [super encodeWithCoder:aCoder]; + [super encodeWithCoder:aCoder]; } -- (NSData * _Nonnull)serialize { - NSMutableData *data = [[NSMutableData alloc] init]; - uint8_t marker = 0xdd; - [data appendBytes:&marker length:1]; - [data appendData:self.secret]; - return data; +- (NSData *_Nonnull)serialize { + NSMutableData *data = [[NSMutableData alloc] init]; + uint8_t marker = 0xdd; + [data appendBytes:&marker length:1]; + [data appendData:self.secret]; + return data; } -- (NSString * _Nonnull)serializeToString { - return dataToHexString(self.serialize); +- (NSString *_Nonnull)serializeToString { + return dataToHexString(self.serialize); } - (BOOL)isEqual:(id)object { - if (![object isKindOfClass:[MTProxySecretType1 class]]) { - return false; - } - MTProxySecretType1 *other = object; - if (![self.secret isEqual:other.secret]) { - return false; - } - return true; + if (![object isKindOfClass:[MTProxySecretType1 class]]) { + return false; + } + MTProxySecretType1 *other = object; + if (![self.secret isEqual:other.secret]) { + return false; + } + return true; } @end @implementation MTProxySecretType2 -- (instancetype _Nullable)initWithSecret:(NSData * _Nonnull)secret domain:(NSString * _Nonnull)domain { - self = [super initWithSecret:secret]; - if (self != nil) { - _domain = domain; - } - return self; +- (instancetype _Nullable)initWithSecret:(NSData *_Nonnull)secret + domain:(NSString *_Nonnull)domain { + self = [super initWithSecret:secret]; + if (self != nil) { + _domain = domain; + } + return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { - self = [super initWithCoder:aDecoder]; - if (self != nil) { - _domain = [aDecoder decodeObjectForKey:@"domain"]; - } - return self; + self = [super initWithCoder:aDecoder]; + if (self != nil) { + _domain = [aDecoder decodeObjectForKey:@"domain"]; + } + return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { - [super encodeWithCoder:aCoder]; - [aCoder encodeObject:_domain forKey:@"domain"]; + [super encodeWithCoder:aCoder]; + [aCoder encodeObject:_domain forKey:@"domain"]; } -- (NSData * _Nonnull)serialize { - NSMutableData *data = [[NSMutableData alloc] init]; - uint8_t marker = 0xee; - [data appendBytes:&marker length:1]; - [data appendData:self.secret]; - [data appendData:[_domain dataUsingEncoding:NSUTF8StringEncoding]]; - return data; +- (NSData *_Nonnull)serialize { + NSMutableData *data = [[NSMutableData alloc] init]; + uint8_t marker = 0xee; + [data appendBytes:&marker length:1]; + [data appendData:self.secret]; + [data appendData:[_domain dataUsingEncoding:NSUTF8StringEncoding]]; + return data; } -- (NSString * _Nonnull)serializeToString { - NSData *data = [self serialize]; - if ([data respondsToSelector:@selector(base64EncodedDataWithOptions:)]) { - return [[data base64EncodedStringWithOptions:kNilOptions] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]]; - } else { +- (NSString *_Nonnull)serializeToString { + NSData *data = [self serialize]; + if ([data respondsToSelector:@selector(base64EncodedDataWithOptions:)]) { + return [[data base64EncodedStringWithOptions:kNilOptions] + stringByTrimmingCharactersInSet: + [NSCharacterSet characterSetWithCharactersInString:@"="]]; + } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return [self.serialize base64Encoding]; #pragma clang diagnostic pop - } + } } - (BOOL)isEqual:(id)object { - if (![object isKindOfClass:[MTProxySecretType2 class]]) { - return false; - } - MTProxySecretType2 *other = object; - if (![self.secret isEqual:other.secret]) { - return false; - } - if (![self.domain isEqual:other.domain]) { - return false; - } - return true; + if (![object isKindOfClass:[MTProxySecretType2 class]]) { + return false; + } + MTProxySecretType2 *other = object; + if (![self.secret isEqual:other.secret]) { + return false; + } + if (![self.domain isEqual:other.domain]) { + return false; + } + return true; } @end @implementation MTSocksProxySettings -- (instancetype)initWithIp:(NSString *)ip port:(uint16_t)port username:(NSString *)username password:(NSString *)password secret:(NSData *)secret { - self = [super init]; - if (self != nil) { - _ip = ip; - _port = port; - _username = username; - _password = password; - _secret = secret; - } - return self; +- (instancetype)initWithIp:(NSString *)ip + port:(uint16_t)port + username:(NSString *)username + password:(NSString *)password + secret:(NSData *)secret { + self = [super init]; + if (self != nil) { + _ip = ip; + _port = port; + _username = username; + _password = password; + _secret = secret; + } + return self; } - (BOOL)isEqual:(id)object { - if (![object isKindOfClass:[MTSocksProxySettings class]]) { - return false; - } - MTSocksProxySettings *other = object; - if ((other->_ip != nil) != (_ip != nil) || (_ip != nil && ![_ip isEqual:other->_ip])) { - return false; - } - if (other->_port != _port) { - return false; - } - if ((other->_username != nil) != (_username != nil) || (_username != nil && ![_username isEqual:other->_username])) { - return false; - } - if ((other->_password != nil) != (_password != nil) || (_password != nil && ![_password isEqual:other->_password])) { - return false; - } - if ((other->_secret != nil) != (_secret != nil) || (_secret != nil && ![_secret isEqual:other->_secret])) { - return false; - } - return true; + if (![object isKindOfClass:[MTSocksProxySettings class]]) { + return false; + } + MTSocksProxySettings *other = object; + if ((other->_ip != nil) != (_ip != nil) || + (_ip != nil && ![_ip isEqual:other->_ip])) { + return false; + } + if (other->_port != _port) { + return false; + } + if ((other->_username != nil) != (_username != nil) || + (_username != nil && ![_username isEqual:other->_username])) { + return false; + } + if ((other->_password != nil) != (_password != nil) || + (_password != nil && ![_password isEqual:other->_password])) { + return false; + } + if ((other->_secret != nil) != (_secret != nil) || + (_secret != nil && ![_secret isEqual:other->_secret])) { + return false; + } + return true; } - (NSString *)description { - return [NSString stringWithFormat:@"%@:%d+%@+%@+%@", _ip, (int)_port, _username, _password, [_secret description]]; + return + [NSString stringWithFormat:@"%@:%d+%@+%@+%@", _ip, (int)_port, _username, + _password, [_secret description]]; } @end @implementation MTNetworkSettings -- (instancetype)initWithReducedBackupDiscoveryTimeout:(bool)reducedBackupDiscoveryTimeout { - self = [super init]; - if (self != nil) { - _reducedBackupDiscoveryTimeout = reducedBackupDiscoveryTimeout; - } - return self; +- (instancetype)initWithReducedBackupDiscoveryTimeout: + (bool)reducedBackupDiscoveryTimeout { + self = [super init]; + if (self != nil) { + _reducedBackupDiscoveryTimeout = reducedBackupDiscoveryTimeout; + } + return self; } - (BOOL)isEqual:(id)object { - if (![object isKindOfClass:[MTNetworkSettings class]]) { - return false; - } - MTNetworkSettings *other = object; - if (_reducedBackupDiscoveryTimeout != other->_reducedBackupDiscoveryTimeout) { - return false; - } - return true; + if (![object isKindOfClass:[MTNetworkSettings class]]) { + return false; + } + MTNetworkSettings *other = object; + if (_reducedBackupDiscoveryTimeout != other->_reducedBackupDiscoveryTimeout) { + return false; + } + return true; } @end @implementation MTApiEnvironment --(instancetype)init { - self = [self initWithDeviceModelName:nil]; - if (self != nil) - { - - } - return self; +- (instancetype)init { + self = [self initWithDeviceModelName:nil]; + if (self != nil) { + } + return self; } --(id _Nonnull)initWithDeviceModelName:(NSString * _Nullable)deviceModelName { - self = [super init]; - if (self != nil) - { - if (deviceModelName != nil) { - _deviceModel = deviceModelName; - } else { - _deviceModel = [self platformString]; - } - _deviceModelName = deviceModelName; -#if TARGET_OS_IPHONE - _systemVersion = [[UIDevice currentDevice] systemVersion]; -#else - NSProcessInfo *pInfo = [NSProcessInfo processInfo]; - _systemVersion = [[[pInfo operatingSystemVersionString] componentsSeparatedByString:@" "] objectAtIndex:1]; -#endif - -NSString *suffix = @""; -#if TARGET_OS_OSX - NSString *value = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"SOURCE"]; - if (value != nil) { - suffix = [NSString stringWithFormat:@"%@", value]; - } -#endif - - //SOURCE - NSString *versionString = [[NSString alloc] initWithFormat:@"%@ (%@) %@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"], [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"], suffix]; - _appVersion = versionString; - - _systemLangCode = [[NSLocale preferredLanguages] objectAtIndex:0]; - #if TARGET_OS_OSX - _langPack = @"macos"; - #else - _langPack = @"ios"; - #endif - _langPackCode = @""; - - [self _updateApiInitializationHash]; +- (id _Nonnull)initWithDeviceModelName:(NSString *_Nullable)deviceModelName { + self = [super init]; + if (self != nil) { + // GHOSTGRAM: Check for device spoofing from UserDefaults + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + BOOL spoofEnabled = [defaults boolForKey:@"DeviceSpoof.isEnabled"]; + NSInteger profileId = + [defaults integerForKey:@"DeviceSpoof.selectedProfileId"]; + + NSString *spoofedModel = nil; + NSString *spoofedVersion = nil; + + if (spoofEnabled && profileId != 0) { + if (profileId == 100) { + // Custom profile + spoofedModel = [defaults stringForKey:@"DeviceSpoof.customDeviceModel"]; + spoofedVersion = + [defaults stringForKey:@"DeviceSpoof.customSystemVersion"]; + } else { + // Preset profiles + NSDictionary *models = @{ + @1 : @"iPhone 14 Pro", + @2 : @"iPhone 15 Pro Max", + @3 : @"Samsung SM-S918B", + @4 : @"Google Pixel 8 Pro", + @5 : @"PC 64bit", + @6 : @"MacBook Pro", + @7 : @"Web", + @8 : @"HUAWEI MNA-LX9", + @9 : @"Xiaomi 2311DRK48G" + }; + NSDictionary *versions = @{ + @1 : @"iOS 17.2", + @2 : @"iOS 17.4", + @3 : @"Android 14", + @4 : @"Android 14", + @5 : @"Windows 11", + @6 : @"macOS 14.3", + @7 : @"Chrome 121", + @8 : @"HarmonyOS 4.0", + @9 : @"Android 14" + }; + spoofedModel = models[@(profileId)]; + spoofedVersion = versions[@(profileId)]; + } } - return self; + + if (spoofedModel.length > 0) { + _deviceModel = spoofedModel; + } else if (deviceModelName != nil) { + _deviceModel = deviceModelName; + } else { + _deviceModel = [self platformString]; + } + _deviceModelName = deviceModelName; + + if (spoofedVersion.length > 0) { + _systemVersion = spoofedVersion; + } else { +#if TARGET_OS_IPHONE + _systemVersion = [[UIDevice currentDevice] systemVersion]; +#else + NSProcessInfo *pInfo = [NSProcessInfo processInfo]; + _systemVersion = [[[pInfo operatingSystemVersionString] + componentsSeparatedByString:@" "] objectAtIndex:1]; +#endif + } + + NSString *suffix = @""; +#if TARGET_OS_OSX + NSString *value = + [[[NSBundle mainBundle] infoDictionary] objectForKey:@"SOURCE"]; + if (value != nil) { + suffix = [NSString stringWithFormat:@"%@", value]; + } +#endif + + // SOURCE + NSString *versionString = [[NSString alloc] + initWithFormat:@"%@ (%@) %@", + [[[NSBundle mainBundle] infoDictionary] + objectForKey:@"CFBundleShortVersionString"], + [[[NSBundle mainBundle] infoDictionary] + objectForKey:@"CFBundleVersion"], + suffix]; + _appVersion = versionString; + + _systemLangCode = [[NSLocale preferredLanguages] objectAtIndex:0]; +#if TARGET_OS_OSX + _langPack = @"macos"; +#else + _langPack = @"ios"; +#endif + _langPackCode = @""; + + [self _updateApiInitializationHash]; + } + return self; } - (void)_updateApiInitializationHash { - _apiInitializationHash = [[NSString alloc] initWithFormat:@"apiId=%" PRId32 "&deviceModel=%@&systemVersion=%@&appVersion=%@&langCode=%@&layer=%@&langPack=%@&langPackCode=%@&proxy=%@&systemCode=%@", _apiId, _deviceModel, _systemVersion, _appVersion, _systemLangCode, _layer, _langPack, _langPackCode, _socksProxySettings, _systemCode]; + _apiInitializationHash = [[NSString alloc] + initWithFormat: + @"apiId=%" PRId32 + "&deviceModel=%@&systemVersion=%@&appVersion=%@&langCode=%@&layer=%@" + "&langPack=%@&langPackCode=%@&proxy=%@&systemCode=%@", + _apiId, _deviceModel, _systemVersion, _appVersion, _systemLangCode, + _layer, _langPack, _langPackCode, _socksProxySettings, _systemCode]; } - (void)setLayer:(NSNumber *)layer { - _layer = layer; - - [self _updateApiInitializationHash]; + _layer = layer; + + [self _updateApiInitializationHash]; } - (void)setAppVersion:(NSString *)appVersion { - _appVersion = appVersion; - - [self _updateApiInitializationHash]; + _appVersion = appVersion; + + [self _updateApiInitializationHash]; } - (void)setLangPack:(NSString *)langPack { - _langPack = langPack; - - [self _updateApiInitializationHash]; + _langPack = langPack; + + [self _updateApiInitializationHash]; } - (void)setLangPackCode:(NSString *)langPackCode { - _langPackCode = langPackCode; - - [self _updateApiInitializationHash]; + _langPackCode = langPackCode; + + [self _updateApiInitializationHash]; } -- (NSString *)platformString -{ +- (NSString *)platformString { #if TARGET_OS_IPHONE - NSString *platform = [self platform]; - - if ([platform isEqualToString:@"iPhone1,1"]) - return @"iPhone"; - if ([platform isEqualToString:@"iPhone1,2"]) - return @"iPhone 3G"; - if ([platform isEqualToString:@"iPhone2,1"]) - return @"iPhone 3GS"; - if ([platform hasPrefix:@"iPhone3"]) - return @"iPhone 4"; - if ([platform hasPrefix:@"iPhone4"]) - return @"iPhone 4S"; - if ([platform isEqualToString:@"iPhone5,1"] || - [platform isEqualToString:@"iPhone5,2"]) - return @"iPhone 5"; - if ([platform isEqualToString:@"iPhone5,3"] || - [platform isEqualToString:@"iPhone5,4"]) - return @"iPhone 5C"; - if ([platform hasPrefix:@"iPhone6"]) - return @"iPhone 5S"; - if ([platform isEqualToString:@"iPhone7,1"]) - return @"iPhone 6 Plus"; - if ([platform isEqualToString:@"iPhone7,2"]) - return @"iPhone 6"; - if ([platform isEqualToString:@"iPhone8,1"]) - return @"iPhone 6S"; - if ([platform isEqualToString:@"iPhone8,2"]) - return @"iPhone 6S Plus"; - if ([platform isEqualToString:@"iPhone8,4"]) - return @"iPhone SE"; - if ([platform isEqualToString:@"iPhone9,1"] || - [platform isEqualToString:@"iPhone9,3"]) - return @"iPhone 7"; - if ([platform isEqualToString:@"iPhone9,2"] || - [platform isEqualToString:@"iPhone9,4"]) - return @"iPhone 7 Plus"; - if ([platform isEqualToString:@"iPhone10,1"] || - [platform isEqualToString:@"iPhone10,4"]) - return @"iPhone 8"; - if ([platform isEqualToString:@"iPhone10,2"] || - [platform isEqualToString:@"iPhone10,5"]) - return @"iPhone 8 Plus"; - if ([platform isEqualToString:@"iPhone10,3"] || - [platform isEqualToString:@"iPhone10,6"]) - return @"iPhone X"; - if ([platform isEqualToString:@"iPhone11,2"]) - return @"iPhone XS"; - if ([platform isEqualToString:@"iPhone11,4"] || - [platform isEqualToString:@"iPhone11,6"]) - return @"iPhone XS Max"; - if ([platform isEqualToString:@"iPhone11,8"]) - return @"iPhone XR"; - if ([platform isEqualToString:@"iPhone12,1"]) - return @"iPhone 11"; - if ([platform isEqualToString:@"iPhone12,3"]) - return @"iPhone 11 Pro"; - if ([platform isEqualToString:@"iPhone12,5"]) - return @"iPhone 11 Pro Max"; - if ([platform isEqualToString:@"iPhone12,8"]) - return @"iPhone SE (2nd gen)"; - if ([platform isEqualToString:@"iPhone13,1"]) - return @"iPhone 12 mini"; - if ([platform isEqualToString:@"iPhone13,2"]) - return @"iPhone 12"; - if ([platform isEqualToString:@"iPhone13,3"]) - return @"iPhone 12 Pro"; - if ([platform isEqualToString:@"iPhone13,4"]) - return @"iPhone 12 Pro Max"; - if ([platform isEqualToString:@"iPhone14,2"]) - return @"iPhone 13 Pro"; - if ([platform isEqualToString:@"iPhone14,3"]) - return @"iPhone 13 Pro Max"; - if ([platform isEqualToString:@"iPhone14,4"]) - return @"iPhone 13 Mini"; - if ([platform isEqualToString:@"iPhone14,5"]) - return @"iPhone 13"; - if ([platform isEqualToString:@"iPhone14,6"]) - return @"iPhone SE (3rd gen)"; - if ([platform isEqualToString:@"iPhone14,7"]) - return @"iPhone 14"; - if ([platform isEqualToString:@"iPhone14,8"]) - return @"iPhone 14 Plus"; - if ([platform isEqualToString:@"iPhone15,2"]) - return @"iPhone 14 Pro"; - if ([platform isEqualToString:@"iPhone15,3"]) - return @"iPhone 14 Pro Max"; - if ([platform isEqualToString:@"iPhone15,4"]) - return @"iPhone 15"; - if ([platform isEqualToString:@"iPhone15,5"]) - return @"iPhone 15 Plus"; - if ([platform isEqualToString:@"iPhone16,1"]) - return @"iPhone 15 Pro"; - if ([platform isEqualToString:@"iPhone16,2"]) - return @"iPhone 15 Pro Max"; - if ([platform isEqualToString:@"iPhone17,3"]) - return @"iPhone 16"; - if ([platform isEqualToString:@"iPhone17,4"]) - return @"iPhone 16 Plus"; - if ([platform isEqualToString:@"iPhone17,1"]) - return @"iPhone 16 Pro"; - if ([platform isEqualToString:@"iPhone17,2"]) - return @"iPhone 16 Pro Max"; - if ([platform isEqualToString:@"iPhone17,5"]) - return @"iPhone 16e"; - if ([platform isEqualToString:@"iPhone18,3"]) - return @"iPhone 17"; - if ([platform isEqualToString:@"iPhone18,1"]) - return @"iPhone 17 Pro"; - if ([platform isEqualToString:@"iPhone18,2"]) - return @"iPhone 17 Pro Max"; - if ([platform isEqualToString:@"iPhone18,4"]) - return @"iPhone Air"; - - if ([platform hasPrefix:@"iPod1"]) - return @"iPod touch 1G"; - if ([platform hasPrefix:@"iPod2"]) - return @"iPod touch 2G"; - if ([platform hasPrefix:@"iPod3"]) - return @"iPod touch 3G"; - if ([platform hasPrefix:@"iPod4"]) - return @"iPod touch 4G"; - if ([platform hasPrefix:@"iPod5"]) - return @"iPod touch 5G"; - if ([platform hasPrefix:@"iPod7"]) - return @"iPod touch 6G"; - if ([platform hasPrefix:@"iPod9"]) - return @"iPod touch 7G"; - - if ([platform isEqualToString:@"iPad2,5"] || - [platform isEqualToString:@"iPad2,6"] || - [platform isEqualToString:@"iPad2,7"]) - return @"iPad mini"; - - if ([platform hasPrefix:@"iPad2"]) - return @"iPad 2G"; - - if ([platform isEqualToString:@"iPad3,1"] || - [platform isEqualToString:@"iPad3,2"] || - [platform isEqualToString:@"iPad3,3"]) - return @"iPad 3G"; - - if ([platform isEqualToString:@"iPad3,4"] || - [platform isEqualToString:@"iPad3,5"] || - [platform isEqualToString:@"iPad3,6"]) - return @"iPad 3G"; - - if ([platform isEqualToString:@"iPad4,1"] || - [platform isEqualToString:@"iPad4,2"]) - return @"iPad Air"; - - if ([platform isEqualToString:@"iPad4,4"] || - [platform isEqualToString:@"iPad4,5"] || - [platform isEqualToString:@"iPad4,6"]) - return @"iPad mini Retina"; - - if ([platform isEqualToString:@"iPad4,7"] || - [platform isEqualToString:@"iPad4,8"] || - [platform isEqualToString:@"iPad4,9"]) - return @"iPad mini 3"; - - if ([platform isEqualToString:@"iPad5,1"] || - [platform isEqualToString:@"iPad5,2"]) - return @"iPad mini 4"; - - if ([platform isEqualToString:@"iPad5,3"] || - [platform isEqualToString:@"iPad5,4"]) - return @"iPad Air 2"; - - if ([platform isEqualToString:@"iPad6,3"] || - [platform isEqualToString:@"iPad6,4"]) - return @"iPad Pro 9.7 inch"; - - if ([platform isEqualToString:@"iPad6,7"] || - [platform isEqualToString:@"iPad6,8"]) - return @"iPad Pro 12.9 inch"; - - if ([platform isEqualToString:@"iPad6,11"] || - [platform isEqualToString:@"iPad6,12"]) - return @"iPad (2017)"; - - if ([platform isEqualToString:@"iPad7,1"] || - [platform isEqualToString:@"iPad7,2"]) - return @"iPad Pro (2nd gen)"; - - if ([platform isEqualToString:@"iPad7,3"] || - [platform isEqualToString:@"iPad7,4"]) - return @"iPad Pro 10.5 inch"; - - if ([platform isEqualToString:@"iPad7,5"] || - [platform isEqualToString:@"iPad7,6"]) - return @"iPad (6th gen)"; - - if ([platform isEqualToString:@"iPad7,11"] || - [platform isEqualToString:@"iPad7,12"]) - return @"iPad 10.2 inch (7th gen)"; - - if ([platform isEqualToString:@"iPad8,1"] || - [platform isEqualToString:@"iPad8,2"] || - [platform isEqualToString:@"iPad8,3"] || - [platform isEqualToString:@"iPad8,4"]) - return @"iPad Pro 11 inch"; - - if ([platform isEqualToString:@"iPad8,5"] || - [platform isEqualToString:@"iPad8,6"] || - [platform isEqualToString:@"iPad8,7"] || - [platform isEqualToString:@"iPad8,8"]) - return @"iPad Pro 12.9 inch (3rd gen)"; - - if ([platform isEqualToString:@"iPad8,9"] || - [platform isEqualToString:@"iPad8,10"]) - return @"iPad Pro 11 inch (2th gen)"; - - if ([platform isEqualToString:@"iPad8,11"] || - [platform isEqualToString:@"iPad8,12"]) - return @"iPad Pro 12.9 inch (4th gen)"; - - if ([platform isEqualToString:@"iPad11,1"] || - [platform isEqualToString:@"iPad11,2"]) - return @"iPad mini (5th gen)"; - - if ([platform isEqualToString:@"iPad11,3"] || - [platform isEqualToString:@"iPad11,4"]) - return @"iPad Air (3rd gen)"; - - if ([platform isEqualToString:@"iPad11,6"] || - [platform isEqualToString:@"iPad11,7"]) - return @"iPad (8th gen)"; - - if ([platform isEqualToString:@"iPad12,1"] || - [platform isEqualToString:@"iPad12,2"]) - return @"iPad (9th gen)"; - - if ([platform isEqualToString:@"iPad13,1"] || - [platform isEqualToString:@"iPad13,2"]) - return @"iPad Air (4th gen)"; - - if ([platform isEqualToString:@"iPad13,4"] || - [platform isEqualToString:@"iPad13,5"] || - [platform isEqualToString:@"iPad13,6"] || - [platform isEqualToString:@"iPad13,7"]) - return @"iPad Pro 11 inch (3th gen)"; - - if ([platform isEqualToString:@"iPad13,8"] || - [platform isEqualToString:@"iPad13,9"] || - [platform isEqualToString:@"iPad13,10"] || - [platform isEqualToString:@"iPad13,11"]) - return @"iPad Pro 12.9 inch (5th gen)"; - - if ([platform isEqualToString:@"iPad13,16"] || - [platform isEqualToString:@"iPad13,17"]) - return @"iPad Air (5th gen)"; - - if ([platform isEqualToString:@"iPad13,18"] || - [platform isEqualToString:@"iPad13,19"]) - return @"iPad (10th gen)"; - - if ([platform isEqualToString:@"iPad14,1"] || - [platform isEqualToString:@"iPad14,2"]) - return @"iPad mini (6th gen)"; - - if ([platform isEqualToString:@"iPad14,3"] || - [platform isEqualToString:@"iPad14,4"]) - return @"iPad Pro 11 inch (4th gen)"; - - if ([platform isEqualToString:@"iPad14,5"] || - [platform isEqualToString:@"iPad14,6"]) - return @"iPad Pro 12.9 inch (6th gen)"; - - if ([platform isEqualToString:@"iPad14,8"] || - [platform isEqualToString:@"iPad14,9"]) - return @"iPad Air (6th gen)"; - - if ([platform isEqualToString:@"iPad14,10"] || - [platform isEqualToString:@"iPad14,11"]) - return @"iPad Air (7th gen)"; - - if ([platform isEqualToString:@"iPad16,3"] || - [platform isEqualToString:@"iPad16,4"]) - return @"iPad Pro 11 inch (5th gen)"; - - if ([platform isEqualToString:@"iPad16,5"] || - [platform isEqualToString:@"iPad16,6"]) - return @"iPad Pro 12.9 inch (7th gen)"; - - if ([platform hasPrefix:@"iPhone"]) - return @"Unknown iPhone"; - if ([platform hasPrefix:@"iPod"]) - return @"Unknown iPod"; - if ([platform hasPrefix:@"iPad"]) - return @"Unknown iPad"; - - if ([platform hasSuffix:@"86"] || [platform isEqual:@"x86_64"] || [platform isEqual:@"arm64"]) { - return @"iPhone Simulator"; - } + NSString *platform = [self platform]; + + if ([platform isEqualToString:@"iPhone1,1"]) + return @"iPhone"; + if ([platform isEqualToString:@"iPhone1,2"]) + return @"iPhone 3G"; + if ([platform isEqualToString:@"iPhone2,1"]) + return @"iPhone 3GS"; + if ([platform hasPrefix:@"iPhone3"]) + return @"iPhone 4"; + if ([platform hasPrefix:@"iPhone4"]) + return @"iPhone 4S"; + if ([platform isEqualToString:@"iPhone5,1"] || + [platform isEqualToString:@"iPhone5,2"]) + return @"iPhone 5"; + if ([platform isEqualToString:@"iPhone5,3"] || + [platform isEqualToString:@"iPhone5,4"]) + return @"iPhone 5C"; + if ([platform hasPrefix:@"iPhone6"]) + return @"iPhone 5S"; + if ([platform isEqualToString:@"iPhone7,1"]) + return @"iPhone 6 Plus"; + if ([platform isEqualToString:@"iPhone7,2"]) + return @"iPhone 6"; + if ([platform isEqualToString:@"iPhone8,1"]) + return @"iPhone 6S"; + if ([platform isEqualToString:@"iPhone8,2"]) + return @"iPhone 6S Plus"; + if ([platform isEqualToString:@"iPhone8,4"]) + return @"iPhone SE"; + if ([platform isEqualToString:@"iPhone9,1"] || + [platform isEqualToString:@"iPhone9,3"]) + return @"iPhone 7"; + if ([platform isEqualToString:@"iPhone9,2"] || + [platform isEqualToString:@"iPhone9,4"]) + return @"iPhone 7 Plus"; + if ([platform isEqualToString:@"iPhone10,1"] || + [platform isEqualToString:@"iPhone10,4"]) + return @"iPhone 8"; + if ([platform isEqualToString:@"iPhone10,2"] || + [platform isEqualToString:@"iPhone10,5"]) + return @"iPhone 8 Plus"; + if ([platform isEqualToString:@"iPhone10,3"] || + [platform isEqualToString:@"iPhone10,6"]) + return @"iPhone X"; + if ([platform isEqualToString:@"iPhone11,2"]) + return @"iPhone XS"; + if ([platform isEqualToString:@"iPhone11,4"] || + [platform isEqualToString:@"iPhone11,6"]) + return @"iPhone XS Max"; + if ([platform isEqualToString:@"iPhone11,8"]) + return @"iPhone XR"; + if ([platform isEqualToString:@"iPhone12,1"]) + return @"iPhone 11"; + if ([platform isEqualToString:@"iPhone12,3"]) + return @"iPhone 11 Pro"; + if ([platform isEqualToString:@"iPhone12,5"]) + return @"iPhone 11 Pro Max"; + if ([platform isEqualToString:@"iPhone12,8"]) + return @"iPhone SE (2nd gen)"; + if ([platform isEqualToString:@"iPhone13,1"]) + return @"iPhone 12 mini"; + if ([platform isEqualToString:@"iPhone13,2"]) + return @"iPhone 12"; + if ([platform isEqualToString:@"iPhone13,3"]) + return @"iPhone 12 Pro"; + if ([platform isEqualToString:@"iPhone13,4"]) + return @"iPhone 12 Pro Max"; + if ([platform isEqualToString:@"iPhone14,2"]) + return @"iPhone 13 Pro"; + if ([platform isEqualToString:@"iPhone14,3"]) + return @"iPhone 13 Pro Max"; + if ([platform isEqualToString:@"iPhone14,4"]) + return @"iPhone 13 Mini"; + if ([platform isEqualToString:@"iPhone14,5"]) + return @"iPhone 13"; + if ([platform isEqualToString:@"iPhone14,6"]) + return @"iPhone SE (3rd gen)"; + if ([platform isEqualToString:@"iPhone14,7"]) + return @"iPhone 14"; + if ([platform isEqualToString:@"iPhone14,8"]) + return @"iPhone 14 Plus"; + if ([platform isEqualToString:@"iPhone15,2"]) + return @"iPhone 14 Pro"; + if ([platform isEqualToString:@"iPhone15,3"]) + return @"iPhone 14 Pro Max"; + if ([platform isEqualToString:@"iPhone15,4"]) + return @"iPhone 15"; + if ([platform isEqualToString:@"iPhone15,5"]) + return @"iPhone 15 Plus"; + if ([platform isEqualToString:@"iPhone16,1"]) + return @"iPhone 15 Pro"; + if ([platform isEqualToString:@"iPhone16,2"]) + return @"iPhone 15 Pro Max"; + if ([platform isEqualToString:@"iPhone17,3"]) + return @"iPhone 16"; + if ([platform isEqualToString:@"iPhone17,4"]) + return @"iPhone 16 Plus"; + if ([platform isEqualToString:@"iPhone17,1"]) + return @"iPhone 16 Pro"; + if ([platform isEqualToString:@"iPhone17,2"]) + return @"iPhone 16 Pro Max"; + if ([platform isEqualToString:@"iPhone17,5"]) + return @"iPhone 16e"; + if ([platform isEqualToString:@"iPhone18,3"]) + return @"iPhone 17"; + if ([platform isEqualToString:@"iPhone18,1"]) + return @"iPhone 17 Pro"; + if ([platform isEqualToString:@"iPhone18,2"]) + return @"iPhone 17 Pro Max"; + if ([platform isEqualToString:@"iPhone18,4"]) + return @"iPhone Air"; + + if ([platform hasPrefix:@"iPod1"]) + return @"iPod touch 1G"; + if ([platform hasPrefix:@"iPod2"]) + return @"iPod touch 2G"; + if ([platform hasPrefix:@"iPod3"]) + return @"iPod touch 3G"; + if ([platform hasPrefix:@"iPod4"]) + return @"iPod touch 4G"; + if ([platform hasPrefix:@"iPod5"]) + return @"iPod touch 5G"; + if ([platform hasPrefix:@"iPod7"]) + return @"iPod touch 6G"; + if ([platform hasPrefix:@"iPod9"]) + return @"iPod touch 7G"; + + if ([platform isEqualToString:@"iPad2,5"] || + [platform isEqualToString:@"iPad2,6"] || + [platform isEqualToString:@"iPad2,7"]) + return @"iPad mini"; + + if ([platform hasPrefix:@"iPad2"]) + return @"iPad 2G"; + + if ([platform isEqualToString:@"iPad3,1"] || + [platform isEqualToString:@"iPad3,2"] || + [platform isEqualToString:@"iPad3,3"]) + return @"iPad 3G"; + + if ([platform isEqualToString:@"iPad3,4"] || + [platform isEqualToString:@"iPad3,5"] || + [platform isEqualToString:@"iPad3,6"]) + return @"iPad 3G"; + + if ([platform isEqualToString:@"iPad4,1"] || + [platform isEqualToString:@"iPad4,2"]) + return @"iPad Air"; + + if ([platform isEqualToString:@"iPad4,4"] || + [platform isEqualToString:@"iPad4,5"] || + [platform isEqualToString:@"iPad4,6"]) + return @"iPad mini Retina"; + + if ([platform isEqualToString:@"iPad4,7"] || + [platform isEqualToString:@"iPad4,8"] || + [platform isEqualToString:@"iPad4,9"]) + return @"iPad mini 3"; + + if ([platform isEqualToString:@"iPad5,1"] || + [platform isEqualToString:@"iPad5,2"]) + return @"iPad mini 4"; + + if ([platform isEqualToString:@"iPad5,3"] || + [platform isEqualToString:@"iPad5,4"]) + return @"iPad Air 2"; + + if ([platform isEqualToString:@"iPad6,3"] || + [platform isEqualToString:@"iPad6,4"]) + return @"iPad Pro 9.7 inch"; + + if ([platform isEqualToString:@"iPad6,7"] || + [platform isEqualToString:@"iPad6,8"]) + return @"iPad Pro 12.9 inch"; + + if ([platform isEqualToString:@"iPad6,11"] || + [platform isEqualToString:@"iPad6,12"]) + return @"iPad (2017)"; + + if ([platform isEqualToString:@"iPad7,1"] || + [platform isEqualToString:@"iPad7,2"]) + return @"iPad Pro (2nd gen)"; + + if ([platform isEqualToString:@"iPad7,3"] || + [platform isEqualToString:@"iPad7,4"]) + return @"iPad Pro 10.5 inch"; + + if ([platform isEqualToString:@"iPad7,5"] || + [platform isEqualToString:@"iPad7,6"]) + return @"iPad (6th gen)"; + + if ([platform isEqualToString:@"iPad7,11"] || + [platform isEqualToString:@"iPad7,12"]) + return @"iPad 10.2 inch (7th gen)"; + + if ([platform isEqualToString:@"iPad8,1"] || + [platform isEqualToString:@"iPad8,2"] || + [platform isEqualToString:@"iPad8,3"] || + [platform isEqualToString:@"iPad8,4"]) + return @"iPad Pro 11 inch"; + + if ([platform isEqualToString:@"iPad8,5"] || + [platform isEqualToString:@"iPad8,6"] || + [platform isEqualToString:@"iPad8,7"] || + [platform isEqualToString:@"iPad8,8"]) + return @"iPad Pro 12.9 inch (3rd gen)"; + + if ([platform isEqualToString:@"iPad8,9"] || + [platform isEqualToString:@"iPad8,10"]) + return @"iPad Pro 11 inch (2th gen)"; + + if ([platform isEqualToString:@"iPad8,11"] || + [platform isEqualToString:@"iPad8,12"]) + return @"iPad Pro 12.9 inch (4th gen)"; + + if ([platform isEqualToString:@"iPad11,1"] || + [platform isEqualToString:@"iPad11,2"]) + return @"iPad mini (5th gen)"; + + if ([platform isEqualToString:@"iPad11,3"] || + [platform isEqualToString:@"iPad11,4"]) + return @"iPad Air (3rd gen)"; + + if ([platform isEqualToString:@"iPad11,6"] || + [platform isEqualToString:@"iPad11,7"]) + return @"iPad (8th gen)"; + + if ([platform isEqualToString:@"iPad12,1"] || + [platform isEqualToString:@"iPad12,2"]) + return @"iPad (9th gen)"; + + if ([platform isEqualToString:@"iPad13,1"] || + [platform isEqualToString:@"iPad13,2"]) + return @"iPad Air (4th gen)"; + + if ([platform isEqualToString:@"iPad13,4"] || + [platform isEqualToString:@"iPad13,5"] || + [platform isEqualToString:@"iPad13,6"] || + [platform isEqualToString:@"iPad13,7"]) + return @"iPad Pro 11 inch (3th gen)"; + + if ([platform isEqualToString:@"iPad13,8"] || + [platform isEqualToString:@"iPad13,9"] || + [platform isEqualToString:@"iPad13,10"] || + [platform isEqualToString:@"iPad13,11"]) + return @"iPad Pro 12.9 inch (5th gen)"; + + if ([platform isEqualToString:@"iPad13,16"] || + [platform isEqualToString:@"iPad13,17"]) + return @"iPad Air (5th gen)"; + + if ([platform isEqualToString:@"iPad13,18"] || + [platform isEqualToString:@"iPad13,19"]) + return @"iPad (10th gen)"; + + if ([platform isEqualToString:@"iPad14,1"] || + [platform isEqualToString:@"iPad14,2"]) + return @"iPad mini (6th gen)"; + + if ([platform isEqualToString:@"iPad14,3"] || + [platform isEqualToString:@"iPad14,4"]) + return @"iPad Pro 11 inch (4th gen)"; + + if ([platform isEqualToString:@"iPad14,5"] || + [platform isEqualToString:@"iPad14,6"]) + return @"iPad Pro 12.9 inch (6th gen)"; + + if ([platform isEqualToString:@"iPad14,8"] || + [platform isEqualToString:@"iPad14,9"]) + return @"iPad Air (6th gen)"; + + if ([platform isEqualToString:@"iPad14,10"] || + [platform isEqualToString:@"iPad14,11"]) + return @"iPad Air (7th gen)"; + + if ([platform isEqualToString:@"iPad16,3"] || + [platform isEqualToString:@"iPad16,4"]) + return @"iPad Pro 11 inch (5th gen)"; + + if ([platform isEqualToString:@"iPad16,5"] || + [platform isEqualToString:@"iPad16,6"]) + return @"iPad Pro 12.9 inch (7th gen)"; + + if ([platform hasPrefix:@"iPhone"]) + return @"Unknown iPhone"; + if ([platform hasPrefix:@"iPod"]) + return @"Unknown iPod"; + if ([platform hasPrefix:@"iPad"]) + return @"Unknown iPad"; + + if ([platform hasSuffix:@"86"] || [platform isEqual:@"x86_64"] || + [platform isEqual:@"arm64"]) { + return @"iPhone Simulator"; + } #else - return [self macHWName]; + return [self macHWName]; #endif - - return @"Unknown iOS device"; + + return @"Unknown iOS device"; } - + - (NSString *)macHWName { - size_t len = 0; - sysctlbyname("hw.model", NULL, &len, NULL, 0); - if (len) { - char *model = malloc(len*sizeof(char)); - sysctlbyname("hw.model", model, &len, NULL, 0); - NSString *name = [[NSString alloc] initWithUTF8String:model]; - free(model); - return name; - }; - return @"macOS"; + size_t len = 0; + sysctlbyname("hw.model", NULL, &len, NULL, 0); + if (len) { + char *model = malloc(len * sizeof(char)); + sysctlbyname("hw.model", model, &len, NULL, 0); + NSString *name = [[NSString alloc] initWithUTF8String:model]; + free(model); + return name; + }; + return @"macOS"; } -- (NSString *)getSysInfoByName:(char *)typeSpecifier -{ - size_t size; - sysctlbyname(typeSpecifier, NULL, &size, NULL, 0); - - char *answer = malloc(size); - sysctlbyname(typeSpecifier, answer, &size, NULL, 0); - - NSString *results = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding]; - - free(answer); - return results; +- (NSString *)getSysInfoByName:(char *)typeSpecifier { + size_t size; + sysctlbyname(typeSpecifier, NULL, &size, NULL, 0); + + char *answer = malloc(size); + sysctlbyname(typeSpecifier, answer, &size, NULL, 0); + + NSString *results = [NSString stringWithCString:answer + encoding:NSUTF8StringEncoding]; + + free(answer); + return results; } -- (NSString *)platform -{ - return [self getSysInfoByName:"hw.machine"]; +- (NSString *)platform { + return [self getSysInfoByName:"hw.machine"]; } - (MTApiEnvironment *)withUpdatedLangPackCode:(NSString *)langPackCode { - MTApiEnvironment *result = [[MTApiEnvironment alloc] initWithDeviceModelName:_deviceModelName]; - - result.apiId = self.apiId; - result.appVersion = self.appVersion; - result.layer = self.layer; - - result.langPack = self.langPack; - - result->_langPackCode = langPackCode; - - result.disableUpdates = self.disableUpdates; - result.tcpPayloadPrefix = self.tcpPayloadPrefix; - result.datacenterAddressOverrides = self.datacenterAddressOverrides; - result.accessHostOverride = self.accessHostOverride; - result->_socksProxySettings = self.socksProxySettings; - result->_networkSettings = self.networkSettings; - result->_systemCode = self.systemCode; - - [result _updateApiInitializationHash]; - - return result; + MTApiEnvironment *result = + [[MTApiEnvironment alloc] initWithDeviceModelName:_deviceModelName]; + + result.apiId = self.apiId; + result.appVersion = self.appVersion; + result.layer = self.layer; + + result.langPack = self.langPack; + + result->_langPackCode = langPackCode; + + result.disableUpdates = self.disableUpdates; + result.tcpPayloadPrefix = self.tcpPayloadPrefix; + result.datacenterAddressOverrides = self.datacenterAddressOverrides; + result.accessHostOverride = self.accessHostOverride; + result->_socksProxySettings = self.socksProxySettings; + result->_networkSettings = self.networkSettings; + result->_systemCode = self.systemCode; + + [result _updateApiInitializationHash]; + + return result; } - (instancetype)copyWithZone:(NSZone *)__unused zone { - MTApiEnvironment *result = [[MTApiEnvironment alloc] initWithDeviceModelName:_deviceModelName]; - - result.apiId = self.apiId; - result.appVersion = self.appVersion; - result.layer = self.layer; - - result.langPack = self.langPack; - - result->_langPackCode = self.langPackCode; - result->_socksProxySettings = self.socksProxySettings; - result->_networkSettings = self.networkSettings; - result->_systemCode = self.systemCode; - - result.disableUpdates = self.disableUpdates; - result.tcpPayloadPrefix = self.tcpPayloadPrefix; - result.datacenterAddressOverrides = self.datacenterAddressOverrides; - result.accessHostOverride = self.accessHostOverride; - - [result _updateApiInitializationHash]; - - return result; + MTApiEnvironment *result = + [[MTApiEnvironment alloc] initWithDeviceModelName:_deviceModelName]; + + result.apiId = self.apiId; + result.appVersion = self.appVersion; + result.layer = self.layer; + + result.langPack = self.langPack; + + result->_langPackCode = self.langPackCode; + result->_socksProxySettings = self.socksProxySettings; + result->_networkSettings = self.networkSettings; + result->_systemCode = self.systemCode; + + result.disableUpdates = self.disableUpdates; + result.tcpPayloadPrefix = self.tcpPayloadPrefix; + result.datacenterAddressOverrides = self.datacenterAddressOverrides; + result.accessHostOverride = self.accessHostOverride; + + [result _updateApiInitializationHash]; + + return result; } -- (MTApiEnvironment *)withUpdatedSocksProxySettings:(MTSocksProxySettings *)socksProxySettings { - MTApiEnvironment *result = [[MTApiEnvironment alloc] initWithDeviceModelName:_deviceModelName]; - - result.apiId = self.apiId; - result.appVersion = self.appVersion; - result.layer = self.layer; - - result.langPack = self.langPack; - - result->_langPackCode = self.langPackCode; - result->_socksProxySettings = socksProxySettings; - result->_networkSettings = self.networkSettings; - result->_systemCode = self.systemCode; - - result.disableUpdates = self.disableUpdates; - result.tcpPayloadPrefix = self.tcpPayloadPrefix; - result.datacenterAddressOverrides = self.datacenterAddressOverrides; - result.accessHostOverride = self.accessHostOverride; - - [result _updateApiInitializationHash]; - - return result; +- (MTApiEnvironment *)withUpdatedSocksProxySettings: + (MTSocksProxySettings *)socksProxySettings { + MTApiEnvironment *result = + [[MTApiEnvironment alloc] initWithDeviceModelName:_deviceModelName]; + + result.apiId = self.apiId; + result.appVersion = self.appVersion; + result.layer = self.layer; + + result.langPack = self.langPack; + + result->_langPackCode = self.langPackCode; + result->_socksProxySettings = socksProxySettings; + result->_networkSettings = self.networkSettings; + result->_systemCode = self.systemCode; + + result.disableUpdates = self.disableUpdates; + result.tcpPayloadPrefix = self.tcpPayloadPrefix; + result.datacenterAddressOverrides = self.datacenterAddressOverrides; + result.accessHostOverride = self.accessHostOverride; + + [result _updateApiInitializationHash]; + + return result; } -- (MTApiEnvironment *)withUpdatedNetworkSettings:(MTNetworkSettings *)networkSettings { - MTApiEnvironment *result = [[MTApiEnvironment alloc] initWithDeviceModelName:_deviceModelName]; - - result.apiId = self.apiId; - result.appVersion = self.appVersion; - result.layer = self.layer; - - result.langPack = self.langPack; - - result->_langPackCode = self.langPackCode; - result->_socksProxySettings = self.socksProxySettings; - result->_networkSettings = networkSettings; - result->_systemCode = self.systemCode; - - result.disableUpdates = self.disableUpdates; - result.tcpPayloadPrefix = self.tcpPayloadPrefix; - result.datacenterAddressOverrides = self.datacenterAddressOverrides; - result.accessHostOverride = self.accessHostOverride; - - [result _updateApiInitializationHash]; - - return result; +- (MTApiEnvironment *)withUpdatedNetworkSettings: + (MTNetworkSettings *)networkSettings { + MTApiEnvironment *result = + [[MTApiEnvironment alloc] initWithDeviceModelName:_deviceModelName]; + + result.apiId = self.apiId; + result.appVersion = self.appVersion; + result.layer = self.layer; + + result.langPack = self.langPack; + + result->_langPackCode = self.langPackCode; + result->_socksProxySettings = self.socksProxySettings; + result->_networkSettings = networkSettings; + result->_systemCode = self.systemCode; + + result.disableUpdates = self.disableUpdates; + result.tcpPayloadPrefix = self.tcpPayloadPrefix; + result.datacenterAddressOverrides = self.datacenterAddressOverrides; + result.accessHostOverride = self.accessHostOverride; + + [result _updateApiInitializationHash]; + + return result; } - (MTApiEnvironment *)withUpdatedSystemCode:(NSData *)systemCode { - MTApiEnvironment *result = [[MTApiEnvironment alloc] initWithDeviceModelName:_deviceModelName]; - - result.apiId = self.apiId; - result.appVersion = self.appVersion; - result.layer = self.layer; - - result.langPack = self.langPack; - - result->_langPackCode = self.langPackCode; - result->_socksProxySettings = self.socksProxySettings; - result->_networkSettings = self.networkSettings; - result->_systemCode = systemCode; - - result.disableUpdates = self.disableUpdates; - result.tcpPayloadPrefix = self.tcpPayloadPrefix; - result.datacenterAddressOverrides = self.datacenterAddressOverrides; - result.accessHostOverride = self.accessHostOverride; - - [result _updateApiInitializationHash]; - - return result; + MTApiEnvironment *result = + [[MTApiEnvironment alloc] initWithDeviceModelName:_deviceModelName]; + + result.apiId = self.apiId; + result.appVersion = self.appVersion; + result.layer = self.layer; + + result.langPack = self.langPack; + + result->_langPackCode = self.langPackCode; + result->_socksProxySettings = self.socksProxySettings; + result->_networkSettings = self.networkSettings; + result->_systemCode = systemCode; + + result.disableUpdates = self.disableUpdates; + result.tcpPayloadPrefix = self.tcpPayloadPrefix; + result.datacenterAddressOverrides = self.datacenterAddressOverrides; + result.accessHostOverride = self.accessHostOverride; + + [result _updateApiInitializationHash]; + + return result; } @end - diff --git a/submodules/MtProtoKit/Sources/MTNetworkAvailability.m b/submodules/MtProtoKit/Sources/MTNetworkAvailability.m index 47f45844..3cdbfc6b 100644 --- a/submodules/MtProtoKit/Sources/MTNetworkAvailability.m +++ b/submodules/MtProtoKit/Sources/MTNetworkAvailability.m @@ -62,6 +62,8 @@ static void MTNetworkAvailabilityContextRelease(const void *info) zeroAddress.sin_len = sizeof(zeroAddress); zeroAddress.sin_family = AF_INET; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" _reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress); if (_reachability != NULL) { @@ -84,6 +86,7 @@ static void MTNetworkAvailabilityContextRelease(const void *info) if (SCNetworkReachabilitySetCallback(_reachability, &MTAvailabilityCallback, &context)) SCNetworkReachabilitySetDispatchQueue(_reachability, [MTNetworkAvailability networkAvailabilityQueue].nativeQueue); +#pragma clang diagnostic pop } }]; } @@ -101,8 +104,11 @@ static void MTNetworkAvailabilityContextRelease(const void *info) [[MTNetworkAvailability networkAvailabilityQueue] dispatchOnQueue:^{ [timer invalidate]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" SCNetworkReachabilitySetCallback(reachability, NULL, NULL); SCNetworkReachabilitySetDispatchQueue(reachability, NULL); +#pragma clang diagnostic pop CFRelease(reachability); }]; } @@ -126,7 +132,10 @@ static void MTNetworkAvailabilityContextRelease(const void *info) if (_reachability != nil) { SCNetworkReachabilityFlags currentFlags = 0; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" if (SCNetworkReachabilityGetFlags(_reachability, ¤tFlags)) +#pragma clang diagnostic pop [self updateReachability:currentFlags notify:notify]; } }]; diff --git a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift index 4e9432cc..a744c229 100644 --- a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift +++ b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift @@ -455,7 +455,7 @@ public func notificationSoundSelectionController(context: AccountContext, update let presentationData = context.sharedContext.currentPresentationData.with { $0 } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.PeerInfo_DeleteToneTitle, text: presentationData.strings.PeerInfo_DeleteToneText(title).string, actions: [ + controller.present(textAlertController(context: context, title: presentationData.strings.PeerInfo_DeleteToneTitle, text: presentationData.strings.PeerInfo_DeleteToneText(title).string, actions: [ TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: { updateState { state in var state = state diff --git a/submodules/OpusBinding/BUILD b/submodules/OpusBinding/BUILD index f65be5a8..78d27acd 100644 --- a/submodules/OpusBinding/BUILD +++ b/submodules/OpusBinding/BUILD @@ -24,6 +24,8 @@ objc_library( ], sdk_frameworks = [ "Foundation", + "AVFoundation", + "AudioToolbox", ], visibility = [ "//visibility:public", diff --git a/submodules/OpusBinding/PublicHeaders/OpusBinding/OpusBinding.h b/submodules/OpusBinding/PublicHeaders/OpusBinding/OpusBinding.h index 30524dbc..88139968 100644 --- a/submodules/OpusBinding/PublicHeaders/OpusBinding/OpusBinding.h +++ b/submodules/OpusBinding/PublicHeaders/OpusBinding/OpusBinding.h @@ -2,4 +2,5 @@ #import #import -#import \ No newline at end of file +#import +#import \ No newline at end of file diff --git a/submodules/PasscodeUI/Sources/PasscodeSetupController.swift b/submodules/PasscodeUI/Sources/PasscodeSetupController.swift index a4592b80..0c62555e 100644 --- a/submodules/PasscodeUI/Sources/PasscodeSetupController.swift +++ b/submodules/PasscodeUI/Sources/PasscodeSetupController.swift @@ -34,7 +34,7 @@ public final class PasscodeSetupController: ViewController { self.mode = mode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style diff --git a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift index 9427aa0c..e6c93894 100644 --- a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift +++ b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift @@ -39,7 +39,7 @@ public class SetupTwoStepVerificationController: ViewController { self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -89,7 +89,7 @@ public class SetupTwoStepVerificationController: ViewController { private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData), transition: .immediate) self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.controllerNode.updatePresentationData(self.presentationData) } diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift index eaa84497..54843c67 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift @@ -71,7 +71,7 @@ public final class TwoFactorDataInputScreen: ViewController { self.presentationData = self.sharedContext.currentPresentationData.with { $0 } let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme) - let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor) + let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: defaultTheme.overallDarkAppearance, buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close))) @@ -119,7 +119,7 @@ public final class TwoFactorDataInputScreen: ViewController { return } if values[0] != values[1] { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [ + strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [ TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) ]), in: .window(.root)) return @@ -167,7 +167,7 @@ public final class TwoFactorDataInputScreen: ViewController { text = strongSelf.presentationData.strings.Login_UnknownError } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, completed: { [weak statusController] in statusController?.dismiss() @@ -196,7 +196,7 @@ public final class TwoFactorDataInputScreen: ViewController { return } if values[0] != values[1] { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [ + strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [ TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) ]), in: .window(.root)) return @@ -481,7 +481,7 @@ public final class TwoFactorDataInputScreen: ViewController { } switch strongSelf.mode { case let .emailAddress(password, hint, doneText): - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationTitle, text: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationText, actions: [ + strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationTitle, text: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationText, actions: [ TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.TwoFactorSetup_Email_SkipConfirmationSkip, action: { guard let strongSelf = self else { return @@ -543,7 +543,7 @@ public final class TwoFactorDataInputScreen: ViewController { strongSelf.push(TwoFactorDataInputScreen(sharedContext: strongSelf.sharedContext, engine: strongSelf.engine, mode: .emailAddress(password: password, hint: "", doneText: doneText), stateUpdated: strongSelf.stateUpdated, presentation: strongSelf.navigationPresentation)) } case let .passwordRecovery(recovery, _): - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertTitle, text: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertText, actions: [ + strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertTitle, text: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertText, actions: [ TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.TwoFactorSetup_PasswordRecovery_SkipAlertAction, action: { guard let strongSelf = self else { return diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift index 66e73dba..6b0209ed 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift @@ -57,7 +57,7 @@ public final class TwoFactorAuthSplashScreen: ViewController { self.presentationData = self.sharedContext.currentPresentationData.with { $0 } let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme) - let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor) + let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: defaultTheme.overallDarkAppearance, buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close))) diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 9cf2bfb8..72931c65 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -754,7 +754,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr self.galleryNode.setControlsHidden(true, animated: false) if let centralItemNode = self.galleryNode.pager.centralItemNode(), let itemSize = centralItemNode.contentSize() { self.preferredContentSize = itemSize.aspectFitted(self.view.bounds.size) - self.containerLayoutUpdated(ContainerViewLayout(size: self.preferredContentSize, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) + self.containerLayoutUpdated(ContainerViewLayout(size: self.preferredContentSize, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) centralItemNode.activateAsInitial() } } diff --git a/submodules/PeerInfoUI/BUILD b/submodules/PeerInfoUI/BUILD index 7c57b565..756a3d95 100644 --- a/submodules/PeerInfoUI/BUILD +++ b/submodules/PeerInfoUI/BUILD @@ -80,6 +80,9 @@ swift_library( "//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController", "//submodules/TelegramUI/Components/PeerManagement/OldChannelsController", "//submodules/TelegramUI/Components/PeerInfo/MessagePriceItem", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift index 999385a0..10bf5156 100644 --- a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift +++ b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift @@ -242,7 +242,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index deb1d436..57a6e9a3 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -1091,7 +1091,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD text = presentationData.strings.Channel_EditAdmin_CannotEdit } - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) }) }, transferOwnership: { let _ = (context.engine.data.get( @@ -1104,19 +1104,30 @@ public func channelAdminController(context: AccountContext, updatedPresentationD } transferOwnershipDisposable.set((context.engine.peers.checkOwnershipTranfserAvailability(memberId: adminId) |> deliverOnMainQueue).start(error: { error in - let controller = channelOwnershipTransferController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, member: member, initialError: error, present: { c, a in - presentControllerImpl?(c, a) - }, completion: { upgradedPeerId in - if let upgradedPeerId = upgradedPeerId { - upgradedToSupergroupImpl(upgradedPeerId, { + let controller = channelOwnershipTransferController( + context: context, + updatedPresentationData: updatedPresentationData, + peer: peer, + member: member, + initialError: error, + present: { c, a in + presentControllerImpl?(c, a) + }, + push: { c in + pushControllerImpl?(c) + }, + completion: { upgradedPeerId in + if let upgradedPeerId = upgradedPeerId { + upgradedToSupergroupImpl(upgradedPeerId, { + dismissImpl?() + transferedOwnership(member.id) + }) + } else { dismissImpl?() transferedOwnership(member.id) - }) - } else { - dismissImpl?() - transferedOwnership(member.id) + } } - }) + ) presentControllerImpl?(controller, nil) })) }) @@ -1617,20 +1628,21 @@ public func channelAdminController(context: AccountContext, updatedPresentationD rightNavigationButton = nil footerItem = ChannelAdminAddBotFooterItem(theme: presentationData.theme, title: state.adminRights ? presentationData.strings.Bot_AddToChat_Add_AddAsAdmin : presentationData.strings.Bot_AddToChat_Add_AddAsMember, action: { if state.adminRights { - let theme = AlertControllerTheme(presentationData: presentationData) - let attributedTitle = NSAttributedString(string: presentationData.strings.Bot_AddToChat_Add_AdminAlertTitle, font: Font.semibold(presentationData.listsFontSize.baseDisplaySize), textColor: theme.primaryColor, paragraphAlignment: .center) - let text = isGroup ? presentationData.strings.Bot_AddToChat_Add_AdminAlertTextGroup(peerTitle).string : presentationData.strings.Bot_AddToChat_Add_AdminAlertTextChannel(peerTitle).string - - let body = MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor) - let bold = MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - let controller = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Bot_AddToChat_Add_AdminAlertAdd, action: { - rightButtonActionImpl() - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - })], actionLayout: .vertical) - presentControllerImpl?(controller, nil) + + let alertController = textAlertController( + context: context, + updatedPresentationData: updatedPresentationData, + title: presentationData.strings.Bot_AddToChat_Add_AdminAlertTitle, + text: text, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Bot_AddToChat_Add_AdminAlertAdd, action: { rightButtonActionImpl() + }), + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), + ], + actionLayout: .vertical + ) + presentControllerImpl?(alertController, nil) } else { rightButtonActionImpl() } diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift index 52ff8fb0..c661eab9 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift @@ -135,7 +135,7 @@ final class ChannelDiscussionGroupSearchContainerNode: SearchDisplayControllerCo super.init() - self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + self.dimNode.backgroundColor = .clear self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.listNode.isHidden = true diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupHeaderItem.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupHeaderItem.swift index 9a87b600..e98240c8 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupHeaderItem.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupHeaderItem.swift @@ -90,7 +90,7 @@ class ChannelDiscussionGroupSetupHeaderItemNode: ListViewItemNode { self.labelNode.contentMode = .left self.labelNode.contentsScale = UIScreen.main.scale - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.imageNode) self.addSubnode(self.titleNode) diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift index d0e05756..b95ba046 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift @@ -9,6 +9,11 @@ import ItemListUI import PresentationDataUtils import AccountContext import SearchBarNode +import GlassBackgroundComponent +import ComponentFlow +import ComponentDisplayAdapters +import AppBundle +import ActivityIndicator final class ChannelDiscussionGroupSetupSearchItem: ItemListControllerSearch { let context: AccountContext @@ -84,8 +89,8 @@ private final class ChannelDiscussionGroupSetupSearchItemNode: ItemListControlle } override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight))) - self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)), navigationBarHeight: 0.0, transition: transition) + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) + self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height)), navigationBarHeight: navigationBarHeight, transition: transition) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -100,17 +105,40 @@ private final class ChannelDiscussionGroupSetupSearchItemNode: ItemListControlle private let searchBarFont = Font.regular(17.0) private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarContentNode, ItemListControllerSearchNavigationContentNode { + private struct Params: Equatable { + let size: CGSize + let leftInset: CGFloat + let rightInset: CGFloat + + init(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + self.size = size + self.leftInset = leftInset + self.rightInset = rightInset + } + } + private var theme: PresentationTheme private let strings: PresentationStrings private let cancel: () -> Void + private let backgroundContainer: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView + private let iconView: UIImageView + private var activityIndicator: ActivityIndicator? private let searchBar: SearchBarNode + private let close: (background: GlassBackgroundView, icon: UIImageView) + + private var params: Params? private var queryUpdated: ((String) -> Void)? var activity: Bool = false { didSet { - searchBar.activity = activity + if self.activity != oldValue { + if let params = self.params { + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + } + } } } init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) { @@ -119,11 +147,42 @@ private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarC self.cancel = cancel - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern) + self.backgroundContainer = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + self.backgroundContainer.contentView.addSubview(self.backgroundView) + self.iconView = UIImageView() + self.backgroundView.contentView.addSubview(self.iconView) + + self.close = (GlassBackgroundView(), UIImageView()) + self.close.background.contentView.addSubview(self.close.icon) + + self.searchBar = SearchBarNode( + theme: SearchBarNodeTheme( + background: .clear, + separator: .clear, + inputFill: .clear, + primaryText: theme.chat.inputPanel.panelControlColor, + placeholder: theme.chat.inputPanel.inputPlaceholderColor, + inputIcon: theme.chat.inputPanel.inputControlColor, + inputClear: theme.chat.inputPanel.panelControlColor, + accent: theme.chat.inputPanel.panelControlAccentColor, + keyboard: theme.rootController.keyboardColor + ), + presentationTheme: theme, + strings: strings, + fieldStyle: .inlineNavigation, + forceSeparator: false, + displayBackground: false, + cancelText: nil + ) super.init() - self.addSubnode(self.searchBar) + self.view.addSubview(self.backgroundContainer) + self.backgroundView.contentView.addSubview(self.searchBar.view) + + self.backgroundContainer.contentView.addSubview(self.close.background) + self.close.background.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:)))) self.searchBar.cancel = { [weak self] in self?.searchBar.deactivate(clear: false) @@ -141,13 +200,21 @@ private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarC self.updatePlaceholder() } + @objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.searchBar.cancel?() + } + } + func setQueryUpdated(_ f: @escaping (String) -> Void) { self.queryUpdated = f } func updateTheme(_ theme: PresentationTheme) { self.theme = theme - self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: self.theme), strings: self.strings) + if let params = self.params { + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + } self.updatePlaceholder() } @@ -158,13 +225,87 @@ private final class ChannelDiscussionSearchNavigationContentNode: NavigationBarC } override var nominalHeight: CGFloat { - return 54.0 + return 60.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { - let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0)) - self.searchBar.frame = searchBarFrame - self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + self.params = Params(size: size, leftInset: leftInset, rightInset: rightInset) + + let transition = ComponentTransition(transition) + + let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0), size: CGSize(width: size.width - 16.0 * 2.0 - leftInset - rightInset - 44.0 - 8.0, height: 44.0)) + let closeFrame = CGRect(origin: CGPoint(x: size.width - 16.0 - rightInset - 44.0, y: backgroundFrame.minY), size: CGSize(width: 44.0, height: 44.0)) + + transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size)) + self.backgroundContainer.update(size: size, isDark: self.theme.overallDarkAppearance, transition: transition) + + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + if self.iconView.image == nil { + self.iconView.image = UIImage(bundleImageName: "Navigation/Search")?.withRenderingMode(.alwaysTemplate) + } + transition.setTintColor(view: self.iconView, color: self.theme.rootController.navigationSearchBar.inputIconColor) + + if let image = self.iconView.image { + let imageSize: CGSize + let iconFrame: CGRect + let iconFraction: CGFloat = 0.8 + imageSize = CGSize(width: image.size.width * iconFraction, height: image.size.height * iconFraction) + iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floor((backgroundFrame.height - imageSize.height) * 0.5)), size: imageSize) + transition.setPosition(view: self.iconView, position: iconFrame.center) + transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + } + + if self.activity { + let activityIndicator: ActivityIndicator + if let current = self.activityIndicator { + activityIndicator = current + } else { + activityIndicator = ActivityIndicator(type: .custom(self.theme.chat.inputPanel.inputControlColor, 14.0, 14.0, false)) + self.activityIndicator = activityIndicator + self.backgroundView.contentView.addSubview(activityIndicator.view) + } + let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0)) + let indicatorFrame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((backgroundFrame.height - indicatorSize.height) * 0.5)), size: indicatorSize) + transition.setPosition(view: activityIndicator.view, position: indicatorFrame.center) + transition.setBounds(view: activityIndicator.view, bounds: CGRect(origin: CGPoint(), size: indicatorFrame.size)) + } else if let activityIndicator = self.activityIndicator { + self.activityIndicator = nil + activityIndicator.view.removeFromSuperview() + } + self.iconView.isHidden = self.activity + + let searchBarFrame = CGRect(origin: CGPoint(x: 36.0, y: 0.0), size: CGSize(width: backgroundFrame.width - 36.0 - 4.0, height: 44.0)) + transition.setFrame(view: self.searchBar.view, frame: searchBarFrame) + self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition) + + if self.close.icon.image == nil { + self.close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setStrokeColor(UIColor.white.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0)) + context.move(to: CGPoint(x: size.width - 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) + } + + if let image = close.icon.image { + self.close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size)) + } + self.close.icon.tintColor = self.theme.chat.inputPanel.panelControlColor + + transition.setFrame(view: self.close.background, frame: closeFrame) + self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + return size } func activate() { diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift index 0116746e..1aec3ba6 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift @@ -1203,7 +1203,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon strongSelf.enqueueEmptyQueryTransition(transition, firstTime: firstTime) if entries == nil { - strongSelf.emptyQueryListNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + strongSelf.emptyQueryListNode.backgroundColor = .clear } else { strongSelf.emptyQueryListNode.backgroundColor = presentationData.theme.chatList.backgroundColor } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift index bd022fb7..d0135110 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift @@ -42,8 +42,9 @@ public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMe self.presentationData = self.presentationData.withUpdated(theme: forceTheme) } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + self._hasGlassStyle = true self.navigationPresentation = .modal self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index b9f3bf04..af8f4190 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -685,7 +685,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index 5e130739..701e1c79 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -1132,22 +1132,26 @@ public func channelPermissionsController(context: AccountContext, updatedPresent controller.navigationPresentation = .modal controller.setState(.custom(icon: .animation("BroadcastGroup"), title: presentationData.strings.BroadcastGroups_IntroTitle, subtitle: nil, text: presentationData.strings.BroadcastGroups_IntroText, buttonTitle: presentationData.strings.BroadcastGroups_Convert, secondaryButtonTitle: presentationData.strings.BroadcastGroups_Cancel, footerText: nil), animated: false) controller.proceed = { [weak controller] result in - let attributedTitle = NSAttributedString(string: presentationData.strings.BroadcastGroups_ConfirmationAlert_Title, font: Font.semibold(presentationData.listsFontSize.baseDisplaySize), textColor: presentationData.theme.actionSheet.primaryTextColor, paragraphAlignment: .center) - let body = MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: presentationData.theme.actionSheet.primaryTextColor) - let bold = MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: presentationData.theme.actionSheet.primaryTextColor) - let attributedText = parseMarkdownIntoAttributedString(presentationData.strings.BroadcastGroups_ConfirmationAlert_Text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - let alertController = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.BroadcastGroups_ConfirmationAlert_Convert, action: { [weak controller] in - controller?.dismiss() - - let _ = (convertGroupToGigagroup(account: context.account, peerId: originalPeerId) - |> deliverOnMainQueue).start(completed: { - let participantsLimit = context.currentLimitsConfiguration.with { $0 }.maxSupergroupMemberCount - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .gigagroupConversion(text: presentationData.strings.BroadcastGroups_Success(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.decimalSeparator)).string), elevatedLayout: true, action: { _ in return false }), nil) - - dismissToChatController?() - }) - })]) + let alertController = textAlertController( + context: context, + updatedPresentationData: updatedPresentationData, + title: presentationData.strings.BroadcastGroups_ConfirmationAlert_Title, + text: presentationData.strings.BroadcastGroups_ConfirmationAlert_Text, + actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: presentationData.strings.BroadcastGroups_ConfirmationAlert_Convert, action: { [weak controller] in + controller?.dismiss() + + let _ = (convertGroupToGigagroup(account: context.account, peerId: originalPeerId) + |> deliverOnMainQueue).start(completed: { + let participantsLimit = context.currentLimitsConfiguration.with { $0 }.maxSupergroupMemberCount + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .gigagroupConversion(text: presentationData.strings.BroadcastGroups_Success(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.decimalSeparator)).string), elevatedLayout: true, action: { _ in return false }), nil) + + dismissToChatController?() + }) + }) + ] + ) controller?.present(alertController, in: .window(.root)) } pushControllerImpl?(controller) diff --git a/submodules/PeerInfoUI/Sources/ChatSlowmodeItem.swift b/submodules/PeerInfoUI/Sources/ChatSlowmodeItem.swift index 781c3eec..f87a8316 100644 --- a/submodules/PeerInfoUI/Sources/ChatSlowmodeItem.swift +++ b/submodules/PeerInfoUI/Sources/ChatSlowmodeItem.swift @@ -95,7 +95,7 @@ class ChatSlowmodeItemNode: ListViewItemNode { return textNode } - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.textNodes.forEach(self.addSubnode) } diff --git a/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift b/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift index baa545c7..4817be5e 100644 --- a/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift +++ b/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift @@ -103,7 +103,7 @@ class ChatUnrestrictBoostersItemNode: ListViewItemNode { return textNode } - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.iconNodes.forEach(self.addSubnode) self.textNodes.forEach(self.addSubnode) diff --git a/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift b/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift index 6f636531..46469ec4 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift @@ -110,8 +110,8 @@ private final class ChannelMembersSearchItemNode: ItemListControllerSearchNode { } override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight))) - self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)), navigationBarHeight: 0.0, transition: transition) + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) + self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height)), navigationBarHeight: navigationBarHeight, transition: transition) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { diff --git a/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift b/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift index 16c072a3..a298a6ab 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift @@ -8,24 +8,53 @@ import TelegramPresentationData import ItemListUI import PresentationDataUtils import SearchBarNode +import GlassBackgroundComponent +import ComponentFlow +import ComponentDisplayAdapters +import AppBundle +import ActivityIndicator private let searchBarFont = Font.regular(17.0) final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, ItemListControllerSearchNavigationContentNode { + private struct Params: Equatable { + let size: CGSize + let leftInset: CGFloat + let rightInset: CGFloat + + init(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + self.size = size + self.leftInset = leftInset + self.rightInset = rightInset + } + } + private var theme: PresentationTheme private let strings: PresentationStrings private let searchMode: ChannelMembersSearchMode private let cancel: () -> Void + private let backgroundContainer: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView + private let iconView: UIImageView + private var activityIndicator: ActivityIndicator? private let searchBar: SearchBarNode + private let close: (background: GlassBackgroundView, icon: UIImageView) + + private var params: Params? private var queryUpdated: ((String) -> Void)? var activity: Bool = false { didSet { - searchBar.activity = activity + if self.activity != oldValue { + if let params = self.params { + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + } + } } } + init(theme: PresentationTheme, strings: PresentationStrings, mode: ChannelMembersSearchMode, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) { self.theme = theme self.strings = strings @@ -33,11 +62,42 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item self.cancel = cancel - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: false) + self.backgroundContainer = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + self.backgroundContainer.contentView.addSubview(self.backgroundView) + self.iconView = UIImageView() + self.backgroundView.contentView.addSubview(self.iconView) + + self.close = (GlassBackgroundView(), UIImageView()) + self.close.background.contentView.addSubview(self.close.icon) + + self.searchBar = SearchBarNode( + theme: SearchBarNodeTheme( + background: .clear, + separator: .clear, + inputFill: .clear, + primaryText: theme.chat.inputPanel.panelControlColor, + placeholder: theme.chat.inputPanel.inputPlaceholderColor, + inputIcon: theme.chat.inputPanel.inputControlColor, + inputClear: theme.chat.inputPanel.panelControlColor, + accent: theme.chat.inputPanel.panelControlAccentColor, + keyboard: theme.rootController.keyboardColor + ), + presentationTheme: theme, + strings: strings, + fieldStyle: .inlineNavigation, + forceSeparator: false, + displayBackground: false, + cancelText: nil + ) super.init() - self.addSubnode(self.searchBar) + self.view.addSubview(self.backgroundContainer) + self.backgroundView.contentView.addSubview(self.searchBar.view) + + self.backgroundContainer.contentView.addSubview(self.close.background) + self.close.background.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:)))) self.searchBar.cancel = { [weak self] in self?.searchBar.deactivate(clear: false) @@ -55,13 +115,21 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item self.updatePlaceholder() } + @objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.searchBar.cancel?() + } + } + func setQueryUpdated(_ f: @escaping (String) -> Void) { self.queryUpdated = f } func updateTheme(_ theme: PresentationTheme) { self.theme = theme - self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: self.theme), strings: self.strings) + if let params = self.params { + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + } self.updatePlaceholder() } @@ -77,13 +145,87 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item } override var nominalHeight: CGFloat { - return 54.0 + return 60.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { - let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0)) - self.searchBar.frame = searchBarFrame - self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + self.params = Params(size: size, leftInset: leftInset, rightInset: rightInset) + + let transition = ComponentTransition(transition) + + let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0), size: CGSize(width: size.width - 16.0 * 2.0 - leftInset - rightInset - 44.0 - 8.0, height: 44.0)) + let closeFrame = CGRect(origin: CGPoint(x: size.width - 16.0 - rightInset - 44.0, y: backgroundFrame.minY), size: CGSize(width: 44.0, height: 44.0)) + + transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size)) + self.backgroundContainer.update(size: size, isDark: self.theme.overallDarkAppearance, transition: transition) + + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + if self.iconView.image == nil { + self.iconView.image = UIImage(bundleImageName: "Navigation/Search")?.withRenderingMode(.alwaysTemplate) + } + transition.setTintColor(view: self.iconView, color: self.theme.rootController.navigationSearchBar.inputIconColor) + + if let image = self.iconView.image { + let imageSize: CGSize + let iconFrame: CGRect + let iconFraction: CGFloat = 0.8 + imageSize = CGSize(width: image.size.width * iconFraction, height: image.size.height * iconFraction) + iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floor((backgroundFrame.height - imageSize.height) * 0.5)), size: imageSize) + transition.setPosition(view: self.iconView, position: iconFrame.center) + transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + } + + if self.activity { + let activityIndicator: ActivityIndicator + if let current = self.activityIndicator { + activityIndicator = current + } else { + activityIndicator = ActivityIndicator(type: .custom(self.theme.chat.inputPanel.inputControlColor, 14.0, 14.0, false)) + self.activityIndicator = activityIndicator + self.backgroundView.contentView.addSubview(activityIndicator.view) + } + let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0)) + let indicatorFrame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((backgroundFrame.height - indicatorSize.height) * 0.5)), size: indicatorSize) + transition.setPosition(view: activityIndicator.view, position: indicatorFrame.center) + transition.setBounds(view: activityIndicator.view, bounds: CGRect(origin: CGPoint(), size: indicatorFrame.size)) + } else if let activityIndicator = self.activityIndicator { + self.activityIndicator = nil + activityIndicator.view.removeFromSuperview() + } + self.iconView.isHidden = self.activity + + let searchBarFrame = CGRect(origin: CGPoint(x: 36.0, y: 0.0), size: CGSize(width: backgroundFrame.width - 36.0 - 4.0, height: 44.0)) + transition.setFrame(view: self.searchBar.view, frame: searchBarFrame) + self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition) + + if self.close.icon.image == nil { + self.close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setStrokeColor(UIColor.white.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0)) + context.move(to: CGPoint(x: size.width - 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) + } + + if let image = close.icon.image { + self.close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size)) + } + self.close.icon.tintColor = self.theme.chat.inputPanel.panelControlColor + + transition.setFrame(view: self.close.background, frame: closeFrame) + self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + return size } func activate() { @@ -94,4 +236,3 @@ final class GroupInfoSearchNavigationContentNode: NavigationBarContentNode, Item self.searchBar.deactivate(clear: false) } } - diff --git a/submodules/PeerInfoUI/Sources/ItemListCallListItem.swift b/submodules/PeerInfoUI/Sources/ItemListCallListItem.swift index 23bb4f30..cfd32b4a 100644 --- a/submodules/PeerInfoUI/Sources/ItemListCallListItem.swift +++ b/submodules/PeerInfoUI/Sources/ItemListCallListItem.swift @@ -174,7 +174,7 @@ public class ItemListCallListItemNode: ListViewItemNode { self.accessibilityArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.accessibilityArea) diff --git a/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift b/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift index e1e2d04c..64133463 100644 --- a/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift +++ b/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift @@ -168,7 +168,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.switchNode) diff --git a/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift b/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift index a9824f78..4d3b3634 100644 --- a/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift +++ b/submodules/PeerInfoUI/Sources/ItemListSecretChatKeyItem.swift @@ -123,7 +123,7 @@ class ItemListSecretChatKeyItemNode: ListViewItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.keyNode) diff --git a/submodules/PeerInfoUI/Sources/PeerAutoremoveTimeoutItem.swift b/submodules/PeerInfoUI/Sources/PeerAutoremoveTimeoutItem.swift index f45479be..b0a2827b 100644 --- a/submodules/PeerInfoUI/Sources/PeerAutoremoveTimeoutItem.swift +++ b/submodules/PeerInfoUI/Sources/PeerAutoremoveTimeoutItem.swift @@ -125,7 +125,7 @@ class PeerRemoveTimeoutItemNode: ListViewItemNode, ItemListItemNode { return TextNode() } - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.titleNodes.forEach(self.addSubnode) diff --git a/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneActionItem.swift b/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneActionItem.swift index 4fe64adb..ba292243 100644 --- a/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneActionItem.swift +++ b/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneActionItem.swift @@ -100,7 +100,7 @@ class UserInfoEditingPhoneActionItemNode: ListViewItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.iconNode) self.addSubnode(self.titleNode) diff --git a/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift b/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift index 409db1eb..c3ff6f4c 100644 --- a/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift +++ b/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift @@ -131,7 +131,7 @@ class UserInfoEditingPhoneItemNode: ItemListRevealOptionsItemNode, ItemListItemN self.clearButton.displaysAsynchronously = false self.clearButton.isHidden = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.editableControlNode) self.addSubnode(self.labelNode) diff --git a/submodules/PeersNearbyUI/Sources/PeersNearbyHeaderItem.swift b/submodules/PeersNearbyUI/Sources/PeersNearbyHeaderItem.swift index 57f756bf..e1b70cb3 100644 --- a/submodules/PeersNearbyUI/Sources/PeersNearbyHeaderItem.swift +++ b/submodules/PeersNearbyUI/Sources/PeersNearbyHeaderItem.swift @@ -76,7 +76,7 @@ class PeersNearbyHeaderItemNode: ListViewItemNode { self.animationNode = DefaultAnimatedStickerNodeImpl() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.animationNode) diff --git a/submodules/Postbox/Sources/DeletedMessagesView.swift b/submodules/Postbox/Sources/DeletedMessagesView.swift index 7f3b4eda..b1ab425f 100644 --- a/submodules/Postbox/Sources/DeletedMessagesView.swift +++ b/submodules/Postbox/Sources/DeletedMessagesView.swift @@ -15,7 +15,7 @@ final class MutableDeletedMessagesView: MutablePostboxView { for operation in operations { switch operation { case let .Remove(indices): - for (index, _) in indices { + for (index, _, _) in indices { testMessageIds.append(index.id) } default: diff --git a/submodules/Postbox/Sources/HistoryTagInfoView.swift b/submodules/Postbox/Sources/HistoryTagInfoView.swift index 912b4a86..9b9e0adc 100644 --- a/submodules/Postbox/Sources/HistoryTagInfoView.swift +++ b/submodules/Postbox/Sources/HistoryTagInfoView.swift @@ -32,7 +32,7 @@ final class MutableHistoryTagInfoView: MutablePostboxView { } case let .Remove(indicesAndTags): if self.currentIndex != nil { - for (index, tags) in indicesAndTags { + for (index, tags, _) in indicesAndTags { if tags.contains(self.tag) { if index == self.currentIndex { self.currentIndex = nil diff --git a/submodules/Postbox/Sources/MessageHistoryOperation.swift b/submodules/Postbox/Sources/MessageHistoryOperation.swift index 71e7e84d..c4537b58 100644 --- a/submodules/Postbox/Sources/MessageHistoryOperation.swift +++ b/submodules/Postbox/Sources/MessageHistoryOperation.swift @@ -2,7 +2,7 @@ import Foundation enum MessageHistoryOperation { case InsertMessage(IntermediateMessage) - case Remove([(MessageIndex, MessageTags)]) + case Remove([(MessageIndex, MessageTags, Int64?)]) case UpdateReadState(PeerId, CombinedPeerReadState) case UpdateEmbeddedMedia(MessageIndex, ReadBuffer) case UpdateTimestamp(MessageIndex, Int32) diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index 2d290a75..cd004d82 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -197,19 +197,19 @@ final class MessageHistoryTable: Table { let buckets = self.continuousIndexIntervalsForRemoving(accumulatedRemoveIndices) for bucket in buckets { - var indicesWithMetadata: [(MessageIndex, MessageTags)] = [] + var indicesWithMetadata: [(MessageIndex, MessageTags, Int64?)] = [] var globalIndicesWithMetadata: [(GlobalMessageTags, MessageIndex)] = [] for index in bucket { let tagsAndGlobalTags = self.justRemove(index, unsentMessageOperations: &unsentMessageOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations) - if let (tags, globalTags) = tagsAndGlobalTags { - indicesWithMetadata.append((index, tags)) + if let (tags, globalTags, threadId) = tagsAndGlobalTags { + indicesWithMetadata.append((index, tags, threadId)) if !globalTags.isEmpty { globalIndicesWithMetadata.append((globalTags, index)) } } else { - indicesWithMetadata.append((index, MessageTags())) + indicesWithMetadata.append((index, MessageTags(), nil)) } } assert(bucket.count == indicesWithMetadata.count) @@ -352,8 +352,8 @@ final class MessageHistoryTable: Table { processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations) var updatedGroupInfos: [MessageId: MessageGroupInfo] = [:] - if let (message, previousTags) = self.justUpdate(storeMessage.index, message: storeMessage, keepLocalTags: true, sharedKey: sharedKey, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder, unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations, updatedMedia: &updatedMedia) { - outputOperations.append(.Remove([(storeMessage.index, previousTags)])) + if let (message, previousTags, previousThreadId) = self.justUpdate(storeMessage.index, message: storeMessage, keepLocalTags: true, sharedKey: sharedKey, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder, unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations, updatedMedia: &updatedMedia) { + outputOperations.append(.Remove([(storeMessage.index, previousTags, previousThreadId)])) outputOperations.append(.InsertMessage(message)) if !updatedGroupInfos.isEmpty { outputOperations.append(.UpdateGroupInfos(updatedGroupInfos)) @@ -367,8 +367,8 @@ final class MessageHistoryTable: Table { processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations) var updatedGroupInfos: [MessageId: MessageGroupInfo] = [:] - if let (message, previousTags) = self.justUpdate(index, message: storeMessage, keepLocalTags: false, sharedKey: sharedKey, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder, unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations, updatedMedia: &updatedMedia) { - outputOperations.append(.Remove([(index, previousTags)])) + if let (message, previousTags, previousThreadId) = self.justUpdate(index, message: storeMessage, keepLocalTags: false, sharedKey: sharedKey, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder, unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations, updatedMedia: &updatedMedia) { + outputOperations.append(.Remove([(index, previousTags, previousThreadId)])) outputOperations.append(.InsertMessage(message)) if !updatedGroupInfos.isEmpty { outputOperations.append(.UpdateGroupInfos(updatedGroupInfos)) @@ -944,7 +944,7 @@ final class MessageHistoryTable: Table { self.storeIntermediateMessage(updatedMessage, sharedKey: self.key(MessageIndex.absoluteLowerBound())) let operations: [MessageHistoryOperation] = [ - .Remove([(index, message.tags)]), + .Remove([(index, message.tags, message.threadId)]), .InsertMessage(updatedMessage) ] if operationsByPeerId[message.id.peerId] == nil { @@ -1352,7 +1352,7 @@ final class MessageHistoryTable: Table { return result } - private func justRemove(_ index: MessageIndex, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation]) -> (MessageTags, GlobalMessageTags)? { + private func justRemove(_ index: MessageIndex, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation]) -> (MessageTags, GlobalMessageTags, Int64?)? { let key = self.key(index) if let value = self.valueBox.get(self.table, key: key) { let resultTags: MessageTags @@ -1440,7 +1440,7 @@ final class MessageHistoryTable: Table { resultGlobalTags = message.globalTags self.valueBox.remove(self.table, key: key, secure: true) - return (resultTags, resultGlobalTags) + return (resultTags, resultGlobalTags, message.threadId) } else { return nil } @@ -1544,7 +1544,7 @@ final class MessageHistoryTable: Table { }) } - private func justUpdate(_ index: MessageIndex, message: InternalStoreMessage, keepLocalTags: Bool, sharedKey: ValueBoxKey, sharedBuffer: WriteBuffer, sharedEncoder: PostboxEncoder, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], updatedGroupInfos: inout [MessageId: MessageGroupInfo], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], updatedMedia: inout [MediaId: Media?]) -> (IntermediateMessage, MessageTags)? { + private func justUpdate(_ index: MessageIndex, message: InternalStoreMessage, keepLocalTags: Bool, sharedKey: ValueBoxKey, sharedBuffer: WriteBuffer, sharedEncoder: PostboxEncoder, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], updatedGroupInfos: inout [MessageId: MessageGroupInfo], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], updatedMedia: inout [MediaId: Media?]) -> (IntermediateMessage, MessageTags, Int64?)? { if let previousMessage = self.getMessage(index) { var mediaToUpdate: [Media] = [] @@ -2037,7 +2037,7 @@ final class MessageHistoryTable: Table { self.valueBox.set(self.table, key: self.key(message.index, key: sharedKey), value: sharedBuffer) - let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: tags, globalTags: message.globalTags, localTags: updatedLocalTags, customTags: message.customTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), previousMessage.tags) + let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: tags, globalTags: message.globalTags, localTags: updatedLocalTags, customTags: message.customTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), previousMessage.tags, previousMessage.threadId) for media in mediaToUpdate { if let id = media.id { diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index a8b000f9..eb259183 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -676,7 +676,7 @@ final class MutableMessageHistoryView: MutablePostboxView { } } case let .Remove(indicesAndTags): - for (index, _) in indicesAndTags { + for (index, _, _) in indicesAndTags { if self.namespaces.contains(index.id.namespace) { if loadedState.remove(index: index) { hasChanges = true @@ -821,7 +821,7 @@ final class MutableMessageHistoryView: MutablePostboxView { } case let .Remove(indices): if !self.topTaggedMessages.isEmpty { - for (index, _) in indices { + for (index, _, _) in indices { if let maybeCurrentTopMessage = self.topTaggedMessages[index.id.namespace], let currentTopMessage = maybeCurrentTopMessage, index.id == currentTopMessage.id { let item: MessageHistoryTopTaggedMessage? = nil self.topTaggedMessages[index.id.namespace] = item @@ -873,7 +873,7 @@ final class MutableMessageHistoryView: MutablePostboxView { break findOperation } case let .Remove(indices): - for (index, _) in indices { + for (index, _, _) in indices { if currentIds.contains(index.id) { updateMessage = true break findOperation @@ -956,7 +956,7 @@ final class MutableMessageHistoryView: MutablePostboxView { break outer } case let .Remove(indicesWithTags): - for (index, _) in indicesWithTags { + for (index, _, _) in indicesWithTags { if cachedData.messageIds.contains(index.id) { updatedCachedPeerDataMessages = true break outer diff --git a/submodules/Postbox/Sources/MessageView.swift b/submodules/Postbox/Sources/MessageView.swift index 1ae3ed29..48312584 100644 --- a/submodules/Postbox/Sources/MessageView.swift +++ b/submodules/Postbox/Sources/MessageView.swift @@ -18,7 +18,7 @@ final class MutableMessageView { case let .Remove(indices): if let message = self.message { let messageIndex = message.index - for (index, _) in indices { + for (index, _, _) in indices { if index == messageIndex { self.message = nil updated = true diff --git a/submodules/Postbox/Sources/PeerChatTopIndexableMessageIds.swift b/submodules/Postbox/Sources/PeerChatTopIndexableMessageIds.swift index ed898666..84e6c996 100644 --- a/submodules/Postbox/Sources/PeerChatTopIndexableMessageIds.swift +++ b/submodules/Postbox/Sources/PeerChatTopIndexableMessageIds.swift @@ -1,7 +1,7 @@ import Foundation private struct PeerChatTopTaggedUpdateRecord: Equatable, Hashable { - let peerId: PeerId + let peerAndThreadId: PeerAndThreadId let namespace: MessageId.Namespace } @@ -10,71 +10,85 @@ final class PeerChatTopTaggedMessageIdsTable: Table { return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) } - private var cachedTopIds: [PeerId: [MessageId.Namespace: MessageId?]] = [:] + private var cachedTopIds: [PeerAndThreadId: [MessageId.Namespace: MessageId?]] = [:] private var updatedPeerIds = Set() - private let sharedKey = ValueBoxKey(length: 8 + 4) + private let sharedKeyNoThreadId = ValueBoxKey(length: 8 + 4) + private let sharedKeyWithThreadId = ValueBoxKey(length: 8 + 4 + 8) - private func key(peerId: PeerId, namespace: MessageId.Namespace) -> ValueBoxKey { - self.sharedKey.setInt64(0, value: peerId.toInt64()) - self.sharedKey.setInt32(8, value: namespace) - return self.sharedKey + private func key(combinedId: PeerAndThreadId, namespace: MessageId.Namespace) -> ValueBoxKey { + if let threadId = combinedId.threadId { + self.sharedKeyWithThreadId.setInt64(0, value: combinedId.peerId.toInt64()) + self.sharedKeyWithThreadId.setInt32(8, value: namespace) + self.sharedKeyWithThreadId.setInt64(8 + 4, value: threadId) + + return self.sharedKeyWithThreadId + } else { + self.sharedKeyNoThreadId.setInt64(0, value: combinedId.peerId.toInt64()) + self.sharedKeyNoThreadId.setInt32(8, value: namespace) + + return self.sharedKeyNoThreadId + } } - func get(peerId: PeerId, namespace: MessageId.Namespace) -> MessageId? { - if let cachedDict = self.cachedTopIds[peerId] { + func get(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace) -> MessageId? { + let combinedId = PeerAndThreadId(peerId: peerId, threadId: threadId) + + if let cachedDict = self.cachedTopIds[combinedId] { if let maybeCachedId = cachedDict[namespace] { return maybeCachedId } else { - if let value = self.valueBox.get(self.table, key: self.key(peerId: peerId, namespace: namespace)) { + if let value = self.valueBox.get(self.table, key: self.key(combinedId: combinedId, namespace: namespace)) { var messageIdId: Int32 = 0 value.read(&messageIdId, offset: 0, length: 4) - self.cachedTopIds[peerId]![namespace] = MessageId(peerId: peerId, namespace: namespace, id: messageIdId) + self.cachedTopIds[combinedId]![namespace] = MessageId(peerId: peerId, namespace: namespace, id: messageIdId) return MessageId(peerId: peerId, namespace: namespace, id: messageIdId) } else { let item: MessageId? = nil - self.cachedTopIds[peerId]![namespace] = item + self.cachedTopIds[combinedId]![namespace] = item return nil } } } else { - if let value = self.valueBox.get(self.table, key: self.key(peerId: peerId, namespace: namespace)) { + if let value = self.valueBox.get(self.table, key: self.key(combinedId: combinedId, namespace: namespace)) { var messageIdId: Int32 = 0 value.read(&messageIdId, offset: 0, length: 4) - self.cachedTopIds[peerId] = [namespace: MessageId(peerId: peerId, namespace: namespace, id: messageIdId)] + self.cachedTopIds[combinedId] = [namespace: MessageId(peerId: peerId, namespace: namespace, id: messageIdId)] return MessageId(peerId: peerId, namespace: namespace, id: messageIdId) } else { let item: MessageId? = nil - self.cachedTopIds[peerId] = [namespace: item] + self.cachedTopIds[combinedId] = [namespace: item] return nil } } } - private func set(peerId: PeerId, namespace: MessageId.Namespace, id: MessageId?) { - if let _ = self.cachedTopIds[peerId] { - self.cachedTopIds[peerId]![namespace] = id + private func set(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, id: MessageId?) { + let combinedId = PeerAndThreadId(peerId: peerId, threadId: threadId) + + if let _ = self.cachedTopIds[combinedId] { + self.cachedTopIds[combinedId]![namespace] = id } else { - self.cachedTopIds[peerId] = [namespace: id] + self.cachedTopIds[combinedId] = [namespace: id] } - self.updatedPeerIds.insert(PeerChatTopTaggedUpdateRecord(peerId: peerId, namespace: namespace)) + self.updatedPeerIds.insert(PeerChatTopTaggedUpdateRecord(peerAndThreadId: combinedId, namespace: namespace)) } - func replay(historyOperationsByPeerId: [PeerId : [MessageHistoryOperation]]) { + func replay(historyOperationsByPeerId: [PeerId: [MessageHistoryOperation]]) { for (_, operations) in historyOperationsByPeerId { for operation in operations { switch operation { case let .InsertMessage(message): if message.flags.contains(.TopIndexable) { - let currentTopMessageId = self.get(peerId: message.id.peerId, namespace: message.id.namespace) + let currentTopMessageId = self.get(peerId: message.id.peerId, threadId: message.threadId, namespace: message.id.namespace) if currentTopMessageId == nil || currentTopMessageId! < message.id { - self.set(peerId: message.id.peerId, namespace: message.id.namespace, id: message.id) + self.set(peerId: message.id.peerId, threadId: message.threadId, namespace: message.id.namespace, id: message.id) } } case let .Remove(indices): - for (index, _) in indices { - if let messageId = self.get(peerId: index.id.peerId, namespace: index.id.namespace), index.id == messageId { - self.set(peerId: index.id.peerId, namespace: index.id.namespace, id: nil) + for (index, _, threadId) in indices { + if let messageId = self.get(peerId: index.id.peerId, threadId: threadId, namespace: index.id.namespace), index.id == messageId { + self.set(peerId: index.id.peerId, threadId: threadId, namespace: index.id.namespace, id: nil) } } default: @@ -92,12 +106,12 @@ final class PeerChatTopTaggedMessageIdsTable: Table { override func beforeCommit() { if !self.updatedPeerIds.isEmpty { for record in self.updatedPeerIds { - if let cachedDict = self.cachedTopIds[record.peerId], let maybeMessageId = cachedDict[record.namespace] { + if let cachedDict = self.cachedTopIds[record.peerAndThreadId], let maybeMessageId = cachedDict[record.namespace] { if let maybeMessageId = maybeMessageId { var messageIdId: Int32 = maybeMessageId.id - self.valueBox.set(self.table, key: self.key(peerId: record.peerId, namespace: record.namespace), value: MemoryBuffer(memory: &messageIdId, capacity: 4, length: 4, freeWhenDone: false)) + self.valueBox.set(self.table, key: self.key(combinedId: record.peerAndThreadId, namespace: record.namespace), value: MemoryBuffer(memory: &messageIdId, capacity: 4, length: 4, freeWhenDone: false)) } else { - self.valueBox.remove(self.table, key: self.key(peerId: record.peerId, namespace: record.namespace), secure: false) + self.valueBox.remove(self.table, key: self.key(combinedId: record.peerAndThreadId, namespace: record.namespace), secure: false) } } } diff --git a/submodules/Postbox/Sources/PeerView.swift b/submodules/Postbox/Sources/PeerView.swift index f7ff4c11..d0fa0b4f 100644 --- a/submodules/Postbox/Sources/PeerView.swift +++ b/submodules/Postbox/Sources/PeerView.swift @@ -247,7 +247,7 @@ final class MutablePeerView: MutablePostboxView { break outer } case let .Remove(indicesWithTags): - for (index, _) in indicesWithTags { + for (index, _, _) in indicesWithTags { if cachedData.messageIds.contains(index.id) { updateMessages = true break outer diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 7f8e92ac..396fe12a 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -3494,20 +3494,20 @@ final class PostboxImpl { useRootInterfaceStateForThread: Bool ) -> Disposable { var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:] - var mainPeerIdForTopTaggedMessages: PeerId? - switch peerIds { + var mainPeerIdForTopTaggedMessages: (peerId: PeerId, threadId: Int64?)? + if tag == nil { + switch peerIds { case let .single(id, threadId): - if threadId == nil { - mainPeerIdForTopTaggedMessages = id - } + mainPeerIdForTopTaggedMessages = (id, threadId) case let .associated(id, _): - mainPeerIdForTopTaggedMessages = id + mainPeerIdForTopTaggedMessages = (id, nil) case .external: mainPeerIdForTopTaggedMessages = nil + } } if let peerId = mainPeerIdForTopTaggedMessages { for namespace in topTaggedMessageIdNamespaces { - if let messageId = self.peerChatTopTaggedMessageIdsTable.get(peerId: peerId, namespace: namespace) { + if let messageId = self.peerChatTopTaggedMessageIdsTable.get(peerId: peerId.peerId, threadId: peerId.threadId, namespace: namespace) { if let index = self.messageHistoryIndexTable.getIndex(messageId) { if let message = self.messageHistoryTable.getMessage(index) { topTaggedMessages[namespace] = MessageHistoryTopTaggedMessage.intermediate(message) diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index ac8046b6..f2a16847 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -123,6 +123,9 @@ swift_library( "//submodules/TelegramUI/Components/Premium/PremiumCoinComponent", "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/Gifts/TableComponent", + "//submodules/TelegramUI/Components/Gifts/PeerTableCellComponent", + "//submodules/TelegramUI/Components/AlertComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index 52330915..810c269d 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -493,7 +493,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { } }) case let .starsMore(theme, title): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: { arguments.expandStars() }) case let .starsInfo(_, text): @@ -521,14 +521,14 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { if case let .channel(channel) = peer, case .group = channel.info { isGroup = true } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(isGroup ? presentationData.strings.BoostGift_GroupBoosts($0) : presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(isGroup ? presentationData.strings.BoostGift_GroupBoosts($0) : presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: { }, setPeerIdWithRevealedOptions: { lhs, rhs in arguments.setItemIdWithRevealedOptions(lhs, rhs) }, removePeer: { id in arguments.removeChannel(id) }) case let .channelAdd(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.roundPlusIconImage(theme), title: text, alwaysPlain: false, hasSeparator: true, sectionId: self.section, height: .compactPeerList, color: .accent, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.roundPlusIconImage(theme), title: text, alwaysPlain: false, hasSeparator: true, sectionId: self.section, height: .compactPeerList, color: .accent, editing: false, action: { arguments.openChannelsSelection() }) case let .channelsInfo(_, text): @@ -582,7 +582,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { arguments.openPremiumIntro() }) case let .prizeDescription(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateState { state in var updatedState = state updatedState.showPrizeDescription = value @@ -590,7 +590,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { } }) case let .prizeDescriptionText(_, placeholder, value, count): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "\(count)"), text: value, placeholder: placeholder, returnKeyType: .done, spacing: 24.0, maxLength: 128, tag: CreateGiveawayEntryTag.description, sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "\(count)"), text: value, placeholder: placeholder, returnKeyType: .done, spacing: 24.0, maxLength: 128, tag: CreateGiveawayEntryTag.description, sectionId: self.section, textUpdated: { value in arguments.updateState { state in var updatedState = state updatedState.prizeDescription = value @@ -616,7 +616,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { } else { text = presentationData.strings.InviteLink_Create_TimeLimitExpiryDateNever } - return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.BoostGift_DateEnds, label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.BoostGift_DateEnds, label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: { arguments.dismissInput() var focus = false arguments.updateState { state in @@ -677,7 +677,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { case let .timeInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .winners(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateState { state in var updatedState = state updatedState.showWinners = value diff --git a/submodules/PremiumUI/Sources/GiftOptionItem.swift b/submodules/PremiumUI/Sources/GiftOptionItem.swift index 12166ff4..eac888a1 100644 --- a/submodules/PremiumUI/Sources/GiftOptionItem.swift +++ b/submodules/PremiumUI/Sources/GiftOptionItem.swift @@ -212,7 +212,7 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) diff --git a/submodules/PremiumUI/Sources/GiveawayInfoController.swift b/submodules/PremiumUI/Sources/GiveawayInfoController.swift index b50ada30..2db71bc9 100644 --- a/submodules/PremiumUI/Sources/GiveawayInfoController.swift +++ b/submodules/PremiumUI/Sources/GiveawayInfoController.swift @@ -7,8 +7,8 @@ import TelegramCore import AccountContext import TelegramStringFormatting import TelegramPresentationData -import Markdown -import AlertUI +import ComponentFlow +import AlertComponent public func presentGiveawayInfoController( context: AccountContext, @@ -23,6 +23,8 @@ public func presentGiveawayInfoController( peerIds.append(adminId) } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let _ = (context.engine.data.get( TelegramEngine.EngineData.Item.Messages.Message(id: messageId), EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)) @@ -121,22 +123,16 @@ public func presentGiveawayInfoController( channelsCount = 1 + giveawayResults.additionalChannelsCount } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - - let timeZone = TimeZone.current let untilDate = stringForDate(timestamp: untilDateValue, timeZone: timeZone, strings: presentationData.strings) let title: String - let text: String + var text: String var warning: String? - - var dismissImpl: (() -> Void)? - - var actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - dismissImpl?() - })] + + var actions: [AlertScreen.Action] = [ + .init(title: presentationData.strings.Common_OK, type: .default) + ] var additionalPrizes = "" if let prizeDescription, !prizeDescription.isEmpty { @@ -322,278 +318,65 @@ public func presentGiveawayInfoController( case .refunded: result = "" warning = presentationData.strings.Chat_Giveaway_Info_Refunded - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Close, action: { - dismissImpl?() - })] case .notWon: result = "**\(presentationData.strings.Chat_Giveaway_Info_DidntWin)**\n\n" case let .wonPremium(slug): result = "**\(presentationData.strings.Chat_Giveaway_Info_Won("").string)**\n\n" - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Chat_Giveaway_Info_ViewPrize, action: { - dismissImpl?() - openLink(slug) - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?() - })] + actions = [ + .init(title: presentationData.strings.Chat_Giveaway_Info_ViewPrize, type: .default, action: { + openLink(slug) + }), + .init(title: presentationData.strings.Common_Cancel) + ] case let .wonStars(stars): let _ = stars result = "**\(presentationData.strings.Chat_Giveaway_Info_Won("").string)**\n\n" - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Chat_Giveaway_Info_ViewPrize, action: { - dismissImpl?() - openLink("") - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?() - })] + actions = [ + .init(title: presentationData.strings.Chat_Giveaway_Info_ViewPrize, type: .default, action: { + openLink("") + }), + .init(title: presentationData.strings.Common_Cancel) + ] } text = "\(result)\(intro)\(additionalPrizes)\n\n\(ending)" } - let alertController = giveawayInfoAlertController( - context: context, - updatedPresentationData: updatedPresentationData, - title: title, - text: text, - warning: warning, - actions: actions - ) - dismissImpl = { [weak alertController] in - alertController?.dismissAnimated() + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + if let warning { + content.append(AnyComponentWithIdentity( + id: "warning", + component: AnyComponent( + AlertTextComponent(content: .plain(warning), color: .destructive, style: .background(.bold)) + ) + )) } + + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) + } + + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + content: content, + actions: actions, + updatedPresentationData: effectiveUpdatedPresentationData + ) present(alertController) }) } - -private final class GiveawayInfoAlertContentNode: AlertContentNode { - private let title: String - private let text: String - private let warning: String? - - private let titleNode: ASTextNode - private let textNode: ASTextNode - fileprivate let warningBackgroundNode: ASImageNode - fileprivate let warningTextNode: ImmediateTextNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - public var theme: PresentationTheme - - public override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - public init(theme: AlertControllerTheme, ptheme: PresentationTheme, title: String, text: String, warning: String?, actions: [TextAlertAction]) { - self.theme = ptheme - self.title = title - self.text = text - self.warning = warning - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 0 - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.warningBackgroundNode = ASImageNode() - self.warningBackgroundNode.displaysAsynchronously = false - - self.warningTextNode = ImmediateTextNode() - self.warningTextNode.maximumNumberOfLines = 0 - self.warningTextNode.lineSpacing = 0.1 - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - - self.addSubnode(self.warningBackgroundNode) - self.addSubnode(self.warningTextNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - } - - public override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - - let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor) - let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor) - let attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - self.textNode.attributedText = attributedText - - self.warningTextNode.attributedText = NSAttributedString(string: self.warning ?? "", font: Font.semibold(13.0), textColor: theme.destructiveColor, paragraphAlignment: .center) - self.warningBackgroundNode.image = generateStretchableFilledCircleImage(radius: 5.0, color: theme.destructiveColor.withAlphaComponent(0.1)) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - public override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let titleSize = self.titleNode.measure(measureSize) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 4.0 - - let textSize = self.textNode.measure(measureSize) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 6.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - if "".isEmpty { - effectiveActionLayout = .vertical - } - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - var contentWidth = max(titleSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - var warningHeight: CGFloat = 0.0 - if let _ = self.warning { - let warningSize = self.warningTextNode.updateLayout(measureSize) - let warningFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - warningSize.width) / 2.0), y: origin.y + 20.0), size: warningSize) - transition.updateFrame(node: self.warningTextNode, frame: warningFrame) - - transition.updateFrame(node: self.warningBackgroundNode, frame: warningFrame.insetBy(dx: -8.0, dy: -8.0)) - - warningHeight += warningSize.height + 26.0 - } - - let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + 8.0 + actionsHeight + warningHeight + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} - -private func giveawayInfoAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, title: String, text: String, warning: String?, actions: [TextAlertAction]) -> AlertController { - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - - let contentNode = GiveawayInfoAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, title: title, text: text, warning: warning, actions: actions) - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - } - - return controller -} diff --git a/submodules/PremiumUI/Sources/IncreaseLimitHeaderItem.swift b/submodules/PremiumUI/Sources/IncreaseLimitHeaderItem.swift index f203973b..14600adf 100644 --- a/submodules/PremiumUI/Sources/IncreaseLimitHeaderItem.swift +++ b/submodules/PremiumUI/Sources/IncreaseLimitHeaderItem.swift @@ -99,7 +99,7 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { self.textNode.contentMode = .left self.textNode.contentsScale = UIScreen.main.scale - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.textNode) diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index e531a8b1..2decd5b6 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -1133,7 +1133,7 @@ private final class DemoSheetContent: CombinedComponent { let closeButton = closeButton.update( component: GlassBarButtonComponent( - size: CGSize(width: 40.0, height: 40.0), + size: CGSize(width: 44.0, height: 44.0), backgroundColor: UIColor(rgb: 0x7f76f4), isDark: false, state: .tintedGlass, @@ -1147,7 +1147,7 @@ private final class DemoSheetContent: CombinedComponent { component.dismiss() } ), - availableSize: CGSize(width: 40.0, height: 40.0), + availableSize: CGSize(width: 44.0, height: 44.0), transition: .immediate ) context.add(closeButton diff --git a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift index be059544..a17a1396 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift @@ -23,6 +23,8 @@ import InvisibleInkDustNode import PremiumStarComponent import GlassBarButtonComponent import ButtonComponent +import TableComponent +import PeerTableCellComponent private final class PremiumGiftCodeSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -163,7 +165,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in @@ -326,7 +328,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent { title: strings.GiftLink_From, component: AnyComponent( Button( - content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: fromPeer)), + content: AnyComponent(PeerTableCellComponent(context: context.component.context, theme: theme, strings: strings, peer: fromPeer)), action: { if let peer = fromPeer, peer.id != accountContext.account.peerId { component.openPeer(peer) @@ -344,7 +346,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent { title: strings.GiftLink_To, component: AnyComponent( Button( - content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)), + content: AnyComponent(PeerTableCellComponent(context: context.component.context, theme: theme, strings: strings, peer: toPeer)), action: { if toPeer.id != accountContext.account.peerId { component.openPeer(toPeer) @@ -857,306 +859,6 @@ final class GiftLinkButtonContentComponent: CombinedComponent { } } -private final class TableComponent: CombinedComponent { - class Item: Equatable { - public let id: AnyHashable - public let title: String - public let component: AnyComponent - - public init(id: IdType, title: String, component: AnyComponent) { - self.id = AnyHashable(id) - self.title = title - self.component = component - } - - public static func == (lhs: Item, rhs: Item) -> Bool { - if lhs.id != rhs.id { - return false - } - if lhs.title != rhs.title { - return false - } - if lhs.component != rhs.component { - return false - } - return true - } - } - - private let theme: PresentationTheme - private let items: [Item] - - public init(theme: PresentationTheme, items: [Item]) { - self.theme = theme - self.items = items - } - - public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool { - if lhs.theme !== rhs.theme { - return false - } - if lhs.items != rhs.items { - return false - } - return true - } - - final class State: ComponentState { - var cachedBorderImage: (UIImage, PresentationTheme)? - } - - func makeState() -> State { - return State() - } - - public static var body: Body { - let leftColumnBackground = Child(Rectangle.self) - let verticalBorder = Child(Rectangle.self) - let titleChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) - let valueChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) - let borderChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) - let outerBorder = Child(Image.self) - - return { context in - let verticalPadding: CGFloat = 11.0 - let horizontalPadding: CGFloat = 12.0 - let borderWidth: CGFloat = 1.0 - - let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor - let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6) - - var leftColumnWidth: CGFloat = 0.0 - - var updatedTitleChildren: [_UpdatedChildComponent] = [] - var updatedValueChildren: [_UpdatedChildComponent] = [] - var updatedBorderChildren: [_UpdatedChildComponent] = [] - - for item in context.component.items { - let titleChild = titleChildren[item.id].update( - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: item.title, font: Font.regular(15.0), textColor: context.component.theme.list.itemPrimaryTextColor)) - )), - availableSize: context.availableSize, - transition: context.transition - ) - updatedTitleChildren.append(titleChild) - - if titleChild.size.width > leftColumnWidth { - leftColumnWidth = titleChild.size.width - } - } - - leftColumnWidth = max(100.0, leftColumnWidth + horizontalPadding * 2.0) - let rightColumnWidth = context.availableSize.width - leftColumnWidth - - var i = 0 - var rowHeights: [Int: CGFloat] = [:] - var totalHeight: CGFloat = 0.0 - - for item in context.component.items { - let titleChild = updatedTitleChildren[i] - let valueChild = valueChildren[item.id].update( - component: item.component, - availableSize: CGSize(width: rightColumnWidth - horizontalPadding * 2.0, height: context.availableSize.height), - transition: context.transition - ) - updatedValueChildren.append(valueChild) - - let rowHeight = max(40.0, max(titleChild.size.height, valueChild.size.height) + verticalPadding * 2.0) - rowHeights[i] = rowHeight - totalHeight += rowHeight - - if i < context.component.items.count - 1 { - let borderChild = borderChildren[item.id].update( - component: AnyComponent(Rectangle(color: borderColor)), - availableSize: CGSize(width: context.availableSize.width, height: borderWidth), - transition: context.transition - ) - updatedBorderChildren.append(borderChild) - } - - i += 1 - } - - let leftColumnBackground = leftColumnBackground.update( - component: Rectangle(color: context.component.theme.list.itemInputField.backgroundColor), - availableSize: CGSize(width: leftColumnWidth, height: totalHeight), - transition: context.transition - ) - context.add( - leftColumnBackground - .position(CGPoint(x: leftColumnWidth / 2.0, y: totalHeight / 2.0)) - ) - - let borderImage: UIImage - if let (currentImage, theme) = context.state.cachedBorderImage, theme === context.component.theme { - borderImage = currentImage - } else { - let borderRadius: CGFloat = 14.0 - borderImage = generateImage(CGSize(width: borderRadius * 2.0 + 6.0, height: borderRadius * 2.0 + 6.0), rotatedContext: { size, context in - let bounds = CGRect(origin: .zero, size: size) - context.setFillColor(backgroundColor.cgColor) - context.fill(bounds) - - let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil) - context.setBlendMode(.clear) - context.addPath(path) - context.fillPath() - - context.setBlendMode(.normal) - context.setStrokeColor(borderColor.cgColor) - context.setLineWidth(borderWidth) - context.addPath(path) - context.strokePath() - })!.stretchableImage(withLeftCapWidth: Int(borderRadius), topCapHeight: Int(borderRadius)) - context.state.cachedBorderImage = (borderImage, context.component.theme) - } - - let outerBorder = outerBorder.update( - component: Image(image: borderImage), - availableSize: CGSize(width: context.availableSize.width, height: totalHeight), - transition: context.transition - ) - context.add(outerBorder - .position(CGPoint(x: context.availableSize.width / 2.0, y: totalHeight / 2.0)) - ) - - let verticalBorder = verticalBorder.update( - component: Rectangle(color: borderColor), - availableSize: CGSize(width: borderWidth, height: totalHeight), - transition: context.transition - ) - context.add( - verticalBorder - .position(CGPoint(x: leftColumnWidth - borderWidth / 2.0, y: totalHeight / 2.0)) - ) - - i = 0 - var originY: CGFloat = 0.0 - for (titleChild, valueChild) in zip(updatedTitleChildren, updatedValueChildren) { - let rowHeight = rowHeights[i] ?? 0.0 - - let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size) - let valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + horizontalPadding, y: originY + verticalPadding), size: valueChild.size) - - context.add(titleChild - .position(titleFrame.center) - ) - - context.add(valueChild - .position(valueFrame.center) - ) - - if i < updatedBorderChildren.count { - let borderChild = updatedBorderChildren[i] - context.add(borderChild - .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + rowHeight - borderWidth / 2.0)) - ) - } - - originY += rowHeight - i += 1 - } - - return CGSize(width: context.availableSize.width, height: totalHeight) - } - } -} - -private final class PeerCellComponent: Component { - let context: AccountContext - let textColor: UIColor - let peer: EnginePeer? - - init(context: AccountContext, textColor: UIColor, peer: EnginePeer?) { - self.context = context - self.textColor = textColor - self.peer = peer - } - - static func ==(lhs: PeerCellComponent, rhs: PeerCellComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.textColor !== rhs.textColor { - return false - } - if lhs.peer != rhs.peer { - return false - } - return true - } - - final class View: UIView { - private let avatarNode: AvatarNode - private let text = ComponentView() - - private var component: PeerCellComponent? - private weak var state: EmptyComponentState? - - override init(frame: CGRect) { - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 13.0)) - - super.init(frame: frame) - - self.addSubnode(self.avatarNode) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(component: PeerCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.component = component - self.state = state - - self.avatarNode.setPeer( - context: component.context, - theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme, - peer: component.peer, - synchronousLoad: true - ) - - let avatarSize = CGSize(width: 22.0, height: 22.0) - let spacing: CGFloat = 6.0 - - let textSize = self.text.update( - transition: .immediate, - component: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString(string: component.peer?.compactDisplayTitle ?? "", font: Font.regular(15.0), textColor: component.textColor, paragraphAlignment: .left)) - ) - ), - environment: {}, - containerSize: CGSize(width: availableSize.width - avatarSize.width - spacing, height: availableSize.height) - ) - - let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height) - - let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize) - self.avatarNode.frame = avatarFrame - - if let view = self.text.view { - if view.superview == nil { - self.addSubview(view) - } - let textFrame = CGRect(origin: CGPoint(x: avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) - transition.setFrame(view: view, frame: textFrame) - } - - return size - } - } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} - private final class DustComponent: Component { let color: UIColor diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 71f3a6e4..b8f679fe 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -1679,34 +1679,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } } }) - - self.newPerksDisposable = combineLatest( - queue: Queue.mainQueue(), - ApplicationSpecificNotice.dismissedBusinessBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedBusinessLinksBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedBusinessIntroBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedBusinessChatbotsBadge(accountManager: context.sharedContext.accountManager) - ).startStrict(next: { [weak self] dismissedBusinessBadge, dismissedBusinessLinksBadge, dismissedBusinessIntroBadge, dismissedBusinessChatbotsBadge in - guard let self else { - return - } - var newPerks: [String] = [] - if !dismissedBusinessBadge { - newPerks.append(PremiumPerk.business.identifier) - } - if !dismissedBusinessLinksBadge { - newPerks.append(PremiumPerk.businessLinks.identifier) - } - if !dismissedBusinessIntroBadge { - newPerks.append(PremiumPerk.businessIntro.identifier) - } - if !dismissedBusinessChatbotsBadge { - newPerks.append(PremiumPerk.businessChatBots.identifier) - } - self.newPerks = newPerks - self.updated() - }) - + self.adsEnabledDisposable = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AdsEnabled(id: context.account.peerId)) |> deliverOnMainQueue).start(next: { [weak self] adsEnabled in guard let self else { @@ -2219,7 +2192,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { demoSubject = .todo case .business: demoSubject = .business - let _ = ApplicationSpecificNotice.setDismissedBusinessBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() default: demoSubject = .doubleLimits } @@ -2418,7 +2390,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext, initialData: initialData)) }) - let _ = ApplicationSpecificNotice.setDismissedBusinessChatbotsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() case .businessIntro: let _ = (accountContext.sharedContext.makeBusinessIntroSetupScreenInitialData(context: accountContext) |> take(1) @@ -2428,7 +2399,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeBusinessIntroSetupScreen(context: accountContext, initialData: initialData)) }) - let _ = ApplicationSpecificNotice.setDismissedBusinessIntroBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() case .businessLinks: let _ = (accountContext.sharedContext.makeBusinessLinksSetupScreenInitialData(context: accountContext) |> take(1) @@ -2438,7 +2408,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeBusinessLinksSetupScreen(context: accountContext, initialData: initialData)) }) - let _ = ApplicationSpecificNotice.setDismissedBusinessLinksBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() default: fatalError() } @@ -2457,13 +2426,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { demoSubject = .businessAwayMessage case .businessChatBots: demoSubject = .businessChatBots - let _ = ApplicationSpecificNotice.setDismissedBusinessChatbotsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() case .businessIntro: demoSubject = .businessIntro - let _ = ApplicationSpecificNotice.setDismissedBusinessIntroBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() case .businessLinks: demoSubject = .businessLinks - let _ = ApplicationSpecificNotice.setDismissedBusinessLinksBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() default: fatalError() } @@ -2960,6 +2926,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { private final class PremiumIntroScreenComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment + let overNavigationContainer: UIView let screenContext: PremiumIntroScreen.ScreenContext let mode: PremiumIntroScreen.Mode let source: PremiumSource @@ -2972,7 +2939,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent { let copyLink: (String) -> Void let shareLink: (String) -> Void - init(screenContext: PremiumIntroScreen.ScreenContext, mode: PremiumIntroScreen.Mode, source: PremiumSource, forceDark: Bool, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void, completion: @escaping () -> Void, copyLink: @escaping (String) -> Void, shareLink: @escaping (String) -> Void) { + init(overNavigationContainer: UIView, screenContext: PremiumIntroScreen.ScreenContext, mode: PremiumIntroScreen.Mode, source: PremiumSource, forceDark: Bool, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void, completion: @escaping () -> Void, copyLink: @escaping (String) -> Void, shareLink: @escaping (String) -> Void) { + self.overNavigationContainer = overNavigationContainer self.screenContext = screenContext self.mode = mode self.source = source @@ -3415,8 +3383,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent { let star = Child(PremiumStarComponent.self) let emoji = Child(EmojiHeaderComponent.self) let coin = Child(PremiumCoinComponent.self) - let topPanel = Child(BlurredBackgroundComponent.self) - let topSeparator = Child(Rectangle.self) let title = Child(MultilineTextComponent.self) let secondaryTitle = Child(MultilineTextWithEntitiesComponent.self) let bottomEdgeEffect = Child(EdgeEffectComponent.self) @@ -3504,22 +3470,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent { ) } - let topPanel = topPanel.update( - component: BlurredBackgroundComponent( - color: environment.theme.rootController.navigationBar.blurredBackgroundColor - ), - availableSize: CGSize(width: context.availableSize.width, height: environment.navigationHeight), - transition: context.transition - ) - - let topSeparator = topSeparator.update( - component: Rectangle( - color: environment.theme.rootController.navigationBar.separatorColor - ), - availableSize: CGSize(width: context.availableSize.width, height: UIScreenPixel), - transition: context.transition - ) - let titleString: String if case .premiumGift = context.component.source { titleString = environment.strings.Premium_PremiumGift_Title @@ -3743,14 +3693,12 @@ private final class PremiumIntroScreenComponent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) ) - let topPanelAlpha: CGFloat let titleOffset: CGFloat let titleScale: CGFloat let titleOffsetDelta = (topInset + 160.0) - (environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0) let titleAlpha: CGFloat if let topContentOffset = state.topContentOffset { - topPanelAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0 let topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0 titleOffset = topContentOffset let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta)) @@ -3762,36 +3710,29 @@ private final class PremiumIntroScreenComponent: CombinedComponent { titleAlpha = 1.0 } } else { - topPanelAlpha = 0.0 titleScale = 1.0 titleOffset = 0.0 titleAlpha = state.otherPeerName != nil ? 0.0 : 1.0 } - context.add(header + context.addWithExternalContainer(header .position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + header.size.height / 2.0 - 30.0 - titleOffset * titleScale)) - .scale(titleScale) + .scale(titleScale), + container: context.component.overNavigationContainer ) - context.add(topPanel - .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height / 2.0)) - .opacity(topPanelAlpha) - ) - context.add(topSeparator - .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height)) - .opacity(topPanelAlpha) - ) - - context.add(title + context.addWithExternalContainer(title .position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0))) .scale(titleScale) - .opacity(titleAlpha) + .opacity(titleAlpha), + container: context.component.overNavigationContainer ) - context.add(secondaryTitle + context.addWithExternalContainer(secondaryTitle .position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0))) .scale(titleScale) - .opacity(max(0.0, 1.0 - titleAlpha * 1.8)) + .opacity(max(0.0, 1.0 - titleAlpha * 1.8)), + container: context.component.overNavigationContainer ) var isUnusedGift = false @@ -3989,6 +3930,8 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { public weak var containerView: UIView? public var animationColor: UIColor? + private let overNavigationContainer: UIView + public convenience init(context: AccountContext, mode: Mode = .premium, source: PremiumSource, modal: Bool = true, forceDark: Bool = false, forceHasPremium: Bool = false) { self.init(screenContext: .accountContext(context), mode: mode, source: source, modal: modal, forceDark: forceDark, forceHasPremium: forceHasPremium) } @@ -4005,7 +3948,11 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { var completionImpl: (() -> Void)? var copyLinkImpl: ((String) -> Void)? var shareLinkImpl: ((String) -> Void)? + + self.overNavigationContainer = UIView() + super.init(component: PremiumIntroScreenComponent( + overNavigationContainer: self.overNavigationContainer, screenContext: screenContext, mode: mode, source: source, @@ -4029,11 +3976,11 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { shareLink: { link in shareLinkImpl?(link) } - ), navigationBarAppearance: .transparent, presentationMode: modal ? .modal : .default, theme: forceDark ? .dark : .default, updatedPresentationData: screenContext.updatedPresentationData) + ), navigationBarAppearance: .default, presentationMode: modal ? .modal : .default, theme: forceDark ? .dark : .default, updatedPresentationData: screenContext.updatedPresentationData) + + self._hasGlassStyle = true if modal { - let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed)) - self.navigationItem.setLeftBarButton(cancelItem, animated: false) self.navigationPresentation = .modal } else { self.navigationPresentation = .modalInLargeLayout @@ -4107,12 +4054,20 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { context.account.viewTracker.keepQuickRepliesApproximatelyUpdated() context.account.viewTracker.keepBusinessLinksApproximatelyUpdated() } + + if let navigationBar = self.navigationBar { + navigationBar.view.insertSubview(self.overNavigationContainer, aboveSubview: navigationBar.backgroundView) + } } required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + override public func viewDidLoad() { + super.viewDidLoad() + } + public override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.dismissAllTooltips() @@ -4145,10 +4100,10 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { super.containerLayoutUpdated(layout, transition: transition) if !self.didSetReady { - if let view = self.node.hostView.findTaggedView(tag: PremiumCoinComponent.View.Tag()) as? PremiumCoinComponent.View { + if let view = findTaggedComponentViewImpl(view: self.view, tag: PremiumCoinComponent.View.Tag()) as? PremiumCoinComponent.View { self.didSetReady = true self._ready.set(view.ready) - } else if let view = self.node.hostView.findTaggedView(tag: PremiumStarComponent.View.Tag()) as? PremiumStarComponent.View { + } else if let view = findTaggedComponentViewImpl(view: self.view, tag: PremiumStarComponent.View.Tag()) as? PremiumStarComponent.View { self.didSetReady = true self._ready.set(view.ready) @@ -4161,7 +4116,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { self.containerView = nil self.animationColor = nil } - } else if let view = self.node.hostView.findTaggedView(tag: EmojiHeaderComponent.View.Tag()) as? EmojiHeaderComponent.View { + } else if let view = findTaggedComponentViewImpl(view: self.view, tag: EmojiHeaderComponent.View.Tag()) as? EmojiHeaderComponent.View { self.didSetReady = true self._ready.set(view.ready) diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index e3ff83a2..447f9b2f 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -864,7 +864,7 @@ private final class LimitSheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in diff --git a/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift b/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift index c04daa16..7b357651 100644 --- a/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift @@ -170,7 +170,7 @@ private final class SheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in diff --git a/submodules/PremiumUI/Sources/SubscriptionsCountItem.swift b/submodules/PremiumUI/Sources/SubscriptionsCountItem.swift index 6fecfbfb..861c43d7 100644 --- a/submodules/PremiumUI/Sources/SubscriptionsCountItem.swift +++ b/submodules/PremiumUI/Sources/SubscriptionsCountItem.swift @@ -91,7 +91,7 @@ private final class SubscriptionsCountItemNode: ListViewItemNode { return textNode } - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.textNodes.forEach(self.addSubnode) } diff --git a/submodules/PresentationDataUtils/BUILD b/submodules/PresentationDataUtils/BUILD index 66b10496..28dcd03d 100644 --- a/submodules/PresentationDataUtils/BUILD +++ b/submodules/PresentationDataUtils/BUILD @@ -19,6 +19,7 @@ swift_library( "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/OverlayStatusController:OverlayStatusController", "//submodules/UrlWhitelist:UrlWhitelist", + "//submodules/TelegramUI/Components/AlertComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PresentationDataUtils/Sources/AlertTheme.swift b/submodules/PresentationDataUtils/Sources/AlertTheme.swift index 1fa7f48d..8353b3d4 100644 --- a/submodules/PresentationDataUtils/Sources/AlertTheme.swift +++ b/submodules/PresentationDataUtils/Sources/AlertTheme.swift @@ -4,35 +4,107 @@ import AlertUI import AccountContext import SwiftSignalKit import TelegramPresentationData +import AlertComponent -public func textAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, forceTheme: PresentationTheme? = nil, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false, dismissOnOutsideTap: Bool = true, linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil) -> AlertController { +public func textAlertController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + forceTheme: PresentationTheme? = nil, + title: String?, + text: String, + actions: [TextAlertAction], + actionLayout: TextAlertContentActionLayout = .horizontal, + allowInputInset: Bool = true, + parseMarkdown: Bool = false, + dismissOnOutsideTap: Bool = true, + linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil +) -> ViewController { return textAlertController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, forceTheme: forceTheme, title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, parseMarkdown: parseMarkdown, dismissOnOutsideTap: dismissOnOutsideTap, linkAction: linkAction) } -public func textAlertController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, forceTheme: PresentationTheme? = nil, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false, dismissOnOutsideTap: Bool = true, linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil) -> AlertController { +public func textAlertController( + sharedContext: SharedAccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + forceTheme: PresentationTheme? = nil, + title: String?, + text: String, + actions: [TextAlertAction], + actionLayout: TextAlertContentActionLayout = .horizontal, + allowInputInset: Bool = true, + parseMarkdown: Bool = false, + dismissOnOutsideTap: Bool = true, + linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil +) -> ViewController { var presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 } - if let forceTheme = forceTheme { + if let forceTheme { presentationData = presentationData.withUpdated(theme: forceTheme) } - return textAlertController(alertContext: AlertControllerContext(theme: AlertControllerTheme(presentationData: presentationData), themeSignal: (updatedPresentationData?.signal ?? sharedContext.presentationData) |> map { + let updatedPresentationDataSignal = (updatedPresentationData?.signal ?? sharedContext.presentationData) |> map { presentationData in var presentationData = presentationData if let forceTheme = forceTheme { presentationData = presentationData.withUpdated(theme: forceTheme) } - return AlertControllerTheme(presentationData: presentationData) - }), title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, parseMarkdown: parseMarkdown, dismissOnOutsideTap: dismissOnOutsideTap, linkAction: linkAction) + return presentationData + } + + let mappedActions: [AlertScreen.Action] = actions.map { action in + let mappedType: AlertScreen.Action.ActionType + switch action.type { + case .genericAction: + mappedType = .generic + case .defaultAction: + mappedType = .default + case .destructiveAction: + mappedType = .destructive + case .defaultDestructiveAction: + mappedType = .defaultDestructive + } + return AlertScreen.Action( + title: action.title, + type: mappedType, + action: action.action + ) + } + + let controller = AlertScreen( + configuration: AlertScreen.Configuration( + actionAlignment: actionLayout == .vertical ? .vertical : .default, + dismissOnOutsideTap: dismissOnOutsideTap, + allowInputInset: allowInputInset + ), + title: title, + text: text, + textAction: { attributes in + linkAction?(attributes, 0) + }, + actions: mappedActions, + updatedPresentationData: (initial: presentationData, signal: updatedPresentationDataSignal) + ) + return controller } -public func textAlertController(sharedContext: SharedAccountContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissOnOutsideTap: Bool = true) -> AlertController { - return textAlertController(alertContext: AlertControllerContext(theme: AlertControllerTheme(presentationData: sharedContext.currentPresentationData.with { $0 }), themeSignal: sharedContext.presentationData |> map { presentationData in AlertControllerTheme(presentationData: presentationData) }), title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, dismissOnOutsideTap: dismissOnOutsideTap) -} - -public func richTextAlertController(context: AccountContext, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissAutomatically: Bool = true) -> AlertController { +public func richTextAlertController( + context: AccountContext, + title: NSAttributedString?, + text: NSAttributedString, + actions: [TextAlertAction], + actionLayout: TextAlertContentActionLayout = .horizontal, + allowInputInset: Bool = true, + dismissAutomatically: Bool = true +) -> AlertController { return richTextAlertController(alertContext: AlertControllerContext(theme: AlertControllerTheme(presentationData: context.sharedContext.currentPresentationData.with { $0 }), themeSignal: context.sharedContext.presentationData |> map { presentationData in AlertControllerTheme(presentationData: presentationData) }), title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, dismissAutomatically: dismissAutomatically) } -public func textWithEntitiesAlertController(context: AccountContext, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissAutomatically: Bool = true) -> AlertController { +public func textWithEntitiesAlertController( + context: AccountContext, + title: NSAttributedString?, + text: NSAttributedString, + actions: [TextAlertAction], + actionLayout: TextAlertContentActionLayout = .horizontal, + allowInputInset: Bool = true, + dismissAutomatically: Bool = true +) -> AlertController { return textWithEntitiesAlertController( alertContext: AlertControllerContext( theme: AlertControllerTheme(presentationData: context.sharedContext.currentPresentationData.with { $0 }), diff --git a/submodules/PresentationDataUtils/Sources/OpenUrl.swift b/submodules/PresentationDataUtils/Sources/OpenUrl.swift index 31f70d75..1bb12626 100644 --- a/submodules/PresentationDataUtils/Sources/OpenUrl.swift +++ b/submodules/PresentationDataUtils/Sources/OpenUrl.swift @@ -6,6 +6,7 @@ import AccountContext import OverlayStatusController import UrlWhitelist import TelegramPresentationData +import AlertComponent public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: String, concealed: Bool, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, forceDark: Bool = false, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void, progress: Promise? = nil, alertDisplayUpdated: ((ViewController?) -> Void)? = nil) -> Disposable { var concealed = concealed @@ -95,8 +96,10 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: let alertController = textAlertController(context: context, forceTheme: forceDark ? presentationData.theme : nil, title: nil, text: presentationData.strings.Generic_OpenHiddenLinkAlert(displayUrl).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { disposable.set(openImpl()) })]) - alertController.dismissed = { _ in - alertDisplayUpdated?(nil) + if let alertController = alertController as? AlertScreen { + alertController.dismissed = { _ in + alertDisplayUpdated?(nil) + } } present(alertController) alertDisplayUpdated?(alertController) diff --git a/submodules/PromptUI/BUILD b/submodules/PromptUI/BUILD index ae10576e..80d83f29 100644 --- a/submodules/PromptUI/BUILD +++ b/submodules/PromptUI/BUILD @@ -10,14 +10,17 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/Postbox:Postbox", - "//submodules/TelegramCore:TelegramCore", - "//submodules/AccountContext:AccountContext", - "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", "//submodules/TelegramStringFormatting", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PromptUI/Sources/PromptController.swift b/submodules/PromptUI/Sources/PromptController.swift index 2581f538..d359be83 100644 --- a/submodules/PromptUI/Sources/PromptController.swift +++ b/submodules/PromptUI/Sources/PromptController.swift @@ -8,6 +8,9 @@ import TelegramCore import TelegramPresentationData import AccountContext import TelegramStringFormatting +import ComponentFlow +import AlertComponent +import AlertMultilineInputFieldComponent private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { private var theme: PresentationTheme @@ -194,327 +197,95 @@ private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDeleg } } -private final class PromptAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - private let text: String - private let titleFont: PromptControllerTitleFont - private let subtitle: String? - - private let textNode: ASTextNode - private let subtitleNode: ASTextNode? - let inputFieldNode: PromptInputFieldNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let disposable = MetaDisposable() - - private var validLayout: CGSize? - - private let hapticFeedback = HapticFeedback() - - var complete: (() -> Void)? { - didSet { - self.inputFieldNode.complete = self.complete - } - } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, subtitle: String?, value: String?, placeholder: String?, characterLimit: Int, displayCharacterLimit: Bool) { - self.strings = strings - self.text = text - self.titleFont = titleFont - self.subtitle = subtitle - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 2 - - if subtitle != nil { - let subtitleNode = ASTextNode() - subtitleNode.maximumNumberOfLines = 0 - self.subtitleNode = subtitleNode - } else { - self.subtitleNode = nil - } - - self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: placeholder ?? "", characterLimit: characterLimit, displayCharacterLimit: displayCharacterLimit) - self.inputFieldNode.text = value ?? "" - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.textNode) - if let subtitleNode = self.subtitleNode { - self.addSubnode(subtitleNode) - } - - self.addSubnode(self.inputFieldNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - self.actionNodes.last?.actionEnabled = true - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.inputFieldNode.updateHeight = { [weak self] in - if let strongSelf = self { - if let _ = strongSelf.validLayout { - strongSelf.requestLayout?(.animated(duration: 0.15, curve: .spring)) - } - } - } - -// self.inputFieldNode.textChanged = { [weak self] text in -// if let strongSelf = self, let lastNode = strongSelf.actionNodes.last { -// lastNode.actionEnabled = !text.isEmpty -// } -// } - - self.updateTheme(theme) - } - - deinit { - self.disposable.dispose() - } - - var value: String { - return self.inputFieldNode.text - } - - override func updateTheme(_ theme: AlertControllerTheme) { - let titleFontValue: UIFont - switch self.titleFont { - case .regular: - titleFontValue = Font.regular(13.0) - case .bold: - titleFontValue = Font.semibold(17.0) - } - self.textNode.attributedText = NSAttributedString(string: self.text, font: titleFontValue, textColor: theme.primaryColor, paragraphAlignment: .center) - - if let subtitle = self.subtitle, let subtitleNode = self.subtitleNode { - subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) - } - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) - - let hadValidLayout = self.validLayout != nil - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - let spacing: CGFloat = 5.0 - - let titleSize = CGSize() -// transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) -// origin.y += titleSize.height + 4.0 - - let textSize = self.textNode.measure(measureSize) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 6.0 + spacing - - var subtitleSize: CGSize? - if let subtitleNode { - let subtitleSizeValue = subtitleNode.measure(measureSize) - subtitleSize = subtitleSizeValue - transition.updateFrame(node: subtitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSizeValue.width) / 2.0), y: origin.y), size: subtitleSizeValue)) - origin.y += subtitleSizeValue.height + 6.0 + spacing - } - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) - - var contentWidth = max(titleSize.width, minActionsWidth) - if let subtitleSize { - contentWidth = max(contentWidth, subtitleSize.width) - } - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - let inputFieldWidth = resultWidth - let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) - let inputHeight = inputFieldHeight - transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight)) - transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0) - - var resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom) - if let subtitleSize { - resultSize.height += subtitleSize.height + spacing - } - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if !hadValidLayout { - self.inputFieldNode.activateInput() - } - - return resultSize - } - - func animateError() { - self.inputFieldNode.layer.addShakeAnimation() - self.hapticFeedback.error() - } -} - public enum PromptControllerTitleFont { case regular case bold } -public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, subtitle: String? = nil, value: String?, placeholder: String? = nil, characterLimit: Int = 1000, displayCharacterLimit: Bool = false, apply: @escaping (String?) -> Void) -> AlertController { - let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 } +public func promptController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + text: String, + titleFont: PromptControllerTitleFont = .regular, + subtitle: String? = nil, + value: String?, + placeholder: String? = nil, + characterLimit: Int = 1000, + displayCharacterLimit: Bool = false, + apply: @escaping (String?) -> Void, + dismissed: @escaping () -> Void = {} +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings - var dismissImpl: ((Bool) -> Void)? - var applyImpl: (() -> Void)? - - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - apply(nil) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: { - dismissImpl?(true) - applyImpl?() - })] - - let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, subtitle: subtitle, value: value, placeholder: placeholder, characterLimit: characterLimit, displayCharacterLimit: displayCharacterLimit) - contentNode.complete = { - dismissImpl?(true) - applyImpl?() + let inputState = AlertMultilineInputFieldComponent.ExternalState() + + var content: [AnyComponentWithIdentity] = [] + if subtitle == nil && titleFont == .regular { + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + } else { + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: text) + ) + )) } - applyImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - apply(contentNode.value) + if let subtitle { + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(subtitle)) + ) + )) + } + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertMultilineInputFieldComponent( + context: context, + initialValue: value.flatMap { NSAttributedString(string: $0) }, + placeholder: placeholder ?? "", + characterLimit: characterLimit, + formatMenuAvailability: .none, + emptyLineHandling: .notAllowed, + isInitiallyFocused: true, + externalState: inputState + ) + ) + )) + + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) } - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.inputFieldNode.updateTheme(presentationData.theme) - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - } - dismissImpl = { [weak controller] animated in - contentNode.inputFieldNode.deactivateInput() - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel, action: { + apply(nil) + }), + .init(title: strings.Common_Done, type: .default, action: { + apply(inputState.value.string) + }) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + alertController.dismissed = { byOutsideTap in + if byOutsideTap { + dismissed() } } - return controller + return alertController } private final class AuthAlertContentNode: AlertContentNode { diff --git a/submodules/QrCodeUI/BUILD b/submodules/QrCodeUI/BUILD index 0308b422..e7eb7e22 100644 --- a/submodules/QrCodeUI/BUILD +++ b/submodules/QrCodeUI/BUILD @@ -31,6 +31,15 @@ swift_library( "//submodules/LegacyComponents:LegacyComponents", "//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI", "//submodules/ImageContentAnalysis:ImageContentAnalysis", + "//submodules/ComponentFlow", + "//submodules/Components/SheetComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift index 3649da5e..ceab808e 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift @@ -77,7 +77,7 @@ public final class QrCodeScanScreen: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - let navigationBarTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) + let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close))) diff --git a/submodules/QrCodeUI/Sources/QrCodeScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScreen.swift index 94a293a0..9c975cc2 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScreen.swift @@ -1,17 +1,23 @@ import Foundation import UIKit -import AsyncDisplayKit import Display +import ComponentFlow import SwiftSignalKit import TelegramCore import TelegramPresentationData -import AppBundle -import QrCode +import ViewControllerComponent +import SheetComponent +import BalancedTextComponent +import MultilineTextComponent +import BundleIconComponent +import ButtonComponent +import GlassBarButtonComponent +import PlainButtonComponent import AccountContext -import SolidRoundedButtonNode -import AnimatedStickerNode -import TelegramAnimatedStickerNode -import PresentationDataUtils +import Markdown +import TextFormat +import QrCode +import LottieComponent private func shareQrCode(context: AccountContext, link: String, ecl: String, view: UIView) { let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Chat/Links/QrLogo")), ecl: ecl) @@ -24,7 +30,7 @@ private func shareQrCode(context: AccountContext, link: String, ecl: String, vie guard let image = image else { return } - + let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil) if let window = view.window { activityController.popoverPresentationController?.sourceView = window @@ -34,13 +40,336 @@ private func shareQrCode(context: AccountContext, link: String, ecl: String, vie }) } -public final class QrCodeScreen: ViewController { +private final class SheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let subject: QrCodeScreen.Subject + let dismiss: () -> Void + + init( + context: AccountContext, + subject: QrCodeScreen.Subject, + dismiss: @escaping () -> Void + ) { + self.context = context + self.subject = subject + self.dismiss = dismiss + } + + static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { + if lhs.context !== rhs.context { + return false + } + return true + } + + final class State: ComponentState { + private let idleTimerExtensionDisposable = MetaDisposable() + + private var initialBrightness: CGFloat? + private var brightnessArguments: (Double, Double, CGFloat, CGFloat)? + private var animator: ConstantDisplayLinkAnimator? + + init(context: AccountContext) { + super.init() + + self.idleTimerExtensionDisposable.set(context.sharedContext.applicationBindings.pushIdleTimerExtension()) + + self.animator = ConstantDisplayLinkAnimator(update: { [weak self] in + self?.updateBrightness() + }) + self.animator?.isPaused = true + + self.initialBrightness = UIScreen.main.brightness + self.brightnessArguments = (CACurrentMediaTime(), 0.3, UIScreen.main.brightness, 1.0) + self.updateBrightness() + } + + deinit { + self.idleTimerExtensionDisposable.dispose() + self.animator?.invalidate() + + if UIScreen.main.brightness > 0.99, let initialBrightness = self.initialBrightness { + self.brightnessArguments = (CACurrentMediaTime(), 0.3, UIScreen.main.brightness, initialBrightness) + self.updateBrightness() + } + } + + private func updateBrightness() { + if let (startTime, duration, initial, target) = self.brightnessArguments { + self.animator?.isPaused = false + + let t = CGFloat(max(0.0, min(1.0, (CACurrentMediaTime() - startTime) / duration))) + let value = initial + (target - initial) * t + + UIScreen.main.brightness = value + + if t >= 1.0 { + self.brightnessArguments = nil + self.animator?.isPaused = true + } + } else { + self.animator?.isPaused = true + } + } + } + + func makeState() -> State { + return State(context: self.context) + } + + static var body: Body { + let qrCode = Child(PlainButtonComponent.self) + let closeButton = Child(GlassBarButtonComponent.self) + let title = Child(Text.self) + let text = Child(BalancedTextComponent.self) + + let button = Child(ButtonComponent.self) + + return { context in + let environment = context.environment[EnvironmentType.self] + let component = context.component + let controller = environment.controller() + + let theme = environment.theme + let strings = environment.strings + + let link = component.subject.link + let ecl = component.subject.ecl + + let titleString: String + let textString: String + switch component.subject { + case let .invite(_, type): + titleString = strings.InviteLink_QRCode_Title + switch type { + case .group: + textString = strings.InviteLink_QRCode_Info + case .channel: + textString = strings.InviteLink_QRCode_InfoChannel + case .groupCall: + textString = strings.InviteLink_QRCode_InfoGroupCall + } + case .chatFolder: + titleString = strings.InviteLink_QRCodeFolder_Title + textString = strings.InviteLink_QRCodeFolder_Text + default: + titleString = "" + textString = "" + } + + var contentSize = CGSize(width: context.availableSize.width, height: 36.0) + + let closeButton = closeButton.update( + component: GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { _ in + component.dismiss() + } + ), + availableSize: CGSize(width: 40.0, height: 40.0), + transition: .immediate + ) + context.add(closeButton + .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0)) + ) + + let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0 + + let title = title.update( + component: Text(text: titleString, font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor), + availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), + transition: .immediate + ) + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height)) + ) + contentSize.height += title.size.height + contentSize.height += 13.0 + + let qrCode = qrCode.update( + component: PlainButtonComponent( + content: AnyComponent(QrCodeComponent(context: component.context, link: link, ecl: ecl)), + action: { [weak controller] in + if let view = controller?.view { + shareQrCode(context: component.context, link: link, ecl: ecl, view: view) + } + }, + animateScale: false + ), + availableSize: CGSize(width: 260.0, height: 260.0), + transition: .immediate + ) + context.add(qrCode + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + qrCode.size.height / 2.0)) + ) + contentSize.height += qrCode.size.height + contentSize.height += 17.0 + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + let textColor = theme.actionSheet.primaryTextColor + let linkColor = theme.actionSheet.controlAccentColor + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + + let text = text.update( + component: BalancedTextComponent( + text: .markdown( + text: textString, + attributes: markdownAttributes + ), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), + transition: .immediate + ) + context.add(text + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) + ) + contentSize.height += text.size.height + contentSize.height += 23.0 + + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) + let button = button.update( + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), + cornerRadius: 10.0, + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.InviteLink_QRCode_Share, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) + ), + isEnabled: true, + displaysProgress: false, + action: { [weak controller] in + if let view = controller?.view { + shareQrCode(context: component.context, link: link, ecl: ecl, view: view) + } + } + ), + availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), + transition: .immediate + ) + context.add(button + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) + ) + contentSize.height += button.size.height + contentSize.height += buttonInsets.bottom + + return contentSize + } + } +} + +private final class QrCodeSheetComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + private let context: AccountContext + private let subject: QrCodeScreen.Subject + + init( + context: AccountContext, + subject: QrCodeScreen.Subject + ) { + self.context = context + self.subject = subject + } + + static func ==(lhs: QrCodeSheetComponent, rhs: QrCodeSheetComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + return true + } + + static var body: Body { + let sheet = Child(SheetComponent<(EnvironmentType)>.self) + let animateOut = StoredActionSlot(Action.self) + + return { context in + let environment = context.environment[EnvironmentType.self] + + let controller = environment.controller + + let sheet = sheet.update( + component: SheetComponent( + content: AnyComponent(SheetContent( + context: context.component.context, + subject: context.component.subject, + dismiss: { + animateOut.invoke(Action { _ in + if let controller = controller() as? QrCodeScreen { + controller.dismiss(completion: nil) + } + }) + } + )), + style: .glass, + backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), + followContentSizeChanges: true, + clipsContent: true, + animateOut: animateOut + ), + environment: { + environment + SheetComponentEnvironment( + isDisplaying: environment.value.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { animated in + if animated { + animateOut.invoke(Action { _ in + if let controller = controller() as? QrCodeScreen { + controller.dismiss(completion: nil) + } + }) + } else { + if let controller = controller() as? QrCodeScreen { + controller.dismiss(completion: nil) + } + } + } + ) + }, + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(sheet + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + return context.availableSize + } + } +} + +public final class QrCodeScreen: ViewControllerComponentContainer { public enum SubjectType { case group case channel case groupCall } - + public enum Subject { case peer(peer: EnginePeer) case invite(invite: ExportedInvitation, type: SubjectType) @@ -48,471 +377,181 @@ public final class QrCodeScreen: ViewController { var link: String { switch self { - case let .peer(peer): - return "https://t.me/\(peer.addressName ?? "")" - case let .invite(invite, _): - return invite.link ?? "" - case let .chatFolder(slug): - if slug.hasPrefix("https://") { - return slug - } else { - return "https://t.me/addlist/\(slug)" - } + case let .peer(peer): + return "https://t.me/\(peer.addressName ?? "")" + case let .invite(invite, _): + return invite.link ?? "" + case let .chatFolder(slug): + if slug.hasPrefix("https://") { + return slug + } else { + return "https://t.me/addlist/\(slug)" + } } } var ecl: String { switch self { - case .peer: - return "Q" - case .invite: - return "Q" - case .chatFolder: - return "Q" + case .peer: + return "Q" + case .invite: + return "Q" + case .chatFolder: + return "Q" } } } - private var controllerNode: Node { - return self.displayNode as! Node - } - - private var animatedIn = false - private let context: AccountContext - private let subject: QrCodeScreen.Subject - private var presentationData: PresentationData - private var presentationDataDisposable: Disposable? - - private var initialBrightness: CGFloat? - private var brightnessArguments: (Double, Double, CGFloat, CGFloat)? - - private var animator: ConstantDisplayLinkAnimator? - - private let idleTimerExtensionDisposable = MetaDisposable() - - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, subject: QrCodeScreen.Subject) { + public init( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + subject: QrCodeScreen.Subject + ) { self.context = context - self.subject = subject - self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + super.init( + context: context, + component: QrCodeSheetComponent( + context: context, + subject: subject + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: .default // + ) - super.init(navigationBarPresentationData: nil) - - self.statusBar.statusBarStyle = .Ignore - - self.blocksBackgroundWhenInOverlay = true - - self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.presentationData = presentationData - strongSelf.controllerNode.updatePresentationData(presentationData) - } - }) - - self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension()) - - self.statusBar.statusBarStyle = .Ignore - - self.animator = ConstantDisplayLinkAnimator(update: { [weak self] in - self?.updateBrightness() - }) - self.animator?.isPaused = true + self.navigationPresentation = .flatModal } - - required init(coder aDecoder: NSCoder) { + + required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - deinit { - self.presentationDataDisposable?.dispose() - self.idleTimerExtensionDisposable.dispose() - self.animator?.invalidate() - } - - override public func loadDisplayNode() { - self.displayNode = Node(context: self.context, presentationData: self.presentationData, subject: self.subject) - self.controllerNode.dismiss = { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) - } - self.controllerNode.cancel = { [weak self] in - self?.dismiss() - } - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !self.animatedIn { - self.animatedIn = true - self.controllerNode.animateIn() - - self.initialBrightness = UIScreen.main.brightness - self.brightnessArguments = (CACurrentMediaTime(), 0.3, UIScreen.main.brightness, 1.0) - self.updateBrightness() - } - } - - private func updateBrightness() { - if let (startTime, duration, initial, target) = self.brightnessArguments { - self.animator?.isPaused = false - - let t = CGFloat(max(0.0, min(1.0, (CACurrentMediaTime() - startTime) / duration))) - let value = initial + (target - initial) * t - - UIScreen.main.brightness = value - - if t >= 1.0 { - self.brightnessArguments = nil - self.animator?.isPaused = true - } - } else { - self.animator?.isPaused = true - } - } - - override public func dismiss(completion: (() -> Void)? = nil) { - if UIScreen.main.brightness > 0.99, let initialBrightness = self.initialBrightness { - self.brightnessArguments = (CACurrentMediaTime(), 0.3, UIScreen.main.brightness, initialBrightness) - self.updateBrightness() - } - - self.controllerNode.animateOut(completion: completion) - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) - } - - class Node: ViewControllerTracingNode, ASScrollViewDelegate { - private let context: AccountContext - private let subject: QrCodeScreen.Subject - private var presentationData: PresentationData - - private let dimNode: ASDisplayNode - private let wrappingScrollNode: ASScrollNode - private let contentContainerNode: ASDisplayNode - private let backgroundNode: ASDisplayNode - private let contentBackgroundNode: ASDisplayNode - private let titleNode: ASTextNode - private let cancelButton: HighlightableButtonNode - - private let textNode: ImmediateTextNode - private let qrButtonNode: HighlightTrackingButtonNode - private let qrImageNode: TransformImageNode - private let qrIconNode: AnimatedStickerNode - private var qrCodeSize: Int? - private let buttonNode: SolidRoundedButtonNode - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - var completion: ((Int32) -> Void)? - var dismiss: (() -> Void)? - var cancel: (() -> Void)? - - init(context: AccountContext, presentationData: PresentationData, subject: QrCodeScreen.Subject) { - self.context = context - self.subject = subject - self.presentationData = presentationData - - self.wrappingScrollNode = ASScrollNode() - self.wrappingScrollNode.view.alwaysBounceVertical = true - self.wrappingScrollNode.view.delaysContentTouches = false - self.wrappingScrollNode.view.canCancelContentTouches = true - - self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) - - self.contentContainerNode = ASDisplayNode() - self.contentContainerNode.isOpaque = false - - self.backgroundNode = ASDisplayNode() - self.backgroundNode.clipsToBounds = true - self.backgroundNode.cornerRadius = 16.0 - - let backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor - let textColor = self.presentationData.theme.actionSheet.primaryTextColor - let secondaryTextColor = self.presentationData.theme.actionSheet.secondaryTextColor - let accentColor = self.presentationData.theme.actionSheet.controlAccentColor - - self.contentBackgroundNode = ASDisplayNode() - self.contentBackgroundNode.backgroundColor = backgroundColor - - let title: String - let text: String - switch subject { - case let .invite(_, type): - title = self.presentationData.strings.InviteLink_QRCode_Title - switch type { - case .group: - text = self.presentationData.strings.InviteLink_QRCode_Info - case .channel: - text = self.presentationData.strings.InviteLink_QRCode_InfoChannel - case .groupCall: - text = self.presentationData.strings.InviteLink_QRCode_InfoGroupCall - } - case .chatFolder: - title = self.presentationData.strings.InviteLink_QRCodeFolder_Title - text = self.presentationData.strings.InviteLink_QRCodeFolder_Text - default: - title = "" - text = "" - } - - self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor) - - self.cancelButton = HighlightableButtonNode() - self.cancelButton.setTitle(self.presentationData.strings.Common_Done, with: Font.bold(17.0), with: accentColor, for: .normal) - - self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, isShimmering: false) - - self.textNode = ImmediateTextNode() - self.textNode.maximumNumberOfLines = 3 - self.textNode.textAlignment = .center - - self.qrButtonNode = HighlightTrackingButtonNode() - self.qrImageNode = TransformImageNode() - self.qrImageNode.clipsToBounds = true - self.qrImageNode.cornerRadius = 16.0 - - self.qrIconNode = DefaultAnimatedStickerNodeImpl() - self.qrIconNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "PlaneLogo"), width: 240, height: 240, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) - self.qrIconNode.visibility = true - - super.init() - - self.backgroundColor = nil - self.isOpaque = false - - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - self.addSubnode(self.dimNode) - - self.wrappingScrollNode.view.delegate = self.wrappedScrollViewDelegate - self.addSubnode(self.wrappingScrollNode) - - self.wrappingScrollNode.addSubnode(self.backgroundNode) - self.wrappingScrollNode.addSubnode(self.contentContainerNode) - - self.backgroundNode.addSubnode(self.contentBackgroundNode) - self.contentContainerNode.addSubnode(self.titleNode) - self.contentContainerNode.addSubnode(self.cancelButton) - self.contentContainerNode.addSubnode(self.buttonNode) - - self.contentContainerNode.addSubnode(self.textNode) - self.contentContainerNode.addSubnode(self.qrImageNode) - self.contentContainerNode.addSubnode(self.qrIconNode) - self.contentContainerNode.addSubnode(self.qrButtonNode) - - self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: secondaryTextColor) - self.buttonNode.title = self.presentationData.strings.InviteLink_QRCode_Share - - self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) - self.buttonNode.pressed = { [weak self] in - if let strongSelf = self{ - shareQrCode(context: strongSelf.context, link: subject.link, ecl: subject.ecl, view: strongSelf.view) - } - } - - self.qrImageNode.setSignal(qrCode(string: subject.link, color: .black, backgroundColor: .white, icon: .cutout, ecl: subject.ecl) |> beforeNext { [weak self] size, _ in - guard let strongSelf = self else { - return - } - strongSelf.qrCodeSize = size - if let (layout, navigationHeight) = strongSelf.containerLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate) - } - } |> map { $0.1 }, attemptSynchronously: true) - - self.qrButtonNode.addTarget(self, action: #selector(self.qrPressed), forControlEvents: .touchUpInside) - self.qrButtonNode.highligthedChanged = { [weak self] highlighted in - guard let strongSelf = self else { - return - } - if highlighted { - strongSelf.qrImageNode.alpha = 0.4 - strongSelf.qrIconNode.alpha = 0.4 - } else { - strongSelf.qrImageNode.layer.animateAlpha(from: strongSelf.qrImageNode.alpha, to: 1.0, duration: 0.2) - strongSelf.qrImageNode.alpha = 1.0 - strongSelf.qrIconNode.layer.animateAlpha(from: strongSelf.qrIconNode.alpha, to: 1.0, duration: 0.2) - strongSelf.qrIconNode.alpha = 1.0 - } - } - } - - @objc private func qrPressed() { - self.buttonNode.pressed?() - } - - func updatePresentationData(_ presentationData: PresentationData) { - let previousTheme = self.presentationData.theme - self.presentationData = presentationData - - self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor - self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) - self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor) - - if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - - self.cancelButton.setTitle(self.presentationData.strings.Common_Done, with: Font.bold(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) - self.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) - } - - override func didLoad() { - super.didLoad() - - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never - } - } - - @objc func cancelButtonPressed() { - self.cancel?() - } - - @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.cancelButtonPressed() - } - } - - func animateIn() { - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) - - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - - let dimPosition = self.dimNode.layer.position - self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - } - - func animateOut(completion: (() -> Void)? = nil) { - var dimCompleted = false - var offsetCompleted = false - - let internalCompletion: () -> Void = { [weak self] in - if let strongSelf = self, dimCompleted && offsetCompleted { - strongSelf.dismiss?() - } - completion?() - } - - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in - dimCompleted = true - internalCompletion() - }) - - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - let dimPosition = self.dimNode.layer.position - self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - offsetCompleted = true - internalCompletion() - }) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.bounds.contains(point) { - if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) { - return self.dimNode.view - } - } - return super.hitTest(point, with: event) - } - - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - let contentOffset = scrollView.contentOffset - let additionalTopHeight = max(0.0, -contentOffset.y) - - if additionalTopHeight >= 30.0 { - self.cancelButtonPressed() - } - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - self.containerLayout = (layout, navigationBarHeight) - - var insets = layout.insets(options: [.statusBar, .input]) - insets.top = 32.0 - - let makeImageLayout = self.qrImageNode.asyncLayout() - let imageSide: CGFloat = 240.0 - let imageSize = CGSize(width: imageSide, height: imageSide) - let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil)) - let _ = imageApply() - - let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) - - let imageFrame = CGRect(origin: CGPoint(x: floor((width - imageSize.width) / 2.0), y: insets.top + 16.0), size: imageSize) - transition.updateFrame(node: self.qrImageNode, frame: imageFrame) - transition.updateFrame(node: self.qrButtonNode, frame: imageFrame) - - if let qrCodeSize = self.qrCodeSize { - let (_, cutoutFrame, _) = qrCodeCutout(size: qrCodeSize, dimensions: imageSize, scale: nil) - self.qrIconNode.updateLayout(size: cutoutFrame.size) - transition.updateBounds(node: self.qrIconNode, bounds: CGRect(origin: CGPoint(), size: cutoutFrame.size)) - transition.updatePosition(node: self.qrIconNode, position: imageFrame.center.offsetBy(dx: 0.0, dy: -1.0)) - } - - let inset: CGFloat = 32.0 - var textSize = self.textNode.updateLayout(CGSize(width: width - inset * 3.0, height: CGFloat.greatestFiniteMagnitude)) - let textFrame = CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: imageFrame.maxY + 20.0), size: textSize) - transition.updateFrame(node: self.textNode, frame: textFrame) - - var textSpacing: CGFloat = 111.0 - if case .compact = layout.metrics.widthClass, layout.size.width > layout.size.height { - textSize = CGSize() - self.textNode.isHidden = true - textSpacing = 52.0 - } else { - self.textNode.isHidden = false - } - - let buttonSideInset: CGFloat = 16.0 - let bottomInset = insets.bottom + 10.0 - let buttonWidth = layout.size.width - buttonSideInset * 2.0 - let buttonHeight: CGFloat = 50.0 - - let buttonFrame = CGRect(origin: CGPoint(x: floor((width - buttonWidth) / 2.0), y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight)) - transition.updateFrame(node: self.buttonNode, frame: buttonFrame) - let _ = self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition) - - let titleHeight: CGFloat = 54.0 - let contentHeight = titleHeight + textSize.height + imageSize.height + bottomInset + textSpacing - - let sideInset = floor((layout.size.width - width) / 2.0) - let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight)) - let contentFrame = contentContainerFrame - - var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0)) - if backgroundFrame.minY < contentFrame.minY { - backgroundFrame.origin.y = contentFrame.minY - } - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight)) - let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize) - transition.updateFrame(node: self.titleNode, frame: titleFrame) - - let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight)) - let cancelFrame = CGRect(origin: CGPoint(x: width - cancelSize.width - 16.0, y: 16.0), size: cancelSize) - transition.updateFrame(node: self.cancelButton, frame: cancelFrame) - - let buttonInset: CGFloat = 16.0 - let doneButtonHeight = self.buttonNode.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) - transition.updateFrame(node: self.buttonNode, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: doneButtonHeight)) - - transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { + view.dismissAnimated() } } } + +private final class QrCodeComponent: Component { + let context: AccountContext + let link: String + let ecl: String + + init( + context: AccountContext, + link: String, + ecl: String + ) { + self.context = context + self.link = link + self.ecl = ecl + } + + static func ==(lhs: QrCodeComponent, rhs: QrCodeComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.link != rhs.link { + return false + } + if lhs.ecl != rhs.ecl { + return false + } + return true + } + + final class View: UIView { + private var component: QrCodeComponent? + private var state: EmptyComponentState? + + private let imageNode: TransformImageNode + private let icon = ComponentView() + + private var qrCodeSize: Int? + + private var isUpdating = false + + override init(frame: CGRect) { + self.imageNode = TransformImageNode() + + super.init(frame: frame) + + self.backgroundColor = UIColor.white + self.clipsToBounds = true + self.layer.cornerRadius = 24.0 + self.layer.allowsGroupOpacity = true + + self.addSubview(self.imageNode.view) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: QrCodeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + let previousComponent = self.component + self.component = component + self.state = state + + if previousComponent?.link != component.link { + self.imageNode.setSignal(qrCode(string: component.link, color: .black, backgroundColor: .white, icon: .cutout, ecl: component.ecl) |> beforeNext { [weak self] size, _ in + guard let self else { + return + } + self.qrCodeSize = size + if !self.isUpdating { + self.state?.updated() + } + } |> map { $0.1 }, attemptSynchronously: true) + } + + let size = CGSize(width: 256.0, height: 256.0) + let imageSize = CGSize(width: 240.0, height: 240.0) + + let makeImageLayout = self.imageNode.asyncLayout() + let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil)) + let _ = imageApply() + let imageFrame = CGRect(origin: CGPoint(x: (size.width - imageSize.width) / 2.0, y: (size.height - imageSize.height) / 2.0), size: imageSize) + self.imageNode.frame = imageFrame + + if let qrCodeSize = self.qrCodeSize { + let (_, cutoutFrame, _) = qrCodeCutout(size: qrCodeSize, dimensions: imageSize, scale: nil) + + let _ = self.icon.update( + transition: .immediate, + component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "PlaneLogo"), + loop: true + )), + environment: {}, + containerSize: cutoutFrame.size + ) + if let iconView = self.icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + iconView.bounds = CGRect(origin: CGPoint(), size: cutoutFrame.size) + iconView.center = imageFrame.center.offsetBy(dx: 0.0, dy: -1.0) + } + } + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/SearchBarNode/BUILD b/submodules/SearchBarNode/BUILD index 1842fa27..343809aa 100644 --- a/submodules/SearchBarNode/BUILD +++ b/submodules/SearchBarNode/BUILD @@ -21,6 +21,8 @@ swift_library( "//submodules/AvatarNode:AvatarNode", "//submodules/AccountContext:AccountContext", "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/Components/ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/SearchBarNode/Sources/SearchBarNode.swift b/submodules/SearchBarNode/Sources/SearchBarNode.swift index 131323d3..849b0919 100644 --- a/submodules/SearchBarNode/Sources/SearchBarNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarNode.swift @@ -11,6 +11,7 @@ import AvatarNode import AccountContext import ComponentFlow import EmojiStatusComponent +import ComponentDisplayAdapters private func generateLoupeIcon(color: UIColor) -> UIImage? { return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: color) @@ -369,6 +370,7 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate { } var theme: SearchBarNodeTheme + let style: SearchBarStyle fileprivate func layoutTokens(transition: ContainedViewLayoutTransition = .immediate) { var hasSelected = false @@ -508,6 +510,12 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate { } fileprivate var tokensWidth: CGFloat = 0.0 + var tokensInsetWidth: CGFloat { + if self.tokensWidth == 0.0 { + return 0.0 + } + return self.tokensWidth + 8.0 + } private let measurePrefixLabel: ImmediateTextNode let prefixLabel: ImmediateTextNode @@ -519,8 +527,9 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate { } } - init(theme: SearchBarNodeTheme) { + init(theme: SearchBarNodeTheme, style: SearchBarStyle) { self.theme = theme + self.style = style self.placeholderLabel = ImmediateTextNode() self.placeholderLabel.isUserInteractionEnabled = false @@ -547,7 +556,10 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate { super.init(frame: CGRect()) - self.addSubnode(self.placeholderLabel) + if case .glass = style { + } else { + self.addSubnode(self.placeholderLabel) + } self.addSubnode(self.prefixLabel) self.addSubnode(self.clippingNode) self.clippingNode.addSubnode(self.tokenContainerNode) @@ -682,11 +694,19 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate { let textRect = self.textRect(forBounds: bounds) let labelSize = self.placeholderLabel.updateLayout(textRect.size) - self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX + placeholderXOffset, y: textRect.minY + textOffset + placeholderYOffset), size: labelSize) + + switch self.style { + case .glass, .inlineNavigation: + placeholderYOffset += 0.0 + case .legacy, .modern: + break + } + + self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX + placeholderXOffset, y: floorToScreenPixels(bounds.height - labelSize.height) * 0.5), size: labelSize) let prefixSize = self.prefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height)) let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0) - self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: prefixBounds.minY + textOffset + placeholderYOffset), size: prefixSize) + self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: floorToScreenPixels(bounds.height - prefixSize.height) * 0.5), size: prefixSize) } override func deleteBackward() { @@ -813,40 +833,52 @@ public final class SearchBarNodeTheme: Equatable { public enum SearchBarStyle { case modern case legacy + case inlineNavigation + case glass var font: UIFont { switch self { - case .modern: - return Font.regular(17.0) - case .legacy: - return Font.regular(14.0) + case .modern, .inlineNavigation, .glass: + return Font.regular(17.0) + case .legacy: + return Font.regular(14.0) } } var cornerDiameter: CGFloat { switch self { - case .modern: - return 21.0 - case .legacy: - return 14.0 + case .modern, .inlineNavigation: + return 21.0 + case .glass: + return 22.0 + case .legacy: + return 14.0 } } var height: CGFloat { switch self { - case .modern: - return 36.0 - case .legacy: - return 28.0 + case .inlineNavigation: + return 48.0 + case .glass: + return 44.0 + case .modern: + return 36.0 + case .legacy: + return 28.0 } } var padding: CGFloat { switch self { - case .modern: - return 10.0 - case .legacy: - return 8.0 + case .inlineNavigation: + return 0.0 + case .glass: + return 20.0 + case .modern: + return 10.0 + case .legacy: + return 8.0 } } } @@ -868,6 +900,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { public var tokensUpdated: (([SearchBarToken]) -> Void)? + private let inlineSearchPlaceholder: SearchBarPlaceholderNode + private var inlineSearchPlaceholderContentsView: SearchBarPlaceholderContentView? + private let backgroundNode: NavigationBackgroundNode private let separatorNode: ASDisplayNode private let textBackgroundNode: ASDisplayNode @@ -877,6 +912,8 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { private let clearButton: HighlightableButtonNode private let cancelButton: HighlightableButtonNode + private var takenSearchPlaceholderContentView: SearchBarPlaceholderContentView? + public var placeholderString: NSAttributedString? { get { return self.textField.placeholderString @@ -941,6 +978,12 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { activityIndicator.removeFromSupernode() } self.iconNode.isHidden = self.activity + if let takenSearchPlaceholderContentView = self.takenSearchPlaceholderContentView { + takenSearchPlaceholderContentView.updateSearchIconVisibility(isVisible: !self.activity) + } + if let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholderContentsView { + inlineSearchPlaceholderContentsView.updateSearchIconVisibility(isVisible: !self.activity) + } } } } @@ -965,17 +1008,24 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { private var validLayout: (CGSize, CGFloat, CGFloat)? - private let fieldStyle: SearchBarStyle + public let fieldStyle: SearchBarStyle private let forceSeparator: Bool private var theme: SearchBarNodeTheme? + private var presentationTheme: PresentationTheme private var strings: PresentationStrings? private let cancelText: String? - public init(theme: SearchBarNodeTheme, strings: PresentationStrings, fieldStyle: SearchBarStyle = .legacy, icon: Icon = .loupe, forceSeparator: Bool = false, displayBackground: Bool = true, cancelText: String? = nil) { + private var isAnimatingOut: Bool = false + + public init(theme: SearchBarNodeTheme, presentationTheme: PresentationTheme, strings: PresentationStrings, fieldStyle: SearchBarStyle = .legacy, icon: Icon = .loupe, forceSeparator: Bool = false, displayBackground: Bool = true, cancelText: String? = nil) { + self.presentationTheme = presentationTheme + self.fieldStyle = fieldStyle self.forceSeparator = forceSeparator self.cancelText = cancelText self.icon = icon + + self.inlineSearchPlaceholder = SearchBarPlaceholderNode(fieldStyle: .glass) self.backgroundNode = NavigationBackgroundNode(color: theme.background) self.backgroundNode.isUserInteractionEnabled = false @@ -994,7 +1044,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true - self.textField = SearchBarTextField(theme: theme) + self.textField = SearchBarTextField(theme: theme, style: fieldStyle) self.textField.accessibilityTraits = .searchField self.textField.autocorrectionType = .no self.textField.returnKeyType = .search @@ -1011,14 +1061,27 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { super.init() - self.addSubnode(self.backgroundNode) - self.addSubnode(self.separatorNode) - - self.addSubnode(self.textBackgroundNode) + switch self.fieldStyle { + case .glass: + break + case .inlineNavigation: + break + case .legacy, .modern: + self.addSubnode(self.backgroundNode) + self.addSubnode(self.separatorNode) + self.addSubnode(self.textBackgroundNode) + } self.view.addSubview(self.textField) - self.addSubnode(self.iconNode) + + switch self.fieldStyle { + case .glass, .inlineNavigation: + break + case .legacy, .modern: + self.addSubnode(self.iconNode) + self.addSubnode(self.cancelButton) + } + self.addSubnode(self.clearButton) - self.addSubnode(self.cancelButton) self.textField.delegate = self self.textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged) @@ -1043,11 +1106,11 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside) - self.updateThemeAndStrings(theme: theme, strings: strings) + self.updateThemeAndStrings(theme: theme, presentationTheme: presentationTheme, strings: strings) self.updateIsEmpty(animated: false) } - public func updateThemeAndStrings(theme: SearchBarNodeTheme, strings: PresentationStrings) { + public func updateThemeAndStrings(theme: SearchBarNodeTheme, presentationTheme: PresentationTheme, strings: PresentationStrings) { if self.theme != theme || self.strings !== strings { self.clearButton.accessibilityLabel = strings.WebSearch_RecentSectionClear self.cancelButton.accessibilityLabel = self.cancelText ?? strings.Common_Cancel @@ -1080,6 +1143,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { } self.theme = theme + self.presentationTheme = presentationTheme self.strings = strings if let (boundingSize, leftInset, rightInset) = self.validLayout { self.updateLayout(boundingSize: boundingSize, leftInset: leftInset, rightInset: rightInset, transition: .immediate) @@ -1093,19 +1157,40 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: .immediate) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.bounds.size.height), size: CGSize(width: self.bounds.size.width, height: UIScreenPixel))) - let verticalOffset: CGFloat = boundingSize.height - 82.0 - let contentFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: boundingSize.width - leftInset - rightInset, height: boundingSize.height)) - let textBackgroundHeight = self.fieldStyle.height + let textBackgroundHeight: CGFloat + if case .inlineNavigation = self.fieldStyle { + textBackgroundHeight = boundingSize.height + } else { + textBackgroundHeight = self.fieldStyle.height + } + let verticalOffset: CGFloat + switch self.fieldStyle { + case .inlineNavigation, .glass: + verticalOffset = -textBackgroundHeight + case .legacy, .modern: + verticalOffset = boundingSize.height - 82.0 + } let cancelButtonSize = self.cancelButton.measure(CGSize(width: 100.0, height: CGFloat.infinity)) transition.updateFrame(node: self.cancelButton, frame: CGRect(origin: CGPoint(x: contentFrame.maxX - 10.0 - cancelButtonSize.width, y: verticalOffset + textBackgroundHeight + floorToScreenPixels((textBackgroundHeight - cancelButtonSize.height) / 2.0)), size: cancelButtonSize)) let padding = self.fieldStyle.padding - let textBackgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX + padding, y: verticalOffset + textBackgroundHeight), size: CGSize(width: contentFrame.width - padding * 2.0 - (self.hasCancelButton ? cancelButtonSize.width + 11.0 : 0.0), height: textBackgroundHeight)) + var textBackgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX + padding, y: verticalOffset + textBackgroundHeight), size: CGSize(width: contentFrame.width - padding - (self.hasCancelButton ? cancelButtonSize.width + 11.0 : 0.0), height: textBackgroundHeight)) + if case .glass = self.fieldStyle { + textBackgroundFrame.size.width -= 8.0 + } else { + textBackgroundFrame.size.width -= padding + } transition.updateFrame(node: self.textBackgroundNode, frame: textBackgroundFrame) - let textFrame = CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 24.0, y: textBackgroundFrame.minY), size: CGSize(width: max(1.0, textBackgroundFrame.size.width - 24.0 - 27.0), height: textBackgroundFrame.size.height)) + var textFrame = CGRect(origin: CGPoint(x: 0.0, y: textBackgroundFrame.minY), size: CGSize(width: max(1.0, textBackgroundFrame.size.width - 24.0 - 27.0), height: textBackgroundFrame.size.height)) + if case .inlineNavigation = self.fieldStyle { + textFrame.size.width = boundingSize.width - 27.0 + textBackgroundFrame.size.width = boundingSize.width + } else { + textFrame.origin.x = textBackgroundFrame.minX + 24.0 + } if let iconImage = self.iconNode.image { let iconSize = iconImage.size @@ -1121,6 +1206,54 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 6.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize)) self.textField.frame = textFrame + + let additionalPlaceholderInset = self.textField.tokensInsetWidth + + let searchPlaceholderFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 0.0), size: CGSize(width: max(0.0, boundingSize.width - 16.0 * 2.0 - leftInset - rightInset), height: 44.0)) + + if case .glass = self.fieldStyle, self.takenSearchPlaceholderContentView == nil { + transition.updateFrame(node: self.inlineSearchPlaceholder, frame: searchPlaceholderFrame) + var isFirstTime = false + if let theme = self.theme { + let _ = self.inlineSearchPlaceholder.updateLayout( + placeholderString: self.placeholderString, + compactPlaceholderString: self.placeholderString, + constrainedSize: searchPlaceholderFrame.size, + expansionProgress: 1.0, + iconColor: theme.inputIcon, + foregroundColor: self.presentationTheme.chat.inputPanel.panelControlColor, + backgroundColor: self.presentationTheme.rootController.navigationBar.opaqueBackgroundColor, + controlColor: self.presentationTheme.chat.inputPanel.panelControlColor, + transition: transition + ) + if self.inlineSearchPlaceholderContentsView == nil { + isFirstTime = true + let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholder.takeContents() + inlineSearchPlaceholderContentsView.onCancel = { [weak self] in + guard let self else { + return + } + self.cancel?() + } + self.inlineSearchPlaceholderContentsView = inlineSearchPlaceholderContentsView + self.view.insertSubview(inlineSearchPlaceholderContentsView, at: 0) + } + } + if let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholderContentsView { + inlineSearchPlaceholderContentsView.update(size: searchPlaceholderFrame.size, isActive: true, additionalPlaceholderInset: additionalPlaceholderInset, transition: transition) + transition.updateFrame(view: inlineSearchPlaceholderContentsView, frame: searchPlaceholderFrame) + + if isFirstTime { + self.updateIsEmpty(animated: false) + inlineSearchPlaceholderContentsView.updateSearchIconVisibility(isVisible: !self.activity) + } + } + } + + if !self.isAnimatingOut, let takenSearchPlaceholderContentView = self.takenSearchPlaceholderContentView { + transition.updateFrame(view: takenSearchPlaceholderContentView, frame: searchPlaceholderFrame) + takenSearchPlaceholderContentView.update(size: searchPlaceholderFrame.size, isActive: true, additionalPlaceholderInset: additionalPlaceholderInset, transition: transition) + } } @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { @@ -1138,7 +1271,33 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { } public func animateIn(from node: SearchBarPlaceholderNode, duration: Double, timingFunction: String) { - let initialTextBackgroundFrame = node.view.convert(node.backgroundNode.frame, to: self.view) + guard let (boundingSize, leftInset, rightInset) = self.validLayout else { + return + } + + self.inlineSearchPlaceholder.isHidden = true + + let takenSearchPlaceholderContentView = node.takeContents() + takenSearchPlaceholderContentView.onCancel = { [weak self] in + guard let self else { + return + } + self.cancel?() + } + self.takenSearchPlaceholderContentView = takenSearchPlaceholderContentView + self.view.insertSubview(takenSearchPlaceholderContentView, at: 0) + if let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholderContentsView { + inlineSearchPlaceholderContentsView.removeFromSuperview() + } + + let sourceFrame = node.view.convert(node.bounds, to: self.view) + let targetFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 0.0), size: CGSize(width: max(0.0, boundingSize.width - 16.0 * 2.0 - leftInset - rightInset), height: 44.0)) + let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: timingFunction == kCAMediaTimingFunctionSpring ? .spring : .easeInOut) + takenSearchPlaceholderContentView.frame = sourceFrame + transition.updateFrame(view: takenSearchPlaceholderContentView, frame: targetFrame) + takenSearchPlaceholderContentView.update(size: targetFrame.size, isActive: true, additionalPlaceholderInset: self.textField.tokensInsetWidth, transition: transition) + + /*let initialTextBackgroundFrame = node.view.convert(node.backgroundView.frame, to: self.view) let initialBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.bounds.size.width, height: max(0.0, initialTextBackgroundFrame.maxY + 8.0))) if let fromBackgroundColor = node.backgroundColor, let toBackgroundColor = self.backgroundNode.backgroundColor { @@ -1152,7 +1311,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) self.separatorNode.layer.animateFrame(from: initialSeparatorFrame, to: self.separatorNode.frame, duration: duration, timingFunction: timingFunction) - if let fromTextBackgroundColor = node.backgroundNode.backgroundColor, let toTextBackgroundColor = self.textBackgroundNode.backgroundColor { + if let fromTextBackgroundColor = node.backgroundView.backgroundColor, let toTextBackgroundColor = self.textBackgroundNode.backgroundColor { self.textBackgroundNode.layer.animate(from: fromTextBackgroundColor.cgColor, to: toTextBackgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: timingFunction, duration: duration * 1.0) } self.textBackgroundNode.layer.animateFrame(from: initialTextBackgroundFrame, to: self.textBackgroundNode.frame, duration: duration, timingFunction: timingFunction) @@ -1177,7 +1336,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { let cancelButtonFrame = self.cancelButton.frame self.cancelButton.layer.animatePosition(from: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: initialTextBackgroundFrame.midY), to: self.cancelButton.layer.position, duration: duration, timingFunction: timingFunction) - node.isHidden = true + node.isHidden = true*/ } public func deactivate(clear: Bool = true) { @@ -1191,7 +1350,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { } public func transitionOut(to node: SearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { - let targetTextBackgroundFrame = node.view.convert(node.backgroundNode.frame, to: self.view) + self.isAnimatingOut = true + + /*let targetTextBackgroundFrame = node.view.convert(node.backgroundView.frame, to: self.view) let duration: Double = transition.isAnimated ? 0.5 : 0.0 let timingFunction = kCAMediaTimingFunctionSpring @@ -1307,8 +1468,8 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { let transitionBackgroundNode = ASDisplayNode() transitionBackgroundNode.isLayerBacked = true transitionBackgroundNode.displaysAsynchronously = false - transitionBackgroundNode.backgroundColor = node.backgroundNode.backgroundColor - transitionBackgroundNode.cornerRadius = node.backgroundNode.cornerRadius + transitionBackgroundNode.backgroundColor = node.backgroundView.backgroundColor + transitionBackgroundNode.cornerRadius = node.backgroundView.layer.cornerRadius self.insertSubnode(transitionBackgroundNode, aboveSubnode: self.textBackgroundNode) transitionBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) @@ -1331,7 +1492,52 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.iconNode.layer.animateFrame(from: self.iconNode.frame, to: targetIconFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) let cancelButtonFrame = self.cancelButton.frame - self.cancelButton.layer.animatePosition(from: self.cancelButton.layer.position, to: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: targetTextBackgroundFrame.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + self.cancelButton.layer.animatePosition(from: self.cancelButton.layer.position, to: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: targetTextBackgroundFrame.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: false)*/ + + if let takenSearchPlaceholderContentView = self.takenSearchPlaceholderContentView { + let transition = ComponentTransition(transition) + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2) + + let sourceFrame = node.view.convert(node.bounds, to: self.view) + takenSearchPlaceholderContentView.update(size: sourceFrame.size, isActive: false, additionalPlaceholderInset: 0.0, transition: transition.containedViewLayoutTransition) + takenSearchPlaceholderContentView.updatePlaceholderVisibility(isVisible: true) + takenSearchPlaceholderContentView.updateSearchIconVisibility(isVisible: true) + + transition.setFrame(view: takenSearchPlaceholderContentView, frame: sourceFrame, completion: { [weak node] _ in + node?.putBackContents() + completion() + }) + + let textBackgroundHeight: CGFloat + if case .inlineNavigation = self.fieldStyle { + textBackgroundHeight = sourceFrame.height + } else { + textBackgroundHeight = self.fieldStyle.height + } + + let padding = self.fieldStyle.padding + var textBackgroundFrame = CGRect(origin: CGPoint(x: sourceFrame.minX + padding, y: sourceFrame.minY), size: CGSize(width: sourceFrame.width - padding, height: textBackgroundHeight)) + if case .glass = self.fieldStyle { + textBackgroundFrame.size.width -= 8.0 + } else { + textBackgroundFrame.size.width -= padding + } + + var textFrame = CGRect(origin: CGPoint(x: 0.0, y: textBackgroundFrame.minY), size: CGSize(width: max(1.0, textBackgroundFrame.size.width - 24.0 - 27.0), height: textBackgroundFrame.size.height)) + if case .inlineNavigation = self.fieldStyle { + textFrame.size.width = sourceFrame.width - 27.0 + textBackgroundFrame.size.width = sourceFrame.width + } else { + textFrame.origin.x = textBackgroundFrame.minX + 24.0 + } + transition.setFrame(view: self.textField, frame: textFrame) + //alphaTransition.setAlpha(view: self.textField, alpha: 0.0) + self.textField.isHidden = true + + let clearSize = self.clearButton.bounds.size + alphaTransition.setAlpha(view: self.clearButton.view, alpha: 0.0) + transition.setFrame(view: self.clearButton.view, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 6.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize)) + } } public func textFieldDidBeginEditing(_ textField: UITextField) { @@ -1403,6 +1609,10 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .spring) : .immediate let placeholderTransition = !isEmpty ? .immediate : transition placeholderTransition.updateAlpha(node: self.textField.placeholderLabel, alpha: isEmpty ? 1.0 : 0.0) + if let takenSearchPlaceholderContentView = self.takenSearchPlaceholderContentView { + takenSearchPlaceholderContentView.updatePlaceholderVisibility(isVisible: isEmpty) + } + self.inlineSearchPlaceholderContentsView?.updatePlaceholderVisibility(isVisible: isEmpty) let clearIsHidden = (textIsEmpty && tokensEmpty) && self.prefixString == nil transition.updateAlpha(node: self.clearButton.imageNode, alpha: clearIsHidden ? 0.0 : 1.0) diff --git a/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift b/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift index 25c468e6..f6f4e3ff 100644 --- a/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift @@ -5,6 +5,8 @@ import AsyncDisplayKit import Display import AppBundle import ComponentFlow +import GlassBackgroundComponent +import ComponentDisplayAdapters private let templateLoupeIcon = UIImage(bundleImageName: "Components/Search Bar/Loupe") @@ -21,44 +23,69 @@ private class SearchBarPlaceholderNodeView: UIView { } } -public class SearchBarPlaceholderNode: ASDisplayNode { - public var activate: (() -> Void)? +public final class SearchBarPlaceholderContentView: UIView { + private struct Params { + var placeholderString: NSAttributedString? + var compactPlaceholderString: NSAttributedString? + var constrainedSize: CGSize + var expansionProgress: CGFloat + var iconColor: UIColor + var foregroundColor: UIColor + var backgroundColor: UIColor + var controlColor: UIColor + var isActive: Bool + var additionalPlaceholderInset: CGFloat + + init(placeholderString: NSAttributedString?, compactPlaceholderString: NSAttributedString?, constrainedSize: CGSize, expansionProgress: CGFloat, iconColor: UIColor, foregroundColor: UIColor, backgroundColor: UIColor, controlColor: UIColor, isActive: Bool, additionalPlaceholderInset: CGFloat) { + self.placeholderString = placeholderString + self.compactPlaceholderString = compactPlaceholderString + self.constrainedSize = constrainedSize + self.expansionProgress = expansionProgress + self.iconColor = iconColor + self.foregroundColor = foregroundColor + self.backgroundColor = backgroundColor + self.controlColor = controlColor + self.isActive = isActive + self.additionalPlaceholderInset = additionalPlaceholderInset + } + } - private let fieldStyle: SearchBarStyle - public let backgroundNode: ASDisplayNode + let fieldStyle: SearchBarStyle + let plainBackgroundView: UIImageView + let glassBackgroundView: GlassBackgroundView? private var fillBackgroundColor: UIColor private var foregroundColor: UIColor private var iconColor: UIColor - public let iconNode: ASImageNode - public let labelNode: TextNode + let iconNode: ASImageNode + let labelNode: TextNode + let plainIconNode: ASImageNode + let plainLabelNode: TextNode - var pointerInteraction: PointerInteraction? + private var close: (background: GlassBackgroundView, icon: UIImageView)? - public private(set) var placeholderString: NSAttributedString? + private(set) var placeholderString: NSAttributedString? - private(set) var accessoryComponentContainer: UIView? - private(set) var accessoryComponentView: ComponentHostView? + private var params: Params? - convenience public override init() { - self.init(fieldStyle: .legacy) - } + public var onCancel: (() -> Void)? - public init(fieldStyle: SearchBarStyle = .legacy) { + init(fieldStyle: SearchBarStyle) { self.fieldStyle = fieldStyle - self.backgroundNode = ASDisplayNode() - self.backgroundNode.isLayerBacked = false - self.backgroundNode.displaysAsynchronously = false - self.fillBackgroundColor = UIColor.white self.foregroundColor = UIColor(rgb: 0xededed) self.iconColor = UIColor(rgb: 0x000000, alpha: 0.0) - self.backgroundNode.backgroundColor = self.foregroundColor - self.backgroundNode.cornerRadius = self.fieldStyle.cornerDiameter / 2.0 + self.plainBackgroundView = UIImageView() + + switch fieldStyle { + case .legacy, .modern: + self.glassBackgroundView = nil + case .inlineNavigation, .glass: + self.glassBackgroundView = GlassBackgroundView() + } self.iconNode = ASImageNode() - self.iconNode.isLayerBacked = true self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true @@ -66,51 +93,431 @@ public class SearchBarPlaceholderNode: ASDisplayNode { self.labelNode.isOpaque = false self.labelNode.isUserInteractionEnabled = false + self.plainIconNode = ASImageNode() + self.plainIconNode.displaysAsynchronously = false + self.plainIconNode.displayWithoutProcessing = true + + self.plainLabelNode = TextNode() + self.plainLabelNode.isOpaque = false + self.plainLabelNode.isUserInteractionEnabled = false + + super.init(frame: CGRect()) + + self.plainBackgroundView.isUserInteractionEnabled = true + self.addSubview(self.plainBackgroundView) + + self.plainBackgroundView.addSubview(self.plainIconNode.view) + self.plainBackgroundView.addSubview(self.plainLabelNode.view) + + if let glassBackgroundView = self.glassBackgroundView { + self.addSubview(glassBackgroundView) + + glassBackgroundView.contentView.addSubview(self.iconNode.view) + glassBackgroundView.contentView.addSubview(self.labelNode.view) + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.onCancel?() + } + } + + func updateLayout( + placeholderString: NSAttributedString?, + compactPlaceholderString: NSAttributedString?, + constrainedSize: CGSize, + expansionProgress: CGFloat, + iconColor: UIColor, + foregroundColor: UIColor, + backgroundColor: UIColor, + controlColor: UIColor, + transition: ContainedViewLayoutTransition + ) -> CGFloat { + let params = Params( + placeholderString: placeholderString, + compactPlaceholderString: compactPlaceholderString, + constrainedSize: constrainedSize, + expansionProgress: expansionProgress, + iconColor: iconColor, + foregroundColor: foregroundColor, + backgroundColor: backgroundColor, + controlColor: controlColor, + isActive: false, + additionalPlaceholderInset: 0.0 + ) + self.params = params + return self.updateLayout(params: params, transition: transition) + } + + public func update(size: CGSize, isActive: Bool, additionalPlaceholderInset: CGFloat, transition: ContainedViewLayoutTransition) { + guard var params = self.params else { + return + } + params.constrainedSize = size + params.expansionProgress = 1.0 + params.isActive = isActive + params.additionalPlaceholderInset = additionalPlaceholderInset + let _ = self.updateLayout(params: params, transition: transition) + } + + private func updateLayout(params: Params, transition: ContainedViewLayoutTransition) -> CGFloat { + let labelLayout = TextNode.asyncLayout(self.labelNode) + let plainLabelLayout = TextNode.asyncLayout(self.plainLabelNode) + let currentForegroundColor = self.foregroundColor + let currentIconColor = self.iconColor + + let placeholderString: NSAttributedString? + if params.constrainedSize.width < 350.0 { + placeholderString = params.compactPlaceholderString + } else { + placeholderString = params.placeholderString + } + + let (labelLayoutResult, labelApply) = labelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: params.constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (_, plainLabelApply) = plainLabelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: params.constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + var updatedColor: UIColor? + var updatedIconImage: UIImage? + if !currentForegroundColor.isEqual(params.foregroundColor) { + updatedColor = params.foregroundColor + } + if !currentIconColor.isEqual(params.iconColor) { + updatedIconImage = generateLoupeIcon(color: params.iconColor) + } + + let height = params.constrainedSize.height * params.expansionProgress + + let _ = labelApply() + let _ = plainLabelApply() + + self.fillBackgroundColor = params.backgroundColor + self.foregroundColor = params.foregroundColor + self.iconColor = params.iconColor + self.plainBackgroundView.isUserInteractionEnabled = params.expansionProgress > 0.9999 + + if let updatedColor { + self.plainBackgroundView.backgroundColor = updatedColor + } + if let updatedIconImage { + self.iconNode.image = updatedIconImage + self.plainIconNode.image = updatedIconImage + } + + self.placeholderString = placeholderString + + var iconSize = CGSize() + var totalWidth = labelLayoutResult.size.width + + var spacing: CGFloat = 4.0 + if params.isActive { + spacing = 2.0 + } + + let iconX: CGFloat + if let iconImage = self.iconNode.image { + iconSize = iconImage.size + totalWidth += iconSize.width + spacing + if params.isActive { + iconX = 8.0 + } else { + iconX = floor((params.constrainedSize.width - totalWidth) / 2.0) + } + transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: iconX, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)) + transition.updateFrame(node: self.plainIconNode, frame: CGRect(origin: CGPoint(x: iconX, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)) + } else { + iconX = 12.0 + } + var textOffset: CGFloat = 0.0 + if params.constrainedSize.height >= 36.0 { + textOffset += 1.0 + } + let labelX: CGFloat = iconX + iconSize.width + spacing + params.additionalPlaceholderInset + let labelFrame = CGRect(origin: CGPoint(x: labelX, y: floorToScreenPixels((height - labelLayoutResult.size.height) / 2.0) + textOffset), size: labelLayoutResult.size) + transition.updateFrame(node: self.labelNode, frame: labelFrame) + transition.updateFrame(node: self.plainLabelNode, frame: labelFrame) + + var innerAlpha = max(0.0, params.expansionProgress - 0.77) / 0.23 + if innerAlpha > 0.9999 { + innerAlpha = 1.0 + } else if innerAlpha < 0.0001 { + innerAlpha = 0.0 + } + if self.labelNode.alpha != innerAlpha { + if !transition.isAnimated { + self.labelNode.layer.removeAnimation(forKey: "opacity") + self.iconNode.layer.removeAnimation(forKey: "opacity") + self.plainLabelNode.layer.removeAnimation(forKey: "opacity") + self.plainIconNode.layer.removeAnimation(forKey: "opacity") + } + + transition.updateAlpha(node: self.labelNode, alpha: innerAlpha) + transition.updateAlpha(node: self.iconNode, alpha: innerAlpha) + + transition.updateAlpha(node: self.plainLabelNode, alpha: innerAlpha) + transition.updateAlpha(node: self.plainIconNode, alpha: innerAlpha) + } + + let outerAlpha = min(0.3, params.expansionProgress) / 0.3 + let cornerRadius = height * 0.5 + + if self.plainBackgroundView.layer.cornerRadius != cornerRadius { + if !transition.isAnimated { + self.plainBackgroundView.layer.removeAnimation(forKey: "cornerRadius") + } + transition.updateCornerRadius(layer: self.plainBackgroundView.layer, cornerRadius: cornerRadius) + } + + var plainBackgroundAlpha = outerAlpha + if params.isActive { + plainBackgroundAlpha = 0.0 + } + if self.plainBackgroundView.alpha != plainBackgroundAlpha { + if !transition.isAnimated { + self.plainBackgroundView.layer.removeAnimation(forKey: "opacity") + } + transition.updateAlpha(layer: self.plainBackgroundView.layer, alpha: plainBackgroundAlpha) + } + + var backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: params.constrainedSize.width, height: height)) + if params.isActive { + backgroundFrame.size.width -= 44.0 + 8.0 + } + + if self.plainBackgroundView.frame != backgroundFrame { + if !transition.isAnimated { + self.plainBackgroundView.layer.removeAnimation(forKey: "position") + self.plainBackgroundView.layer.removeAnimation(forKey: "bounds") + } + transition.updateFrame(view: self.plainBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: params.constrainedSize.width, height: height))) + } + + if let glassBackgroundView = self.glassBackgroundView { + transition.updatePosition(layer: glassBackgroundView.layer, position: backgroundFrame.center) + transition.updateBounds(layer: glassBackgroundView.layer, bounds: CGRect(origin: CGPoint(), size: backgroundFrame.size)) + var backgroundAlpha: CGFloat = 1.0 + if backgroundFrame.height < 16.0 { + backgroundAlpha = max(0.0, min(1.0, backgroundFrame.height / 16.0)) + } + if !params.isActive { + backgroundAlpha = 0.0 + } + ComponentTransition(transition).setAlpha(view: glassBackgroundView, alpha: backgroundAlpha) + let isDark = params.backgroundColor.hsb.b < 0.5 + if params.isActive { + glassBackgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: isDark, tintColor: .init(kind: .panel, color: UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: ComponentTransition(transition)) + } + + if params.isActive { + let transition = ComponentTransition(transition) + + let closeFrame = CGRect(origin: CGPoint(x: params.constrainedSize.width - 44.0, y: 0.0), size: CGSize(width: 44.0, height: 44.0)) + + let close: (background: GlassBackgroundView, icon: UIImageView) + var closeTransition = transition + if let current = self.close { + close = current + } else { + closeTransition = closeTransition.withAnimation(.none) + close = (GlassBackgroundView(), UIImageView()) + self.close = close + + close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setStrokeColor(UIColor.white.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0)) + context.move(to: CGPoint(x: size.width - 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) + + close.background.contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:)))) + + close.background.contentView.addSubview(close.icon) + self.insertSubview(close.background, at: 0) + + if let image = close.icon.image { + close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size)) + } + + close.background.frame = closeFrame.offsetBy(dx: closeFrame.width + 40.0, dy: 0.0) + let isDark = params.backgroundColor.hsb.b < 0.5 + close.background.update(size: close.background.bounds.size, cornerRadius: close.background.bounds.height * 0.5, isDark: isDark, tintColor: .init(kind: .panel, color: UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: .immediate) + ComponentTransition.immediate.setScale(view: close.background, scale: 0.001) + } + + close.icon.tintColor = params.controlColor + + transition.setPosition(view: close.background, position: closeFrame.center) + transition.setBounds(view: close.background, bounds: CGRect(origin: CGPoint(), size: closeFrame.size)) + transition.setScale(view: close.background, scale: 1.0) + + if let image = close.icon.image { + transition.setFrame(view: close.icon, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size))) + } + + let isDark = params.backgroundColor.hsb.b < 0.5 + close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: isDark, tintColor: .init(kind: .panel, color: UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: closeTransition) + } else { + let transition = ComponentTransition(transition) + + if let close = self.close { + self.close = nil + let closeBackground = close.background + let closeFrame = CGRect(origin: CGPoint(x: params.constrainedSize.width - 44.0, y: 0.0), size: CGSize(width: 44.0, height: 44.0)).offsetBy(dx: 44.0 + 40.0, dy: 0.0) + transition.setPosition(view: closeBackground, position: closeFrame.center) + transition.setBounds(view: closeBackground, bounds: CGRect(origin: CGPoint(), size: closeFrame.size)) + let isDark = params.backgroundColor.hsb.b < 0.5 + closeBackground.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: isDark, tintColor: .init(kind: .panel, color: UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + transition.setScale(view: closeBackground, scale: 0.001, completion: { [weak closeBackground] _ in + closeBackground?.removeFromSuperview() + }) + } + } + } + + /*if let accessoryComponentContainer = self.accessoryComponentContainer { + accessoryComponentContainer.frame = CGRect(origin: CGPoint(x: constrainedSize.width - accessoryComponentContainer.bounds.width - 4.0, y: floor((constrainedSize.height * expansionProgress - accessoryComponentContainer.bounds.height) / 2.0)), size: accessoryComponentContainer.bounds.size) + transition.updateAlpha(layer: accessoryComponentContainer.layer, alpha: innerAlpha) + }*/ + + return height + } + + public func updatePlaceholderVisibility(isVisible: Bool) { + self.labelNode.isHidden = !isVisible + self.plainLabelNode.isHidden = !isVisible + } + + public func updateSearchIconVisibility(isVisible: Bool) { + self.iconNode.isHidden = !isVisible + self.plainIconNode.isHidden = !isVisible + } +} + +public class SearchBarPlaceholderNode: ASDisplayNode { + private struct Params { + var placeholderString: NSAttributedString? + var compactPlaceholderString: NSAttributedString? + var constrainedSize: CGSize + var expansionProgress: CGFloat + var iconColor: UIColor + var foregroundColor: UIColor + var backgroundColor: UIColor + var controlColor: UIColor + + init(placeholderString: NSAttributedString?, compactPlaceholderString: NSAttributedString?, constrainedSize: CGSize, expansionProgress: CGFloat, iconColor: UIColor, foregroundColor: UIColor, backgroundColor: UIColor, controlColor: UIColor) { + self.placeholderString = placeholderString + self.compactPlaceholderString = compactPlaceholderString + self.constrainedSize = constrainedSize + self.expansionProgress = expansionProgress + self.iconColor = iconColor + self.foregroundColor = foregroundColor + self.backgroundColor = backgroundColor + self.controlColor = controlColor + } + } + + public var activate: (() -> Void)? + + private let containerView: UIView + private let contentView: SearchBarPlaceholderContentView + + public var backgroundView: UIView { + if let glassBackgroundView = self.contentView.glassBackgroundView { + return glassBackgroundView + } else { + return self.contentView.plainBackgroundView + } + } + + public var iconNode: ASImageNode { + return self.contentView.iconNode + } + + public var labelNode: TextNode { + return self.contentView.labelNode + } + + public var fieldStyle: SearchBarStyle { + return self.contentView.fieldStyle + } + + var pointerInteraction: PointerInteraction? + + public var placeholderString: NSAttributedString? { + return self.contentView.placeholderString + } + + private(set) var accessoryComponentContainer: UIView? + private(set) var accessoryComponentView: ComponentHostView? + + private var params: Params? + private var currentLayoutHeight: CGFloat? + private var isTakenOut: Bool = false + + public init(fieldStyle: SearchBarStyle = .legacy) { + self.containerView = UIView() + self.contentView = SearchBarPlaceholderContentView(fieldStyle: fieldStyle) + super.init() - self.addSubnode(self.backgroundNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.labelNode) - - self.backgroundNode.isUserInteractionEnabled = true + self.view.addSubview(self.containerView) + self.containerView.addSubview(self.contentView) } override public func didLoad() { super.didLoad() let gestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.backgroundTap(_:))) - gestureRecognizer.highlight = { [weak self] point in + /*gestureRecognizer.highlight = { [weak self] point in guard let strongSelf = self else { return } - if let _ = point { - strongSelf.backgroundNode.layer.animate(from: (strongSelf.backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) - strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9) - } else { - strongSelf.backgroundNode.layer.animate(from: (strongSelf.backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.4) - strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor + if let backgroundNode = strongSelf.contentView.backgroundNode { + if let _ = point { + backgroundNode.layer.animate(from: (backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) + backgroundNode.backgroundColor = strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9) + } else { + backgroundNode.layer.animate(from: (backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.4) + backgroundNode.backgroundColor = strongSelf.foregroundColor + } } - } + }*/ gestureRecognizer.tapActionAtPoint = { _ in return .waitForSingleTap } - self.backgroundNode.view.addGestureRecognizer(gestureRecognizer) + self.containerView.addGestureRecognizer(gestureRecognizer) - self.pointerInteraction = PointerInteraction(node: self, style: .caret, willEnter: { [weak self] in + /*self.pointerInteraction = PointerInteraction(node: self, style: .caret, willEnter: { [weak self] in guard let strongSelf = self else { return } - strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor.withMultipliedBrightnessBy(0.95) + if let backgroundNode = strongSelf.contentView.backgroundNode { + backgroundNode.backgroundColor = strongSelf.foregroundColor.withMultipliedBrightnessBy(0.95) + } }, willExit: { [weak self] in guard let strongSelf = self else { return } - strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor - }) + if let backgroundNode = strongSelf.contentView.backgroundNode { + backgroundNode.backgroundColor = strongSelf.foregroundColor + } + })*/ } public func setAccessoryComponent(component: AnyComponent?) { - if let component = component { + /*if let component = component { let accessoryComponentContainer: UIView if let current = self.accessoryComponentContainer { accessoryComponentContainer = current @@ -142,119 +549,43 @@ public class SearchBarPlaceholderNode: ASDisplayNode { accessoryComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak accessoryComponentView] _ in accessoryComponentView?.removeFromSuperview() }) + }*/ + } + + public func takeContents() -> SearchBarPlaceholderContentView { + self.isTakenOut = true + return self.contentView + } + + public func putBackContents() { + self.isTakenOut = false + self.containerView.addSubview(self.contentView) + if let params = self.params { + let _ = self.update(params: params, transition: .immediate) } } - public func asyncLayout() -> (_ placeholderString: NSAttributedString?, _ compactPlaceholderString: NSAttributedString?, _ constrainedSize: CGSize, _ expansionProgress: CGFloat, _ iconColor: UIColor, _ foregroundColor: UIColor, _ backgroundColor: UIColor, _ transition: ContainedViewLayoutTransition) -> (CGFloat, () -> Void) { - let labelLayout = TextNode.asyncLayout(self.labelNode) - let currentForegroundColor = self.foregroundColor - let currentIconColor = self.iconColor + public func updateLayout(placeholderString: NSAttributedString?, compactPlaceholderString: NSAttributedString?, constrainedSize: CGSize, expansionProgress: CGFloat, iconColor: UIColor, foregroundColor: UIColor, backgroundColor: UIColor, controlColor: UIColor, transition: ContainedViewLayoutTransition) -> CGFloat { + let params = Params(placeholderString: placeholderString, compactPlaceholderString: compactPlaceholderString, constrainedSize: constrainedSize, expansionProgress: expansionProgress, iconColor: iconColor, foregroundColor: foregroundColor, backgroundColor: backgroundColor, controlColor: controlColor) + self.params = params - return { fullPlaceholderString, compactPlaceholderString, constrainedSize, expansionProgress, iconColor, foregroundColor, backgroundColor, transition in - let placeholderString: NSAttributedString? - if constrainedSize.width < 350.0 { - placeholderString = compactPlaceholderString - } else { - placeholderString = fullPlaceholderString - } - - let (labelLayoutResult, labelApply) = labelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - var updatedColor: UIColor? - var updatedIconImage: UIImage? - if !currentForegroundColor.isEqual(foregroundColor) { - updatedColor = foregroundColor - } - if !currentIconColor.isEqual(iconColor) { - updatedIconImage = generateLoupeIcon(color: iconColor) - } - - let height = constrainedSize.height * expansionProgress - return (height, { [weak self] in - if let strongSelf = self { - let _ = labelApply() - - strongSelf.fillBackgroundColor = backgroundColor - strongSelf.foregroundColor = foregroundColor - strongSelf.iconColor = iconColor - strongSelf.backgroundNode.isUserInteractionEnabled = expansionProgress > 0.9999 - - if let updatedColor = updatedColor { - strongSelf.backgroundNode.backgroundColor = updatedColor - } - if let updatedIconImage = updatedIconImage { - strongSelf.iconNode.image = updatedIconImage - } - - strongSelf.placeholderString = placeholderString - - var iconSize = CGSize() - var totalWidth = labelLayoutResult.size.width - let spacing: CGFloat = 6.0 - - if let iconImage = strongSelf.iconNode.image { - iconSize = iconImage.size - totalWidth += iconSize.width + spacing - transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: floor((constrainedSize.width - totalWidth) / 2.0), y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)) - } - var textOffset: CGFloat = 0.0 - if constrainedSize.height >= 36.0 { - textOffset += 1.0 - } - let labelFrame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - totalWidth) / 2.0) + iconSize.width + spacing, y: floorToScreenPixels((height - labelLayoutResult.size.height) / 2.0) + textOffset), size: labelLayoutResult.size) - transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame) - - var innerAlpha = max(0.0, expansionProgress - 0.77) / 0.23 - if innerAlpha > 0.9999 { - innerAlpha = 1.0 - } else if innerAlpha < 0.0001 { - innerAlpha = 0.0 - } - if strongSelf.labelNode.alpha != innerAlpha { - if !transition.isAnimated { - strongSelf.labelNode.layer.removeAnimation(forKey: "opacity") - strongSelf.iconNode.layer.removeAnimation(forKey: "opacity") - } - - transition.updateAlpha(node: strongSelf.labelNode, alpha: innerAlpha) - transition.updateAlpha(node: strongSelf.iconNode, alpha: innerAlpha) - } - - let outerAlpha = min(0.3, expansionProgress) / 0.3 - let cornerRadius = min(strongSelf.fieldStyle.cornerDiameter / 2.0, height / 2.0) - - if strongSelf.backgroundNode.cornerRadius != cornerRadius { - if !transition.isAnimated { - strongSelf.backgroundNode.layer.removeAnimation(forKey: "cornerRadius") - } - transition.updateCornerRadius(node: strongSelf.backgroundNode, cornerRadius: cornerRadius) - } - - if strongSelf.backgroundNode.alpha != outerAlpha { - if !transition.isAnimated { - strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") - } - transition.updateAlpha(node: strongSelf.backgroundNode, alpha: outerAlpha) - } - - if strongSelf.backgroundNode.frame != CGRect(origin: CGPoint(), size: CGSize(width: constrainedSize.width, height: height)) { - if !transition.isAnimated { - strongSelf.backgroundNode.layer.removeAnimation(forKey: "position") - strongSelf.backgroundNode.layer.removeAnimation(forKey: "bounds") - } - transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: constrainedSize.width, height: height))) - } - - if let accessoryComponentContainer = strongSelf.accessoryComponentContainer { - accessoryComponentContainer.frame = CGRect(origin: CGPoint(x: constrainedSize.width - accessoryComponentContainer.bounds.width - 4.0, y: floor((constrainedSize.height * expansionProgress - accessoryComponentContainer.bounds.height) / 2.0)), size: accessoryComponentContainer.bounds.size) - transition.updateAlpha(layer: accessoryComponentContainer.layer, alpha: innerAlpha) - - } - } - }) + if self.isTakenOut { + return self.currentLayoutHeight ?? 44.0 + } else { + let height = self.update(params: params, transition: transition) + self.currentLayoutHeight = height + return height } } + private func update(params: Params, transition: ContainedViewLayoutTransition) -> CGFloat { + let height = self.contentView.updateLayout(placeholderString: params.placeholderString, compactPlaceholderString: params.compactPlaceholderString, constrainedSize: params.constrainedSize, expansionProgress: params.expansionProgress, iconColor: params.iconColor, foregroundColor: params.foregroundColor, backgroundColor: params.backgroundColor, controlColor: params.controlColor, transition: transition) + let size = CGSize(width: params.constrainedSize.width, height: height) + transition.updateFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size)) + return height + } + @objc private func backgroundTap(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { if case .ended = recognizer.state { self.activate?() diff --git a/submodules/SearchUI/BUILD b/submodules/SearchUI/BUILD index 7210ffaa..84660a90 100644 --- a/submodules/SearchUI/BUILD +++ b/submodules/SearchUI/BUILD @@ -10,12 +10,16 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/SearchBarNode:SearchBarNode", - "//submodules/ChatListSearchItemNode:ChatListSearchItemNode", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/SearchBarNode", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/AppBundle", + "//submodules/ActivityIndicator", ], visibility = [ "//visibility:public", diff --git a/submodules/SearchUI/Sources/FixSearchableListNodeScrolling.swift b/submodules/SearchUI/Sources/FixSearchableListNodeScrolling.swift index 9d202507..3704caa3 100644 --- a/submodules/SearchUI/Sources/FixSearchableListNodeScrolling.swift +++ b/submodules/SearchUI/Sources/FixSearchableListNodeScrolling.swift @@ -1,33 +1,6 @@ import Foundation import AsyncDisplayKit import Display -import ChatListSearchItemNode - -public func fixSearchableListNodeScrolling(_ listNode: ListView) { - var searchItemNode: ListViewItemNode? - var nextItemNode: ListViewItemNode? - - listNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatListSearchItemNode { - searchItemNode = itemNode - } else if searchItemNode != nil && nextItemNode == nil { - nextItemNode = itemNode as? ListViewItemNode - } - } - - if let searchItemNode = searchItemNode { - let itemFrame = searchItemNode.apparentFrame - if itemFrame.contains(CGPoint(x: 0.0, y: listNode.insets.top)) { - if itemFrame.minY + itemFrame.height * 0.6 < listNode.insets.top { - if let nextItemNode = nextItemNode { - listNode.ensureItemNodeVisibleAtTopInset(nextItemNode) - } - } else { - listNode.ensureItemNodeVisibleAtTopInset(searchItemNode) - } - } - } -} public func fixNavigationSearchableListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool { if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 { @@ -47,22 +20,3 @@ public func fixNavigationSearchableListNodeScrolling(_ listNode: ListView, searc } return false } - -func fixNavigationSearchableGridNodeScrolling(_ gridNode: GridNode, searchNode: NavigationBarSearchContentNode) -> Bool { - if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 { - let scrollToItem: GridNodeScrollToItem - let targetProgress: CGFloat - if searchNode.expansionProgress < 0.6 { - scrollToItem = GridNodeScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), transition: .animated(duration: 0.3, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true) - targetProgress = 0.0 - } else { - scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .animated(duration: 0.3, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true) - targetProgress = 1.0 - } - searchNode.updateExpansionProgress(targetProgress, animated: true) - - gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { _ in }) - return true - } - return false -} diff --git a/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift b/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift index 49978807..e63cadc7 100644 --- a/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift +++ b/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift @@ -4,11 +4,28 @@ import AsyncDisplayKit import Display import TelegramPresentationData import SearchBarNode +import GlassBackgroundComponent +import ComponentFlow +import ComponentDisplayAdapters +import AppBundle +import ActivityIndicator private let searchBarFont = Font.regular(17.0) -public let navigationBarSearchContentHeight: CGFloat = 54.0 +public let navigationBarSearchContentHeight: CGFloat = 60.0 public class NavigationBarSearchContentNode: NavigationBarContentNode { + private struct Params: Equatable { + let size: CGSize + let leftInset: CGFloat + let rightInset: CGFloat + + init(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + self.size = size + self.leftInset = leftInset + self.rightInset = rightInset + } + } + public var theme: PresentationTheme? public var placeholder: String public var compactPlaceholder: String @@ -19,8 +36,6 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { private var disabledOverlay: ASDisplayNode? public var expansionProgress: CGFloat = 1.0 - - public var additionalHeight: CGFloat = 0.0 private var validLayout: (CGSize, CGFloat, CGFloat)? @@ -30,7 +45,7 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { self.compactPlaceholder = compactPlaceholder ?? placeholder self.inline = inline - self.placeholderNode = SearchBarPlaceholderNode(fieldStyle: .modern) + self.placeholderNode = SearchBarPlaceholderNode(fieldStyle: .glass) self.placeholderNode.labelNode.displaysAsynchronously = false super.init() @@ -41,6 +56,8 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { self.addSubnode(self.placeholderNode) self.placeholderNode.activate = activate + + //self.backgroundColor = .red } public func updateThemeAndPlaceholder(theme: PresentationTheme, placeholder: String, compactPlaceholder: String? = nil) { @@ -102,10 +119,10 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { } private func updatePlaceholder(_ progress: CGFloat, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { - let padding: CGFloat = 10.0 + let padding: CGFloat = 16.0 let baseWidth = size.width - padding * 2.0 - leftInset - rightInset - let fieldHeight: CGFloat = 36.0 + let fieldHeight: CGFloat = 44.0 let fraction = fieldHeight / self.nominalHeight let fullFraction = navigationBarSearchContentHeight / self.nominalHeight @@ -116,21 +133,19 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { var visibleProgress: CGFloat = toLow + (self.expansionProgress - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) visibleProgress = max(0.0, min(1.0, visibleProgress)) - let searchBarNodeLayout = self.placeholderNode.asyncLayout() - let textColor = self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93) var fillColor = self.theme?.rootController.navigationSearchBar.inputFillColor ?? .clear if self.inline, let theme = self.theme, fillColor.distance(to: theme.list.blocksBackgroundColor) < 100 { fillColor = fillColor.withMultipliedBrightnessBy(0.8) } - let backgroundColor = self.theme?.rootController.navigationBar.opaqueBackgroundColor ?? .clear + let backgroundColor = self.theme?.chatList.regularSearchBarColor ?? .clear + let controlColor = self.theme?.chat.inputPanel.panelControlColor ?? .black let placeholderString = NSAttributedString(string: self.placeholder, font: searchBarFont, textColor: textColor) let compactPlaceholderString = NSAttributedString(string: self.compactPlaceholder, font: searchBarFont, textColor: textColor) - let (searchBarHeight, searchBarApply) = searchBarNodeLayout(placeholderString, compactPlaceholderString, CGSize(width: baseWidth, height: fieldHeight), visibleProgress, textColor, fillColor, backgroundColor, transition) - searchBarApply() + let searchBarHeight = self.placeholderNode.updateLayout(placeholderString: placeholderString, compactPlaceholderString: compactPlaceholderString, constrainedSize: CGSize(width: baseWidth, height: fieldHeight), expansionProgress: visibleProgress, iconColor: textColor, foregroundColor: fillColor, backgroundColor: backgroundColor, controlColor: controlColor, transition: transition) let searchBarFrame = CGRect(origin: CGPoint(x: padding + leftInset, y: size.height + (1.0 - visibleProgress) * fieldHeight - 8.0 - fieldHeight), size: CGSize(width: baseWidth, height: fieldHeight)) transition.updateFrame(node: self.placeholderNode, frame: searchBarFrame) @@ -143,10 +158,12 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { } } - override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { self.validLayout = (size, leftInset, rightInset) self.updatePlaceholder(self.expansionProgress, size: size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } override public var height: CGFloat { @@ -158,7 +175,7 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { } override public var nominalHeight: CGFloat { - return navigationBarSearchContentHeight + self.additionalHeight + return 60.0 } override public var mode: NavigationBarContentMode { diff --git a/submodules/SearchUI/Sources/SearchDisplayController.swift b/submodules/SearchUI/Sources/SearchDisplayController.swift index 50420e6b..53cdd002 100644 --- a/submodules/SearchUI/Sources/SearchDisplayController.swift +++ b/submodules/SearchUI/Sources/SearchDisplayController.swift @@ -25,12 +25,14 @@ public final class SearchDisplayController { } } - private let searchBar: SearchBarNode + private var searchBar: SearchBarNode? + private let searchBarIsExternal: Bool private let mode: SearchDisplayControllerMode private let backgroundNode: BackgroundNode public let contentNode: SearchDisplayControllerContentNode private var hasSeparator: Bool private let inline: Bool + private let cancel: () -> Void private var containerLayout: (ContainerViewLayout, CGFloat)? @@ -38,71 +40,75 @@ public final class SearchDisplayController { private var isSearchingDisposable: Disposable? - public init(presentationData: PresentationData, mode: SearchDisplayControllerMode = .navigation, placeholder: String? = nil, hasBackground: Bool = false, hasSeparator: Bool = false, contentNode: SearchDisplayControllerContentNode, inline: Bool = false, cancel: @escaping () -> Void) { + public init( + presentationData: PresentationData, + mode: SearchDisplayControllerMode = .navigation, + placeholder: String? = nil, + hasBackground: Bool = false, + hasSeparator: Bool = false, + contentNode: SearchDisplayControllerContentNode, + inline: Bool = false, + cancel: @escaping () -> Void, + fieldStyle: SearchBarStyle = .modern, + searchBarIsExternal: Bool = false + ) { self.inline = inline - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasBackground: hasBackground, hasSeparator: hasSeparator, inline: inline), strings: presentationData.strings, fieldStyle: .modern, forceSeparator: hasSeparator, displayBackground: hasBackground) + self.cancel = cancel + self.searchBarIsExternal = searchBarIsExternal + + if !searchBarIsExternal { + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasBackground: hasBackground, hasSeparator: hasSeparator, inline: inline), presentationTheme: presentationData.theme, strings: presentationData.strings, fieldStyle: fieldStyle, forceSeparator: hasSeparator, displayBackground: hasBackground) + } self.backgroundNode = BackgroundNode() self.backgroundNode.allowsGroupOpacity = true self.mode = mode self.contentNode = contentNode self.hasSeparator = hasSeparator + + if let searchBar = self.searchBar { + self.setSearchBar(searchBar) + } - self.searchBar.textUpdated = { [weak contentNode] text, _ in - contentNode?.searchTextUpdated(text: text) - } - self.searchBar.tokensUpdated = { [weak contentNode] tokens in - contentNode?.searchTokensUpdated(tokens: tokens) - } - self.searchBar.cancel = { [weak self] in - self?.isDeactivating = true - cancel() - } - self.searchBar.clearPrefix = { [weak contentNode] in - contentNode?.searchTextClearPrefix() - } - self.searchBar.clearTokens = { [weak contentNode] in - contentNode?.searchTextClearTokens() - } self.contentNode.cancel = { [weak self] in self?.isDeactivating = true cancel() } self.contentNode.dismissInput = { [weak self] in - self?.searchBar.deactivate(clear: false) + self?.searchBar?.deactivate(clear: false) } var isFirstTime = true self.contentNode.setQuery = { [weak self] prefix, tokens, query in - if let strongSelf = self { - strongSelf.searchBar.prefixString = prefix - let previousTokens = strongSelf.searchBar.tokens - strongSelf.searchBar.tokens = tokens - strongSelf.searchBar.text = query + if let strongSelf = self, let searchBar = strongSelf.searchBar { + searchBar.prefixString = prefix + let previousTokens = searchBar.tokens + searchBar.tokens = tokens + searchBar.text = query if previousTokens.count < tokens.count && !isFirstTime { if let lastToken = tokens.last, !lastToken.permanent { - strongSelf.searchBar.selectLastToken() + searchBar.selectLastToken() } } isFirstTime = false } } if let placeholder = placeholder { - self.searchBar.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: presentationData.theme.rootController.navigationSearchBar.inputPlaceholderTextColor) + self.searchBar?.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: presentationData.theme.rootController.navigationSearchBar.inputPlaceholderTextColor) } self.contentNode.setPlaceholder = { [weak self] string in - guard string != self?.searchBar.placeholderString?.string else { + guard string != self?.searchBar?.placeholderString?.string else { return } - if let mutableAttributedString = self?.searchBar.placeholderString?.mutableCopy() as? NSMutableAttributedString { + if let mutableAttributedString = self?.searchBar?.placeholderString?.mutableCopy() as? NSMutableAttributedString { mutableAttributedString.mutableString.setString(string) - self?.searchBar.placeholderString = mutableAttributedString + self?.searchBar?.placeholderString = mutableAttributedString } } self.isSearchingDisposable = (contentNode.isSearching |> deliverOnMainQueue).start(next: { [weak self] value in - self?.searchBar.activity = value + self?.searchBar?.activity = value }) if self.contentNode.hasDim { @@ -113,9 +119,32 @@ public final class SearchDisplayController { self.backgroundNode.isTransparent = false } } + + public func setSearchBar(_ searchBar: SearchBarNode) { + self.searchBar = searchBar + + searchBar.textUpdated = { [weak contentNode] text, _ in + contentNode?.searchTextUpdated(text: text) + } + searchBar.tokensUpdated = { [weak contentNode] tokens in + contentNode?.searchTokensUpdated(tokens: tokens) + } + searchBar.cancel = { [weak self] in + self?.isDeactivating = true + self?.cancel() + } + searchBar.clearPrefix = { [weak contentNode] in + contentNode?.searchTextClearPrefix() + } + searchBar.clearTokens = { [weak contentNode] in + contentNode?.searchTextClearTokens() + } + } public func updatePresentationData(_ presentationData: PresentationData) { - self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: self.hasSeparator, inline: self.inline), strings: presentationData.strings) + if !self.searchBarIsExternal { + self.searchBar?.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: self.hasSeparator, inline: self.inline), presentationTheme: presentationData.theme, strings: presentationData.strings) + } self.contentNode.updatePresentationData(presentationData) if self.contentNode.hasDim { @@ -128,6 +157,8 @@ public final class SearchDisplayController { } public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + let defaultNavigationBarHeight = navigationBarHeight + let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 let searchBarHeight: CGFloat = max(20.0, statusBarHeight) + 44.0 let navigationBarOffset: CGFloat @@ -137,32 +168,44 @@ public final class SearchDisplayController { navigationBarOffset = 0.0 } var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: searchBarHeight)) + if self.searchBarIsExternal { + navigationBarFrame.size.height -= 50.0 + } if layout.statusBarHeight == nil { navigationBarFrame.size.height = 64.0 } navigationBarFrame.size.height += 10.0 + var navigationBarHeight = navigationBarFrame.maxY - let searchBarFrame: CGRect - if case .navigation = self.mode { - searchBarFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 54.0) - } else { - searchBarFrame = navigationBarFrame + if !self.searchBarIsExternal, let searchBar = self.searchBar { + let searchBarFrame: CGRect + if case .navigation = self.mode { + if case .glass = searchBar.fieldStyle { + navigationBarHeight = defaultNavigationBarHeight + searchBarFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 44.0) + } else { + searchBarFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 54.0) + } + } else { + searchBarFrame = navigationBarFrame + navigationBarHeight = navigationBarFrame.maxY + 8.0 + } + transition.updateFrame(node: searchBar, frame: searchBarFrame) + searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) } - transition.updateFrame(node: self.searchBar, frame: searchBarFrame) - self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) - self.containerLayout = (layout, navigationBarFrame.maxY) + self.containerLayout = (layout, navigationBarHeight) let bounds = CGRect(origin: CGPoint(), size: layout.size) - transition.updateFrame(node: self.backgroundNode, frame: bounds.insetBy(dx: -20.0, dy: -20.0)) + transition.updateFrame(node: self.backgroundNode, frame: bounds)//.insetBy(dx: -20.0, dy: -20.0)) - var size = layout.size - size.width += 20.0 * 2.0 - transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 20.0), size: size)) + let size = layout.size + //size.width += 20.0 * 2.0 + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) - var safeInsets = layout.safeInsets - safeInsets.left += 20.0 - safeInsets.right += 20.0 + let safeInsets = layout.safeInsets + //safeInsets.left += 20.0 + //safeInsets.right += 20.0 self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: safeInsets, additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition) } @@ -183,14 +226,14 @@ public final class SearchDisplayController { self.backgroundNode.isTransparent = false } - var size = layout.size - size.width += 20.0 * 2.0 + let size = layout.size + //size.width += 20.0 * 2.0 - self.contentNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 20.0), size: size) + self.contentNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size) - var safeInsets = layout.safeInsets - safeInsets.left += 20.0 - safeInsets.right += 20.0 + let safeInsets = layout.safeInsets + //safeInsets.left += 20.0 + //safeInsets.right += 20.0 self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: safeInsets, additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), navigationBarHeight: navigationBarHeight, transition: .immediate) var contentNavigationBarHeight = navigationBarHeight @@ -201,22 +244,22 @@ public final class SearchDisplayController { if !self.contentNode.hasDim { self.backgroundNode.alpha = 1.0 self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue) - - self.backgroundNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) } - if !self.contentNode.hasDim { - if let placeholder = placeholder { - self.searchBar.placeholderString = placeholder.placeholderString - } - } else { - if let placeholder = placeholder { - let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil) - let contentNodePosition = self.backgroundNode.layer.position - if contentNode.animateBackgroundAppearance { - self.backgroundNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + if !self.searchBarIsExternal { + if !self.contentNode.hasDim { + if let placeholder = placeholder { + self.searchBar?.placeholderString = placeholder.placeholderString + } + } else { + if let placeholder = placeholder { + let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundView.frame, to: nil) + let contentNodePosition = self.backgroundNode.layer.position + if contentNode.animateBackgroundAppearance { + self.backgroundNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + } + self.searchBar?.placeholderString = placeholder.placeholderString } - self.searchBar.placeholderString = placeholder.placeholderString } } @@ -240,39 +283,49 @@ public final class SearchDisplayController { navigationBarFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 54.0) } - self.searchBar.frame = navigationBarFrame - insertSubnode(self.searchBar, true) - self.searchBar.layout() - - if focus { - self.searchBar.activate() + if !self.searchBarIsExternal, let searchBar = self.searchBar { + searchBar.frame = navigationBarFrame + insertSubnode(searchBar, true) + searchBar.layout() + + if focus { + searchBar.activate() + } } + if let placeholder = placeholder { - self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + if !self.searchBarIsExternal, let searchBar = self.searchBar { + searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + } if self.contentNode.hasDim { self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) } } else { - self.searchBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + if !self.searchBarIsExternal, let searchBar = self.searchBar { + searchBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + } self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) } } - public func deactivate(placeholder: SearchBarPlaceholderNode?, animated: Bool = true) { - self.searchBar.deactivate(clear: false) + public func deactivate(placeholder: SearchBarPlaceholderNode?, animated: Bool = true, completion: (() -> Void)? = nil) { + if let searchBar = self.searchBar { + searchBar.deactivate(clear: false) + } - let searchBar = self.searchBar - if let placeholder = placeholder { - searchBar.transitionOut(to: placeholder, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate, completion: { - [weak searchBar] in - searchBar?.removeFromSupernode() - }) - } else { - searchBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak searchBar] finished in - if finished { + if !self.searchBarIsExternal, let searchBar = self.searchBar { + if let placeholder = placeholder { + searchBar.transitionOut(to: placeholder, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate, completion: { + [weak searchBar] in searchBar?.removeFromSupernode() - } - }) + }) + } else { + searchBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak searchBar] finished in + if finished { + searchBar?.removeFromSupernode() + } + }) + } } let backgroundNode = self.backgroundNode @@ -282,10 +335,12 @@ public final class SearchDisplayController { if finished { backgroundNode?.removeFromSupernode() } + completion?() }) } else { backgroundNode.removeFromSupernode() contentNode.removeFromSupernode() + completion?() } } diff --git a/submodules/SectionHeaderItem/Sources/SectionHeaderItem.swift b/submodules/SectionHeaderItem/Sources/SectionHeaderItem.swift index 37a1f4f9..2a5f15e6 100644 --- a/submodules/SectionHeaderItem/Sources/SectionHeaderItem.swift +++ b/submodules/SectionHeaderItem/Sources/SectionHeaderItem.swift @@ -73,7 +73,7 @@ private class SectionHeaderItemNode: ListViewItemNode { private var layoutParams: ListViewItemLayoutParams? required init() { - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) } override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index 353a1ec8..06cc3d6d 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -23,7 +23,6 @@ swift_library( "//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/AvatarNode:AvatarNode", "//submodules/CallListUI:CallListUI", - "//submodules/ChatListSearchItemNode:ChatListSearchItemNode", "//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader", "//submodules/ChatListUI:ChatListUI", "//submodules/ContactsPeerItem:ContactsPeerItem", @@ -129,9 +128,13 @@ swift_library( "//submodules/TelegramUI/Components/Settings/PasskeysScreen", "//submodules/TelegramUI/Components/FaceScanScreen", "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", "//submodules/Components/BundleIconComponent", "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/SliderComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift index 65965353..dfbb3230 100644 --- a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift +++ b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift @@ -320,7 +320,9 @@ final class BubbleSettingsController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationThemeSettings = presentationThemeSettings - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings, style: .glass)) + + self._hasGlassStyle = true self.blocksBackgroundWhenInOverlay = true self.acceptsFocusWhenInOverlay = true diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift index bda486bb..9a03e479 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift @@ -48,7 +48,7 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll controller?.inProgress = false var dismissImpl: (() -> Void)? - let codeController = AuthorizationSequenceCodeEntryController(presentationData: presentationData, back: { + let codeController = AuthorizationSequenceCodeEntryController(sharedContext: context.sharedContext, presentationData: presentationData, back: { dismissImpl?() }) codeController.loginWithCode = { [weak codeController] code in @@ -109,7 +109,7 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll let mnc = carrier.mobileNetworkCode ?? "none" let _ = context.engine.auth.reportMissingCode(phoneNumber: phoneNumber, phoneCodeHash: next.hash, mnc: mnc).start() - AuthorizationSequenceController.presentDidNotGetCodeUI(controller: codeController, presentationData: context.sharedContext.currentPresentationData.with({ $0 }), phoneNumber: phoneNumber, mnc: mnc) + AuthorizationSequenceController.presentDidNotGetCodeUI(sharedContext: context.sharedContext, controller: codeController, presentationData: context.sharedContext.currentPresentationData.with({ $0 }), phoneNumber: phoneNumber, mnc: mnc) } codeController.openFragment = { url in context.sharedContext.applicationBindings.openUrl(url) @@ -154,7 +154,15 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll controller?.view.window?.rootViewController?.present(composeController, animated: true, completion: nil) } else { - controller?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_EmailNotConfiguredError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + let alertController = textAlertController( + context: context, + title: nil, + text: presentationData.strings.Login_EmailNotConfiguredError, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) + ] + ) + controller?.present(alertController, in: .window(.root)) } })) case .generic: diff --git a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift index 735565cf..acb2d8f7 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift @@ -133,7 +133,7 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.lowTextNode) self.addSubnode(self.mediumTextNode) diff --git a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadSizeLimitItem.swift b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadSizeLimitItem.swift index affc44c9..280d7365 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadSizeLimitItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadSizeLimitItem.swift @@ -143,7 +143,7 @@ private final class AutodownloadSizeLimitItemNode: ListViewItemNode { self.maxTextNode.isUserInteractionEnabled = false self.maxTextNode.displaysAsynchronously = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.textNode) self.addSubnode(self.minTextNode) diff --git a/submodules/SettingsUI/Sources/Data and Storage/CalculatingCacheSizeItem.swift b/submodules/SettingsUI/Sources/Data and Storage/CalculatingCacheSizeItem.swift index 2d6531d3..2f86442b 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/CalculatingCacheSizeItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/CalculatingCacheSizeItem.swift @@ -90,7 +90,7 @@ private final class CalculatingCacheSizeItemNode: ListViewItemNode { self.titleNode.contentMode = .left self.titleNode.contentsScale = UIScreen.main.scale - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) } diff --git a/submodules/SettingsUI/Sources/Data and Storage/EnergyUsageBatteryLevelItem.swift b/submodules/SettingsUI/Sources/Data and Storage/EnergyUsageBatteryLevelItem.swift index 84ce6fb2..831492c2 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/EnergyUsageBatteryLevelItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/EnergyUsageBatteryLevelItem.swift @@ -112,7 +112,7 @@ class EnergyUsageBatteryLevelItemNode: ListViewItemNode { self.batteryBackgroundNode = ASImageNode() self.batteryForegroundNode = ASImageNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.leftTextNode) self.addSubnode(self.rightTextNode) diff --git a/submodules/SettingsUI/Sources/Data and Storage/KeepMediaDurationPickerItem.swift b/submodules/SettingsUI/Sources/Data and Storage/KeepMediaDurationPickerItem.swift index 69cf7884..a62039c1 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/KeepMediaDurationPickerItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/KeepMediaDurationPickerItem.swift @@ -107,7 +107,7 @@ private final class KeepMediaDurationPickerItemNode: ListViewItemNode { } self.textNodes = textNodes - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) for textNode in textNodes { self.addSubnode(textNode) diff --git a/submodules/SettingsUI/Sources/Data and Storage/MaximumCacheSizePickerItem.swift b/submodules/SettingsUI/Sources/Data and Storage/MaximumCacheSizePickerItem.swift index 6d6d7257..25c1db6d 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/MaximumCacheSizePickerItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/MaximumCacheSizePickerItem.swift @@ -122,7 +122,7 @@ private final class MaximumCacheSizePickerItemNode: ListViewItemNode { } self.textNodes = textNodes - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) for textNode in textNodes { self.addSubnode(textNode) diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerActionSheetController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerActionSheetController.swift index f4c1aee4..bcc32f58 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerActionSheetController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerActionSheetController.swift @@ -14,6 +14,7 @@ import PresentationDataUtils import UrlEscaping public final class ProxyServerActionSheetController: ActionSheetController { + private let sharedContext: SharedAccountContext private var presentationDisposable: Disposable? private let _ready = Promise() @@ -25,10 +26,11 @@ public final class ProxyServerActionSheetController: ActionSheetController { convenience public init(context: AccountContext, server: ProxyServerSettings) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.init(presentationData: presentationData, accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, server: server, updatedPresentationData: context.sharedContext.presentationData) + self.init(sharedContext: context.sharedContext, presentationData: presentationData, accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, server: server, updatedPresentationData: context.sharedContext.presentationData) } - public init(presentationData: PresentationData, accountManager: AccountManager, postbox: Postbox, network: Network, server: ProxyServerSettings, updatedPresentationData: Signal?) { + public init(sharedContext: SharedAccountContext, presentationData: PresentationData, accountManager: AccountManager, postbox: Postbox, network: Network, server: ProxyServerSettings, updatedPresentationData: Signal?) { + self.sharedContext = sharedContext let sheetTheme = ActionSheetControllerTheme(presentationData: presentationData) super.init(theme: sheetTheme) @@ -39,7 +41,7 @@ public final class ProxyServerActionSheetController: ActionSheetController { items.append(ActionSheetTextItem(title: presentationData.strings.SocksProxySetup_AdNoticeHelp)) } items.append(ProxyServerInfoItem(strings: presentationData.strings, network: network, server: server)) - items.append(ProxyServerActionItem(accountManager:accountManager, postbox: postbox, network: network, presentationData: presentationData, server: server, dismiss: { [weak self] success in + items.append(ProxyServerActionItem(sharedContext: sharedContext, accountManager:accountManager, postbox: postbox, network: network, presentationData: presentationData, server: server, dismiss: { [weak self] success in guard let strongSelf = self, !strongSelf.isDismissed else { return } @@ -262,6 +264,7 @@ private final class ProxyServerInfoItemNode: ActionSheetItemNode { } private final class ProxyServerActionItem: ActionSheetItem { + private let sharedContext: SharedAccountContext private let accountManager: AccountManager private let postbox: Postbox private let network: Network @@ -270,7 +273,8 @@ private final class ProxyServerActionItem: ActionSheetItem { private let dismiss: (Bool) -> Void private let present: (ViewController, Any?) -> Void - init(accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(sharedContext: SharedAccountContext, accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { + self.sharedContext = sharedContext self.accountManager = accountManager self.postbox = postbox self.network = network @@ -281,7 +285,7 @@ private final class ProxyServerActionItem: ActionSheetItem { } func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return ProxyServerActionItemNode(accountManager: self.accountManager, postbox: self.postbox, network: self.network, presentationData: self.presentationData, theme: theme, server: self.server, dismiss: self.dismiss, present: self.present) + return ProxyServerActionItemNode(sharedContext: self.sharedContext, accountManager: self.accountManager, postbox: self.postbox, network: self.network, presentationData: self.presentationData, theme: theme, server: self.server, dismiss: self.dismiss, present: self.present) } func updateNode(_ node: ActionSheetItemNode) { @@ -289,6 +293,7 @@ private final class ProxyServerActionItem: ActionSheetItem { } private final class ProxyServerActionItemNode: ActionSheetItemNode { + private let sharedContext: SharedAccountContext private let accountManager: AccountManager private let postbox: Postbox private let network: Network @@ -305,7 +310,8 @@ private final class ProxyServerActionItemNode: ActionSheetItemNode { private let disposable = MetaDisposable() private var revertSettings: ProxySettings? - init(accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, theme: ActionSheetControllerTheme, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(sharedContext: SharedAccountContext, accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, theme: ActionSheetControllerTheme, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { + self.sharedContext = sharedContext self.accountManager = accountManager self.postbox = postbox self.network = network @@ -430,7 +436,7 @@ private final class ProxyServerActionItemNode: ActionSheetItemNode { strongSelf.buttonNode.isUserInteractionEnabled = true strongSelf.requestLayoutUpdate() - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.SocksProxySetup_FailedToConnect, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) + strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.SocksProxySetup_FailedToConnect, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) } } })) diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsActionItem.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsActionItem.swift index 6b3e7849..e1f086ab 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsActionItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsActionItem.swift @@ -114,7 +114,7 @@ private final class ProxySettingsActionItemNode: ListViewItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.isAccessibilityElement = true diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift index da7512dd..3108d649 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift @@ -174,7 +174,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.titleNode) self.addSubnode(self.statusNode) diff --git a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageItem.swift b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageItem.swift index e50a2c31..7a085e60 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageItem.swift @@ -119,7 +119,7 @@ private final class StorageUsageItemNode: ListViewItemNode { self.lineNodes = [] self.descriptionNodes = [] - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.lineMaskNode) } diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainController.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainController.swift index 4d62514a..d75618da 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainController.swift @@ -7,465 +7,99 @@ import TelegramCore import TelegramPresentationData import AccountContext import UrlEscaping -import ActivityIndicator +import ComponentFlow +import AlertComponent +import AlertInputFieldComponent -private final class WebBrowserDomainInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { - private var theme: PresentationTheme - private let backgroundNode: ASImageNode - fileprivate let textInputNode: EditableTextNode - private let placeholderNode: ASTextNode +public func webBrowserDomainController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, apply: @escaping (String?) -> Void) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings - var updateHeight: (() -> Void)? - var complete: (() -> Void)? - var textChanged: ((String) -> Void)? + let inputState = AlertInputFieldComponent.ExternalState() - private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0) - private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0) - - var text: String { - get { - return self.textInputNode.attributedText?.string ?? "" - } - set { - self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor) - self.placeholderNode.isHidden = !newValue.isEmpty - } + let doneIsEnabled: Signal = inputState.valueSignal + |> map { value in + return !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } - var placeholder: String = "" { - didSet { - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - } - } - - init(theme: PresentationTheme, placeholder: String) { - self.theme = theme + let doneInProgressPromise = ValuePromise(false) + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.WebBrowser_Exceptions_Create_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.WebBrowser_Exceptions_Create_Text)) + ) + )) - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - - self.textInputNode = EditableTextNode() - self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor] - self.textInputNode.clipsToBounds = true - self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) - self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0) - self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance - self.textInputNode.keyboardType = .URL - self.textInputNode.autocapitalizationType = .none - self.textInputNode.returnKeyType = .done - self.textInputNode.autocorrectionType = .no - self.textInputNode.tintColor = theme.actionSheet.controlAccentColor - - self.placeholderNode = ASTextNode() - self.placeholderNode.isUserInteractionEnabled = false - self.placeholderNode.displaysAsynchronously = false - self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - - super.init() - - self.textInputNode.delegate = self - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.textInputNode) - self.addSubnode(self.placeholderNode) - } - - func updateTheme(_ theme: PresentationTheme) { - self.theme = theme - - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor - } - - func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let textFieldHeight = self.calculateTextFieldMetrics(width: width) - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - - let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom)) - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - - let placeholderSize = self.placeholderNode.measure(backgroundFrame.size) - transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) - - transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height))) - - return panelHeight - } - - func activateInput() { - self.textInputNode.becomeFirstResponder() - } - - func deactivateInput() { - self.textInputNode.resignFirstResponder() - } - - @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { - self.updateTextNodeText(animated: true) - self.textChanged?(editableTextNode.textView.text) - self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty - } - - private let domainRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.?)*([a-zA-Z]*)?(:)?(/)?$", options: []) - private let pathRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}/", options: []) - - var inProgress = false - - func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - if self.inProgress { - return false - } - if text == "\n" { - self.complete?() - return false - } - - if let domainRegex = self.domainRegex, let pathRegex = self.pathRegex { - let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text) - let domainMatches = domainRegex.matches(in: updatedText, options: [], range: NSRange(location: 0, length: updatedText.utf16.count)) - let pathMatches = pathRegex.matches(in: updatedText, options: [], range: NSRange(location: 0, length: updatedText.utf16.count)) - - if domainMatches.count > 0, pathMatches.count == 0 { - return true - } else { - return false - } - } - - return true - } - - private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height)) - - return min(61.0, max(33.0, unboundTextFieldHeight)) - } - - private func updateTextNodeText(animated: Bool) { - let backgroundInsets = self.backgroundInsets - - let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width) - - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - if !self.bounds.size.height.isEqual(to: panelHeight) { - self.updateHeight?() - } - } - - @objc func clearPressed() { - self.textInputNode.attributedText = nil - self.deactivateInput() - } -} - -private final class WebBrowserDomainAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - - private let titleNode: ASTextNode - private let textNode: ASTextNode - let activityIndicator: ActivityIndicator - let inputFieldNode: WebBrowserDomainInputFieldNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let disposable = MetaDisposable() - - private var validLayout: CGSize? - - private let hapticFeedback = HapticFeedback() - - var complete: (() -> Void)? { - didSet { - self.inputFieldNode.complete = self.complete - } - } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction]) { - self.strings = strings - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 2 - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 2 - - self.activityIndicator = ActivityIndicator(type: .custom(ptheme.rootController.navigationBar.secondaryTextColor, 20.0, 1.5, false), speed: .slow) - self.activityIndicator.isHidden = true - - self.inputFieldNode = WebBrowserDomainInputFieldNode(theme: ptheme, placeholder: strings.WebBrowser_Exceptions_Create_Placeholder) - self.inputFieldNode.text = "" - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - - self.addSubnode(self.inputFieldNode) - self.addSubnode(self.activityIndicator) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - self.actionNodes.last?.actionEnabled = false - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.inputFieldNode.updateHeight = { [weak self] in - if let strongSelf = self { - if let _ = strongSelf.validLayout { - strongSelf.requestLayout?(.animated(duration: 0.15, curve: .spring)) - } - } - } - - self.inputFieldNode.textChanged = { [weak self] text in - if let strongSelf = self, let lastNode = strongSelf.actionNodes.last { - lastNode.actionEnabled = !text.isEmpty - } - } - - self.updateTheme(theme) - } - - deinit { - self.disposable.dispose() - } - - var link: String { - return self.inputFieldNode.text - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.strings.WebBrowser_Exceptions_Create_Title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = NSAttributedString(string: self.strings.WebBrowser_Exceptions_Create_Text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) - - let hadValidLayout = self.validLayout != nil - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - let spacing: CGFloat = 5.0 - - let titleSize = self.titleNode.measure(measureSize) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 4.0 - - let textSize = self.textNode.measure(measureSize) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 6.0 + spacing - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) - - var contentWidth = max(titleSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - let inputFieldWidth = resultWidth - let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) - let inputHeight = inputFieldHeight - let inputFrame = CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight) - transition.updateFrame(node: self.inputFieldNode, frame: inputFrame) - transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0) - - let activitySize = CGSize(width: 20.0, height: 20.0) - transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: inputFrame.maxX - activitySize.width - 23.0, y: inputFrame.midY - activitySize.height / 2.0 - 3.0), size: activitySize)) - - let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if !hadValidLayout { - self.inputFieldNode.activateInput() - } - - return resultSize - } - - func animateError() { - self.inputFieldNode.layer.addShakeAnimation() - self.hapticFeedback.error() - } -} - -public func webBrowserDomainController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, apply: @escaping (String?) -> Void) -> AlertController { - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - - var dismissImpl: ((Bool) -> Void)? + let domainRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.?)*([a-zA-Z]*)?(:)?(/)?$", options: []) + let pathRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}/", options: []) var applyImpl: (() -> Void)? + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + initialValue: nil, + placeholder: strings.WebBrowser_Exceptions_Create_Placeholder, + characterLimit: nil, + hasClearButton: true, + keyboardType: .URL, + autocapitalizationType: .none, + autocorrectionType: .no, + isInitiallyFocused: true, + externalState: inputState, + shouldChangeText: { updatedText in + guard let domainRegex, let pathRegex else { + return true + } + let domainMatches = domainRegex.matches(in: updatedText, options: [], range: NSRange(location: 0, length: updatedText.utf16.count)) + let pathMatches = pathRegex.matches(in: updatedText, options: [], range: NSRange(location: 0, length: updatedText.utf16.count)) + if domainMatches.count > 0, pathMatches.count == 0 { + return true + } else { + return false + } + }, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) - var inProgress = false - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - if !inProgress { - dismissImpl?(true) - apply(nil) - } - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: { - if !inProgress { - applyImpl?() - } - })] - - let contentNode = WebBrowserDomainAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions) - contentNode.complete = { - applyImpl?() + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) } - applyImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - inProgress = true - contentNode.inputFieldNode.inProgress = true - contentNode.activityIndicator.isHidden = false - - let updatedLink = explicitUrl(contentNode.link) + + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.Common_Done, type: .default, action: { + applyImpl?() + }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get()) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + applyImpl = { + let updatedLink = explicitUrl(inputState.value) if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true]) { + doneInProgressPromise.set(true) apply(updatedLink) } else { - contentNode.animateError() + inputState.animateError() } } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.inputFieldNode.updateTheme(presentationData.theme) - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - } - dismissImpl = { [weak controller] animated in - contentNode.inputFieldNode.deactivateInput() - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + return alertController } diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift index 2ba71084..29197201 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift @@ -132,7 +132,7 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.iconNode) self.addSubnode(self.titleNode) diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift index 410254c4..ecaa1035 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift @@ -117,7 +117,7 @@ private final class WebBrowserItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.iconNode) self.addSubnode(self.checkIconNode) diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift index c66c0a99..85fd2174 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift @@ -419,7 +419,7 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl }) dismissImpl = { [weak linkController] in linkController?.view.endEditing(true) - linkController?.dismissAnimated() + linkController?.dismiss(completion: nil) } controller?.present(linkController, in: .window(.root)) } diff --git a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift index df1a0039..7cd58def 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift @@ -16,6 +16,7 @@ import AccountUtils import PremiumUI import PasswordSetupUI import StorageUsageScreen +import AlertComponent private struct DeleteAccountOptionsArguments { let changePhoneNumber: () -> Void @@ -102,43 +103,43 @@ private enum DeleteAccountOptionsEntry: ItemListNodeEntry, Equatable { let arguments = arguments as! DeleteAccountOptionsArguments switch self { case let .changePhoneNumber(_, title, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.changePhoneNumber, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.changePhoneNumber, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.changePhoneNumber() }) case let .addAccount(_, title, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteAddAccount, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteAddAccount, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.addAccount() }) case let .changePrivacy(_, title, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.security, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.security, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.setupPrivacy() }) case let .setTwoStepAuth(_, title, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteSetTwoStepAuth, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteSetTwoStepAuth, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.setupTwoStepAuth() }) case let .setPasscode(_, title, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteSetPasscode, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteSetPasscode, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.setPasscode() }) case let .clearCache(_, title, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.dataAndStorage, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.dataAndStorage, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.clearCache() }) case let .clearSyncedContacts(_, title, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.clearSynced, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.clearSynced, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.clearSyncedContacts() }) case let .deleteChats(_, title, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteChats, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteChats, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.deleteChats() }) case let .contactSupport(_, title, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.support, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.support, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.contactSupport() }) case let .deleteAccount(_, title): - return ItemListActionItem(presentationData: presentationData, title: title, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.deleteAccount() }) } @@ -362,32 +363,36 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) }) } - - let alertController = textAlertController(context: context, title: nil, text: presentationData.strings.Settings_FAQ_Intro, actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_FAQ_Button, action: { - openFaq(resolvedUrlPromise) - dismissImpl?() - }), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - supportPeerDisposable.set((supportPeer.get() - |> take(1) - |> deliverOnMainQueue).start(next: { peerId in - guard let peerId = peerId else { - return - } - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer else { + let alertController = AlertScreen( + context: context, + title: nil, + text: presentationData.strings.Settings_FAQ_Intro, + actions: [ + .init(title: presentationData.strings.Settings_FAQ_Button, action: { + openFaq(resolvedUrlPromise) + dismissImpl?() + }), + .init(title: presentationData.strings.Common_OK, type: .default, action: { + supportPeerDisposable.set((supportPeer.get() + |> take(1) + |> deliverOnMainQueue).start(next: { peerId in + guard let peerId else { return } - if let navigationController = navigationController { - dismissImpl?() - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) - } - }) - })) - }) - ]) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + guard let peer else { + return + } + if let navigationController = navigationController { + dismissImpl?() + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) + } + }) + })) + }) + ] + ) alertController.dismissed = { _ in addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_support_cancel") } diff --git a/submodules/SettingsUI/Sources/DeleteAccountPeersItem.swift b/submodules/SettingsUI/Sources/DeleteAccountPeersItem.swift index 0e7581bd..1ce362fc 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountPeersItem.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountPeersItem.swift @@ -169,7 +169,7 @@ class DeleteAccountPeersItemNode: ListViewItemNode, ItemListItemNode { self.listView = ListView() self.listView.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.listView) diff --git a/submodules/SettingsUI/Sources/DeleteAccountPhoneItem.swift b/submodules/SettingsUI/Sources/DeleteAccountPhoneItem.swift index 11c8ad49..396d6583 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountPhoneItem.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountPhoneItem.swift @@ -167,7 +167,7 @@ final class DeleteAccountPhoneItemNode: ListViewItemNode, ItemListItemNode { self.phoneInputNode = PhoneInputNode(fontSize: 17.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.phoneBackground) self.addSubnode(self.countryButton) diff --git a/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift b/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift index 8417ebce..63ec3ccf 100644 --- a/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift +++ b/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift @@ -36,7 +36,7 @@ public class LocalizationListController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) self.editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.editPressed)) self.doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) @@ -86,7 +86,7 @@ public class LocalizationListController: ViewController { private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) self.title = self.presentationData.strings.Settings_AppLanguage self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) diff --git a/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift b/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift index 4651de00..ec93cca6 100644 --- a/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift +++ b/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift @@ -165,7 +165,7 @@ private final class LocalizationListSearchContainerNode: SearchDisplayController self.presentationDataPromise = Promise(self.presentationData) self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) + self.dimNode.backgroundColor = .clear self.listNode = ListView() self.listNode.accessibilityPageScrolledString = { row, count in @@ -809,7 +809,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode { self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: LocalizationListSearchContainerNode(context: self.context, listState: self.currentListState ?? LocalizationListState.defaultSettings, selectLocalization: { [weak self] info in self?.selectLocalization(info) }, applyingCode: self.applyingCode.get()), inline: true, cancel: { [weak self] in self?.requestDeactivateSearch() - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in diff --git a/submodules/SettingsUI/Sources/LogoutOptionsController.swift b/submodules/SettingsUI/Sources/LogoutOptionsController.swift index 9deadca7..862a3054 100644 --- a/submodules/SettingsUI/Sources/LogoutOptionsController.swift +++ b/submodules/SettingsUI/Sources/LogoutOptionsController.swift @@ -272,10 +272,6 @@ public func logoutOptionsController(context: AccountContext, navigationControlle context.sharedContext.accountManager.accessChallengeData() ) |> map { presentationData, accessChallengeData -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { - dismissImpl?() - }) - var hasPasscode = false switch accessChallengeData.data { case .numericalPassword, .plaintextPassword: @@ -284,7 +280,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle break } - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.LogoutOptions_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.LogoutOptions_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: logoutOptionsEntries(presentationData: presentationData, canAddAccounts: canAddAccounts, hasPasscode: hasPasscode), style: .blocks) return (controllerState, (listState, arguments)) diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift index 336ce4ce..32ca8fcb 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift @@ -243,7 +243,6 @@ private final class NotificationExceptionArguments { } private enum NotificationExceptionEntryId: Hashable { - case search case peerId(Int64) case addException case removeAll @@ -254,13 +253,6 @@ private enum NotificationExceptionEntryId: Hashable { static func ==(lhs: NotificationExceptionEntryId, rhs: NotificationExceptionEntryId) -> Bool { switch lhs { - case .search: - switch rhs { - case .search: - return true - default: - return false - } case .addException: switch rhs { case .addException: @@ -302,7 +294,6 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { typealias ItemGenerationArguments = NotificationExceptionArguments - case search(PresentationTheme, PresentationStrings) case peer(index: Int, peer: EnginePeer, theme: PresentationTheme, strings: PresentationStrings, dateFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, description: String, notificationSettings: TelegramPeerNotificationSettings, revealed: Bool, editing: Bool, isSearching: Bool) case addPeer(index: Int, peer: EnginePeer, theme: PresentationTheme, strings: PresentationStrings, dateFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder) case addException(PresentationTheme, PresentationStrings, NotificationExceptionMode.Mode, Bool) @@ -311,10 +302,6 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! NotificationExceptionArguments switch self { - case let .search(theme, strings): - return NotificationSearchItem(theme: theme, placeholder: strings.Common_Search, activate: { - arguments.activateSearch() - }) case let .addException(theme, strings, mode, editing): let icon: UIImage? switch mode { @@ -352,8 +339,6 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { var stableId: NotificationExceptionEntryId { switch self { - case .search: - return .search case .addException: return .addException case let .peer(_, peer, _, _, _, _, _, _, _, _, _): @@ -367,13 +352,6 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { static func == (lhs: NotificationExceptionEntry, rhs: NotificationExceptionEntry) -> Bool { switch lhs { - case let .search(lhsTheme, lhsStrings): - switch rhs { - case let .search(rhsTheme, rhsStrings): - return lhsTheme === rhsTheme && lhsStrings === rhsStrings - default: - return false - } case let .addException(lhsTheme, lhsStrings, lhsMode, lhsEditing): switch rhs { case let .addException(rhsTheme, rhsStrings, rhsMode, rhsEditing): @@ -406,18 +384,16 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { static func <(lhs: NotificationExceptionEntry, rhs: NotificationExceptionEntry) -> Bool { switch lhs { - case .search: - return true case .addException: switch rhs { - case .search, .addException: + case .addException: return false default: return true } case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _): switch rhs { - case .search, .addException: + case .addException: return false case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _): return lhsIndex < rhsIndex @@ -428,7 +404,7 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { } case let .addPeer(lhsIndex, _, _, _, _, _): switch rhs { - case .search, .addException: + case .addException: return false case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _): return lhsIndex < rhsIndex @@ -936,7 +912,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: NotificationExceptionsSearchContainerNode(context: self.context, mode: self.stateValue.modify {$0}.mode, arguments: self.arguments!), cancel: { [weak self] in self?.requestDeactivateSearch(true) - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in @@ -1007,7 +983,7 @@ private final class NotificationExceptionsSearchContainerNode: SearchDisplayCont self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) + self.dimNode.backgroundColor = .clear self.listNode = ListView() self.listNode.accessibilityPageScrolledString = { row, count in diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift index 7cdd4094..7a13bda8 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift @@ -39,7 +39,9 @@ public class NotificationExceptionsController: ViewController { self.updatedMode = updatedMode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + + self._hasGlassStyle = true self.removeAllItem = UIBarButtonItem(title: self.presentationData.strings.Notification_Exceptions_DeleteAll, style: .plain, target: self, action: #selector(self.removeAllPressed)) self.editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.editPressed)) @@ -90,7 +92,7 @@ public class NotificationExceptionsController: ViewController { private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) self.title = self.presentationData.strings.Notifications_ExceptionsTitle self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) diff --git a/submodules/SettingsUI/Sources/Notifications/NotificationsCategoryItemListItem.swift b/submodules/SettingsUI/Sources/Notifications/NotificationsCategoryItemListItem.swift index 9e0f727d..a63e4174 100644 --- a/submodules/SettingsUI/Sources/Notifications/NotificationsCategoryItemListItem.swift +++ b/submodules/SettingsUI/Sources/Notifications/NotificationsCategoryItemListItem.swift @@ -148,7 +148,7 @@ public class NotificationsCategoryItemListItemNode: ListViewItemNode, ItemListIt self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.subtitleNode) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index f522f382..d7bb50ba 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -115,7 +115,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { self.measureTextNode = TextNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.clipsToBounds = true diff --git a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift index 585f1d76..5eda7838 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveHeaderItem.swift @@ -68,7 +68,7 @@ class GlobalAutoremoveHeaderItemNode: ListViewItemNode { init() { self.animationNode = DefaultAnimatedStickerNodeImpl() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.animationNode) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift index a10a405f..6b4d5aae 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift @@ -269,8 +269,8 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32, } else { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let valueText = timeIntervalString(strings: presentationData.strings, value: timeout, usage: .afterTime) - presentControllerImpl?(standardTextAlertController( - theme: AlertControllerTheme(presentationData: presentationData), + presentControllerImpl?(textAlertController( + context: context, title: presentationData.strings.GlobalAutodeleteSettings_SetConfirmTitle, text: presentationData.strings.GlobalAutodeleteSettings_SetConfirmText(valueText).string, actions: [ @@ -350,8 +350,8 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32, text = presentationData.strings.GlobalAutodeleteSettings_AttemptDisabledGenericSelection } - presentControllerImpl?(standardTextAlertController( - theme: AlertControllerTheme(presentationData: presentationData), + presentControllerImpl?(textAlertController( + context: context, title: nil, text: text, actions: [ diff --git a/submodules/SettingsUI/Sources/Privacy and Security/LoginEmailSetupController.swift b/submodules/SettingsUI/Sources/Privacy and Security/LoginEmailSetupController.swift index 45cb19e0..60087d54 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/LoginEmailSetupController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/LoginEmailSetupController.swift @@ -5,6 +5,7 @@ import SwiftSignalKit import TelegramCore import AccountContext import TelegramPresentationData +import PresentationDataUtils import AuthorizationUI import AuthenticationServices import UndoUI @@ -76,7 +77,7 @@ public func loginEmailSetupController(context: AccountContext, blocking: Bool, e var dismissCodeControllerImpl: (() -> Void)? var presentControllerImpl: ((ViewController) -> Void)? - let codeController = AuthorizationSequenceCodeEntryController(presentationData: presentationData, back: { + let codeController = AuthorizationSequenceCodeEntryController(sharedContext: context.sharedContext, presentationData: presentationData, back: { dismissCodeControllerImpl?() dismiss() }) @@ -119,7 +120,7 @@ public func loginEmailSetupController(context: AccountContext, blocking: Bool, e codeController?.resetCode() } - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])) + presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])) } } }, completed: { [weak codeController] in @@ -148,7 +149,7 @@ public func loginEmailSetupController(context: AccountContext, blocking: Bool, e text = presentationData.strings.Login_EmailNotAllowedError } - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])) + presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])) }, completed: { [weak emailController] in emailController?.inProgress = false }) @@ -173,7 +174,7 @@ public func loginEmailSetupController(context: AccountContext, blocking: Bool, e switch credential { case let appleIdCredential as ASAuthorizationAppleIDCredential: guard let tokenData = appleIdCredential.identityToken, let token = String(data: tokenData, encoding: .utf8) else { - emailController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + emailController?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return } let _ = (verifyLoginEmailChange(account: context.account, code: .appleToken(token)) @@ -193,7 +194,7 @@ public func loginEmailSetupController(context: AccountContext, blocking: Bool, e case .emailNotAllowed: text = presentationData.strings.Login_EmailNotAllowedError } - emailController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + emailController?.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, completed: { [weak emailController] in emailController?.authorization = nil emailController?.authorizationDelegate = nil diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index 09aeb828..ed2b35e3 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -45,8 +45,11 @@ private final class PrivacyAndSecurityControllerArguments { let openEmailSettings: (String?) -> Void let openMessagePrivacy: () -> Void let openGiftsPrivacy: () -> Void + let openDeletedMessages: () -> Void + let openGhostMode: () -> Void + let openMisc: () -> Void - init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openVoiceMessagePrivacy: @escaping () -> Void, openBioPrivacy: @escaping () -> Void, openBirthdayPrivacy: @escaping () -> Void, openSavedMusicPrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVerificationAccessConfiguration?) -> Void, openPasskeys: @escaping () -> Void, openActiveSessions: @escaping () -> Void, toggleArchiveAndMuteNonContacts: @escaping (Bool) -> Void, setupAccountAutoremove: @escaping () -> Void, setupMessageAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void, openEmailSettings: @escaping (String?) -> Void, openMessagePrivacy: @escaping () -> Void, openGiftsPrivacy: @escaping () -> Void) { + init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openVoiceMessagePrivacy: @escaping () -> Void, openBioPrivacy: @escaping () -> Void, openBirthdayPrivacy: @escaping () -> Void, openSavedMusicPrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVerificationAccessConfiguration?) -> Void, openPasskeys: @escaping () -> Void, openActiveSessions: @escaping () -> Void, toggleArchiveAndMuteNonContacts: @escaping (Bool) -> Void, setupAccountAutoremove: @escaping () -> Void, setupMessageAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void, openEmailSettings: @escaping (String?) -> Void, openMessagePrivacy: @escaping () -> Void, openGiftsPrivacy: @escaping () -> Void, openDeletedMessages: @escaping () -> Void, openGhostMode: @escaping () -> Void, openMisc: @escaping () -> Void) { self.account = account self.openBlockedUsers = openBlockedUsers self.openLastSeenPrivacy = openLastSeenPrivacy @@ -70,6 +73,9 @@ private final class PrivacyAndSecurityControllerArguments { self.openEmailSettings = openEmailSettings self.openMessagePrivacy = openMessagePrivacy self.openGiftsPrivacy = openGiftsPrivacy + self.openDeletedMessages = openDeletedMessages + self.openGhostMode = openGhostMode + self.openMisc = openMisc } } @@ -81,6 +87,9 @@ private enum PrivacyAndSecuritySection: Int32 { case messageAutoremove case dataSettings case loginEmail + case antiDelete + case ghostMode + case misc } public enum PrivacyAndSecurityEntryTag: ItemListItemTag { @@ -130,6 +139,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case messageAutoremoveInfo(PresentationTheme, String) case dataSettings(PresentationTheme, String) case dataSettingsInfo(PresentationTheme, String) + case deletedMessages(PresentationTheme, String, String) + case deletedMessagesInfo(PresentationTheme, String) + case ghostMode(PresentationTheme, String, String) + case ghostModeInfo(PresentationTheme, String) + case misc(PresentationTheme, String, String) + case miscInfo(PresentationTheme, String) var section: ItemListSectionId { switch self { @@ -145,6 +160,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { return PrivacyAndSecuritySection.account.rawValue case .dataSettings, .dataSettingsInfo: return PrivacyAndSecuritySection.dataSettings.rawValue + case .deletedMessages, .deletedMessagesInfo: + return PrivacyAndSecuritySection.antiDelete.rawValue + case .ghostMode, .ghostModeInfo: + return PrivacyAndSecuritySection.ghostMode.rawValue + case .misc, .miscInfo: + return PrivacyAndSecuritySection.misc.rawValue } } @@ -214,6 +235,18 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { return 31 case .dataSettingsInfo: return 32 + case .deletedMessages: + return 33 + case .deletedMessagesInfo: + return 34 + case .ghostMode: + return 35 + case .ghostModeInfo: + return 36 + case .misc: + return 37 + case .miscInfo: + return 38 } } @@ -411,6 +444,42 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { } else { return false } + case let .deletedMessages(lhsTheme, lhsText, lhsValue): + if case let .deletedMessages(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } + case let .deletedMessagesInfo(lhsTheme, lhsText): + if case let .deletedMessagesInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .ghostMode(lhsTheme, lhsText, lhsValue): + if case let .ghostMode(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } + case let .ghostModeInfo(lhsTheme, lhsText): + if case let .ghostModeInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .misc(lhsTheme, lhsText, lhsValue): + if case let .misc(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } + case let .miscInfo(lhsTheme, lhsText): + if case let .miscInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } } } @@ -542,6 +611,24 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { }) case let .dataSettingsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .deletedMessages(_, text, value): + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Timer")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + arguments.openDeletedMessages() + }) + case let .deletedMessagesInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .ghostMode(_, text, value): + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Appearance")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + arguments.openGhostMode() + }) + case let .ghostModeInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .misc(_, text, value): + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Storage")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + arguments.openMisc() + }) + case let .miscInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) } } } @@ -1406,10 +1493,10 @@ public func privacyAndSecurityController( let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = textAlertController( context: context, title: emailPattern, text: presentationData.strings.PrivacySettings_LoginEmailAlertText, actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.PrivacySettings_LoginEmailAlertChange, action: { + TextAlertAction(type: .defaultAction, title: presentationData.strings.PrivacySettings_LoginEmailAlertChange, action: { setupEmailImpl?(emailPattern) }), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: { + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { }) ], actionLayout: .vertical @@ -1497,6 +1584,12 @@ public func privacyAndSecurityController( }), true) } })) + }, openDeletedMessages: { + pushControllerImpl?(deletedMessagesController(context: context), true) + }, openGhostMode: { + pushControllerImpl?(ghostModeController(context: context), true) + }, openMisc: { + pushControllerImpl?(miscController(context: context), true) }) actionsDisposable.add(context.engine.peers.managedUpdatedRecentPeers().start()) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift index 73a50a65..b6c12d70 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift @@ -120,7 +120,7 @@ public final class PrivacyIntroController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -161,7 +161,7 @@ public final class PrivacyIntroController: ViewController { private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.controllerNode.updatePresentationData(self.presentationData) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift index 77684f5b..a872ae6a 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift @@ -241,7 +241,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.iconNode) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift index cd902c96..1dc5be38 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift @@ -190,7 +190,7 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.avatarNode) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift index 2bd1a4d1..0b9dab7e 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift @@ -92,7 +92,7 @@ class RecentSessionsHeaderItemNode: ListViewItemNode { self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), fontSize: 16.0, height: 50.0, cornerRadius: 11.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.addSubnode(self.animationNode) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 5f656d9b..6913f3f3 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -1664,6 +1664,12 @@ public func selectivePrivacySettingsController( } else { updatedDisallowedGifts.remove(.premium) } + case .channel: + if value { + updatedDisallowedGifts.insert(.channel) + } else { + updatedDisallowedGifts.remove(.channel) + } default: break } diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift index 595c4adb..076f1874 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift @@ -13,6 +13,9 @@ import AccountContext import SearchBarNode import SearchUI import ChatListSearchItemHeader +import EdgeEffect +import ComponentFlow +import ComponentDisplayAdapters extension SettingsSearchableItemIcon { func image() -> UIImage? { @@ -59,113 +62,6 @@ extension SettingsSearchableItemIcon { } } -final class SettingsSearchItem: ItemListControllerSearch { - let context: AccountContext - let theme: PresentationTheme - let placeholder: String - let activated: Bool - let updateActivated: (Bool) -> Void - let presentController: (ViewController, Any?) -> Void - let pushController: (ViewController) -> Void - let getNavigationController: (() -> NavigationController?)? - let resolvedFaqUrl: Signal - let exceptionsList: Signal - let archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError> - let privacySettings: Signal - let hasTwoStepAuth: Signal - let twoStepAuthData: Signal - let activeSessionsContext: Signal - let webSessionsContext: Signal - - private var updateActivity: ((Bool) -> Void)? - private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) - private let activityDisposable = MetaDisposable() - - init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, getNavigationController: (() -> NavigationController?)?, resolvedFaqUrl: Signal, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasTwoStepAuth: Signal, twoStepAuthData: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { - self.context = context - self.theme = theme - self.placeholder = placeholder - self.activated = activated - self.updateActivated = updateActivated - self.presentController = presentController - self.pushController = pushController - self.getNavigationController = getNavigationController - self.resolvedFaqUrl = resolvedFaqUrl - self.exceptionsList = exceptionsList - self.archivedStickerPacks = archivedStickerPacks - self.privacySettings = privacySettings - self.hasTwoStepAuth = hasTwoStepAuth - self.twoStepAuthData = twoStepAuthData - self.activeSessionsContext = activeSessionsContext - self.webSessionsContext = webSessionsContext - self.activityDisposable.set((activity.get() |> mapToSignal { value -> Signal in - if value { - return .single(value) |> delay(0.2, queue: Queue.mainQueue()) - } else { - return .single(value) - } - }).start(next: { [weak self] value in - self?.updateActivity?(value) - })) - } - - deinit { - self.activityDisposable.dispose() - } - - func isEqual(to: ItemListControllerSearch) -> Bool { - if let to = to as? SettingsSearchItem { - if self.context !== to.context || self.theme !== to.theme || self.placeholder != to.placeholder || self.activated != to.activated { - return false - } - return true - } else { - return false - } - } - - func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { - let updateActivated: (Bool) -> Void = self.updateActivated - if let current = current as? NavigationBarSearchContentNode { - current.updateThemeAndPlaceholder(theme: self.theme, placeholder: self.placeholder) - return current - } else { - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - return NavigationBarSearchContentNode(theme: presentationData.theme, placeholder: presentationData.strings.Settings_Search, activate: { - updateActivated(true) - }) - } - } - - func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode { - let updateActivated: (Bool) -> Void = self.updateActivated - let presentController: (ViewController, Any?) -> Void = self.presentController - let pushController: (ViewController) -> Void = self.pushController - - if let current = current as? SettingsSearchItemNode, let titleContentNode = titleContentNode as? NavigationBarSearchContentNode { - current.updatePresentationData(self.context.sharedContext.currentPresentationData.with { $0 }) - if current.isSearching != self.activated { - if self.activated { - current.activateSearch(placeholderNode: titleContentNode.placeholderNode) - } else { - current.deactivateSearch(placeholderNode: titleContentNode.placeholderNode) - } - } - return current - } else { - return SettingsSearchItemNode(context: self.context, cancel: { - updateActivated(false) - }, updateActivity: { [weak self] value in - self?.activity.set(value) - }, pushController: { c in - pushController(c) - }, presentController: { c, a in - presentController(c, a) - }, getNavigationController: self.getNavigationController, resolvedFaqUrl: self.resolvedFaqUrl, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasTwoStepAuth: self.hasTwoStepAuth, twoStepAuthData: self.twoStepAuthData, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext) - } - } -} - final class SettingsSearchInteraction { let openItem: (SettingsSearchableItem) -> Void let deleteRecentItem: (SettingsSearchableItemId) -> Void @@ -333,6 +229,8 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo private let listNode: ListView private let recentListNode: ListView + private let edgeEffectView: EdgeEffectView + private var enqueuedTransitions: [SettingsSearchContainerTransition] = [] private var enqueuedRecentTransitions: [(SettingsSearchContainerRecentTransition, Bool)] = [] private var hasValidLayout = false @@ -365,12 +263,15 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo return presentationData.strings.VoiceOver_ScrollStatus(row, count).string } + self.edgeEffectView = EdgeEffectView() + super.init() self.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.addSubnode(self.recentListNode) self.addSubnode(self.listNode) + self.view.addSubview(self.edgeEffectView) let interaction = SettingsSearchInteraction(openItem: { result in addRecentSettingsSearchItem(engine: context.engine, item: result.id) @@ -620,6 +521,12 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo self.dequeueTransition() } } + + let edgeEffectHeight: CGFloat = insets.bottom + 8.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) + transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, 50.0), transition: ComponentTransition(transition)) + transition.updateAlpha(layer: self.edgeEffectView.layer, alpha: edgeEffectHeight > 21.0 ? 1.0 : 0.0) } public override func scrollToTop() { @@ -715,7 +622,7 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { } }, resolvedFaqUrl: self.resolvedFaqUrl, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasTwoStepAuth: self.hasTwoStepAuth, twoStepAuthData: self.twoStepAuthData, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext), cancel: { [weak self] in self?.cancel() - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchRecentItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchRecentItem.swift index 10f315d5..f2863b3b 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchRecentItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchRecentItem.swift @@ -127,7 +127,7 @@ class SettingsSearchRecentItemNode: ItemListRevealOptionsItemNode { self.subtitleNode.contentMode = .left self.subtitleNode.contentsScale = UIScreenScale - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.separatorNode) diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchResultItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchResultItem.swift index 7e730023..c04b38af 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchResultItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchResultItem.swift @@ -117,7 +117,7 @@ class SettingsSearchResultItemNode: ListViewItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.iconNode) self.addSubnode(self.titleNode) diff --git a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift index 333125c4..2317be18 100644 --- a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift +++ b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift @@ -6,7 +6,9 @@ import Display import AsyncDisplayKit import TelegramPresentationData import TelegramUIPreferences +import PresentationDataUtils import ProgressNavigationButtonNode +import AccountContext public class TermsOfServiceControllerTheme { public let statusBarStyle: StatusBarStyle @@ -43,6 +45,7 @@ public class TermsOfServiceController: ViewController, StandalonePresentableCont return self.displayNode as! TermsOfServiceControllerNode } + private let context: AccountContext private let presentationData: PresentationData private let text: String private let entities: [MessageTextEntity] @@ -67,7 +70,8 @@ public class TermsOfServiceController: ViewController, StandalonePresentableCont } } - public init(presentationData: PresentationData, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?, signingUp: Bool, accept: @escaping (String?) -> Void, decline: @escaping () -> Void, openUrl: @escaping (String) -> Void) { + public init(context: AccountContext, presentationData: PresentationData, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?, signingUp: Bool, accept: @escaping (String?) -> Void, decline: @escaping () -> Void, openUrl: @escaping (String) -> Void) { + self.context = context self.presentationData = presentationData self.text = text self.entities = entities @@ -112,7 +116,7 @@ public class TermsOfServiceController: ViewController, StandalonePresentableCont text = strongSelf.presentationData.strings.PrivacyPolicy_DeclineMessage declineTitle = strongSelf.presentationData.strings.PrivacyPolicy_DeclineDeclineAndDelete } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.PrivacyPolicy_Decline, text: text, actions: [TextAlertAction(type: .destructiveAction, title: declineTitle, action: { + strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.PrivacyPolicy_Decline, text: text, actions: [TextAlertAction(type: .destructiveAction, title: declineTitle, action: { self?.decline() }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { })], actionLayout: .vertical), in: .window(.root)) @@ -122,7 +126,7 @@ public class TermsOfServiceController: ViewController, StandalonePresentableCont } if let ageConfirmation = strongSelf.ageConfirmation { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.PrivacyPolicy_AgeVerificationTitle, text: strongSelf.presentationData.strings.PrivacyPolicy_AgeVerificationMessage("\(ageConfirmation)").string, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.PrivacyPolicy_AgeVerificationAgree, action: { + strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.PrivacyPolicy_AgeVerificationTitle, text: strongSelf.presentationData.strings.PrivacyPolicy_AgeVerificationMessage("\(ageConfirmation)").string, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.PrivacyPolicy_AgeVerificationAgree, action: { self?.accept(self?.proccessBotNameAfterAccept) })]), in: .window(.root)) } else { diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 44ab48af..d026f7a4 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -228,7 +228,6 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { @@ -625,7 +624,8 @@ final class TextSizeSelectionController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationThemeSettings = presentationThemeSettings - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings, style: .glass)) + self._hasGlassStyle = true self.blocksBackgroundWhenInOverlay = true self.acceptsFocusWhenInOverlay = true diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionItem.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionItem.swift index f19fe421..125e1151 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionItem.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionItem.swift @@ -110,7 +110,7 @@ class BubbleSettingsRadiusItemNode: ListViewItemNode, ItemListItemNode { self.disabledOverlayNode = ASDisplayNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.leftIconNode) self.addSubnode(self.rightIconNode) diff --git a/submodules/SettingsUI/Sources/ThemePickerGridItem.swift b/submodules/SettingsUI/Sources/ThemePickerGridItem.swift index 5504d026..aae20dae 100644 --- a/submodules/SettingsUI/Sources/ThemePickerGridItem.swift +++ b/submodules/SettingsUI/Sources/ThemePickerGridItem.swift @@ -406,7 +406,7 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode { self.scrollNode = ASScrollNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.containerNode) self.addSubnode(self.scrollNode) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index 2713ca2a..26504db1 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -157,7 +157,7 @@ public final class ThemePreviewController: ViewController { let titleView = CounterControllerTitleView(theme: strongSelf.previewTheme) titleView.title = CounterControllerTitle(title: themeName, counter: hasInstallsCount ? strongSelf.presentationData.strings.Theme_UsersCount(max(1, theme.installCount ?? 0)) : "") strongSelf.navigationItem.titleView = titleView - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationTheme, presentationStrings: strongSelf.presentationData.strings)) + strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationTheme, presentationStrings: strongSelf.presentationData.strings), transition: .immediate) } }) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 9232eaf4..a0afa08c 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -377,7 +377,6 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift index 2e5218a8..7d673f35 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift @@ -298,7 +298,7 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode { self.dotsNode.displayWithoutProcessing = true self.dotsNode.image = generateDotsImage() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.fillNode) @@ -537,7 +537,7 @@ private final class ThemeSettingsAccentColorPickerItemNode : ListViewItemNode { self.imageNode.displayWithoutProcessing = true self.imageNode.image = generateCustomSwatchImage() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.imageNode) } @@ -734,7 +734,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { self.listNode = ListView() self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.containerNode) self.addSubnode(self.listNode) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift index 421d3d41..4ba20460 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift @@ -229,7 +229,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode { self.containerNode = ASDisplayNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.containerNode) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsBrightnessItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsBrightnessItem.swift index c4fa77d5..81aee3ec 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsBrightnessItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsBrightnessItem.swift @@ -90,7 +90,7 @@ class ThemeSettingsBrightnessItemNode: ListViewItemNode { self.rightIconNode.displaysAsynchronously = false self.rightIconNode.displayWithoutProcessing = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.leftIconNode) self.addSubnode(self.rightIconNode) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index ac07ff62..b475dce6 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -130,7 +130,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { self.containerNode = ASDisplayNode() self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.clipsToBounds = true diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsFontSizeItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsFontSizeItem.swift index aff462c9..08d11cee 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsFontSizeItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsFontSizeItem.swift @@ -110,7 +110,7 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode, ItemListItemNode { self.disabledOverlayNode = ASDisplayNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.leftIconNode) self.addSubnode(self.rightIconNode) diff --git a/submodules/ShimmerEffect/Sources/ShimmerEffect.swift b/submodules/ShimmerEffect/Sources/ShimmerEffect.swift index 2c586b59..467e16a7 100644 --- a/submodules/ShimmerEffect/Sources/ShimmerEffect.swift +++ b/submodules/ShimmerEffect/Sources/ShimmerEffect.swift @@ -236,12 +236,11 @@ public final class ShimmerEffectForegroundNode: ASDisplayNode { let image: UIImage? if horizontal { - let baseAlpha: CGFloat = 0.1 + //let baseAlpha: CGFloat = 0.1 image = generateImage(CGSize(width: effectSize ?? 200.0, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - let foregroundColor = UIColor(white: 1.0, alpha: min(1.0, baseAlpha * 4.0)) - + //let foregroundColor = foregroundColor //.withAlphaComponent(min(1.0, baseAlpha * 3.0)) if let shadowImage { UIGraphicsPushContext(context) diff --git a/submodules/StatisticsUI/BUILD b/submodules/StatisticsUI/BUILD index 293b755d..f5dbe1a1 100644 --- a/submodules/StatisticsUI/BUILD +++ b/submodules/StatisticsUI/BUILD @@ -49,13 +49,14 @@ swift_library( "//submodules/TelegramNotices", "//submodules/UIKitRuntimeUtils", "//submodules/PasswordSetupUI", - "//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController", "//submodules/TelegramUI/Components/ListItemComponentAdaptor", "//submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen", "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/TelegramUI/Components/Stars/StarsAvatarComponent", "//submodules/TelegramUI/Components/PremiumPeerShortcutComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/StatisticsUI/Sources/BackButton.swift b/submodules/StatisticsUI/Sources/BackButton.swift index 271cae3d..ff04a480 100644 --- a/submodules/StatisticsUI/Sources/BackButton.swift +++ b/submodules/StatisticsUI/Sources/BackButton.swift @@ -149,7 +149,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { case .back: text = presentationData.strings.Common_Back accessibilityText = presentationData.strings.Common_Back - icon = NavigationBar.backArrowImage(color: .white) + icon = navigationBarBackArrowImage(color: .white) case .edit: text = presentationData.strings.Common_Edit accessibilityText = text diff --git a/submodules/StatisticsUI/Sources/BoostLevelHeaderItem.swift b/submodules/StatisticsUI/Sources/BoostLevelHeaderItem.swift index 92707133..5bb6750c 100644 --- a/submodules/StatisticsUI/Sources/BoostLevelHeaderItem.swift +++ b/submodules/StatisticsUI/Sources/BoostLevelHeaderItem.swift @@ -76,7 +76,7 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { private var item: BoostLevelHeaderItem? init() { - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) } override func didLoad() { diff --git a/submodules/StatisticsUI/Sources/BoostsTabsItem.swift b/submodules/StatisticsUI/Sources/BoostsTabsItem.swift index 538b3235..334ede42 100644 --- a/submodules/StatisticsUI/Sources/BoostsTabsItem.swift +++ b/submodules/StatisticsUI/Sources/BoostsTabsItem.swift @@ -111,7 +111,7 @@ private final class BoostsTabsItemNode: ListViewItemNode { self.selectionNode = ASImageNode() self.selectionNode.displaysAsynchronously = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.boostsTextNode) self.addSubnode(self.giftsTextNode) diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 3602d253..af215827 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -1229,7 +1229,7 @@ private enum StatsEntry: ItemListNodeEntry { arguments.presentCpmLocked() }) case .earnStarsInfo: - return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.earnStars, title: presentationData.strings.Monetization_EarnStarsInfo_Title, titleBadge: presentationData.strings.Settings_New, label: presentationData.strings.Monetization_EarnStarsInfo_Text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.earnStars, title: presentationData.strings.Monetization_EarnStarsInfo_Title, titleBadge: nil, label: presentationData.strings.Monetization_EarnStarsInfo_Text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, action: { arguments.openEarnStars() }) } diff --git a/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift b/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift index 98441dc4..687d2a42 100644 --- a/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift +++ b/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift @@ -140,7 +140,7 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.iconNode) self.addSubnode(self.balanceTextNode) diff --git a/submodules/StatisticsUI/Sources/MonetizationIntroScreen.swift b/submodules/StatisticsUI/Sources/MonetizationIntroScreen.swift index 2dc7e168..b82e5658 100644 --- a/submodules/StatisticsUI/Sources/MonetizationIntroScreen.swift +++ b/submodules/StatisticsUI/Sources/MonetizationIntroScreen.swift @@ -12,7 +12,7 @@ import SheetComponent import BundleIconComponent import BalancedTextComponent import MultilineTextComponent -import SolidRoundedButtonComponent +import ButtonComponent import LottieComponent import AccountContext @@ -73,7 +73,7 @@ private final class SheetContent: CombinedComponent { let title = Child(BalancedTextComponent.self) let list = Child(List.self) - let actionButton = Child(SolidRoundedButtonComponent.self) + let actionButton = Child(ButtonComponent.self) let infoBackground = Child(RoundedRectangle.self) let infoTitle = Child(MultilineTextComponent.self) @@ -284,7 +284,7 @@ private final class SheetContent: CombinedComponent { let infoBackground = infoBackground.update( component: RoundedRectangle( color: theme.list.blocksBackgroundColor, - cornerRadius: 10.0 + cornerRadius: 26.0 ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: totalInfoHeight), transition: .immediate @@ -307,37 +307,48 @@ private final class SheetContent: CombinedComponent { contentSize.height += infoPadding contentSize.height += spacing + var buttonTitle: [AnyComponentWithIdentity] = [] + buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "anim_ok"), + color: theme.list.itemCheckColors.foregroundColor, + startingPosition: .begin, + size: CGSize(width: 28.0, height: 28.0), + playOnce: state.playOnce + )))) + buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent( + text: strings.Monetization_Intro_Understood, + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor + )))) + + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let actionButton = actionButton.update( - component: SolidRoundedButtonComponent( - title: strings.Monetization_Intro_Understood, - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: theme.list.itemCheckColors.fillColor, - backgroundColors: [], - foregroundColor: theme.list.itemCheckColors.foregroundColor + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(HStack(buttonTitle, spacing: 2.0)) ), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: 10.0, - gloss: false, - iconName: nil, - animationName: nil, - iconPosition: .left, action: { component.dismiss() } ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), transition: context.transition ) context.add(actionButton .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0)) ) contentSize.height += actionButton.size.height - contentSize.height += 22.0 - - contentSize.height += environment.safeInsets.bottom - + contentSize.height += buttonInsets.bottom + state.playAnimationIfNeeded() return contentSize @@ -397,6 +408,7 @@ private final class SheetContainerComponent: CombinedComponent { }) } )), + style: .glass, backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), followContentSizeChanges: true, externalState: sheetExternalState, diff --git a/submodules/StatisticsUI/Sources/RevenueWithdrawalController.swift b/submodules/StatisticsUI/Sources/RevenueWithdrawalController.swift index 7969836a..e6abc597 100644 --- a/submodules/StatisticsUI/Sources/RevenueWithdrawalController.swift +++ b/submodules/StatisticsUI/Sources/RevenueWithdrawalController.swift @@ -6,107 +6,148 @@ import TelegramPresentationData import PresentationDataUtils import AccountContext import PasswordSetupUI -import Markdown -import OwnershipTransferController +import ComponentFlow +import AlertComponent +import AlertInputFieldComponent -func confirmRevenueWithdrawalController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (String) -> Void) -> ViewController { - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } +func confirmRevenueWithdrawalController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + peerId: EnginePeer.Id, + present: @escaping (ViewController, Any?) -> Void, + completion: @escaping (String) -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + let inputState = AlertInputFieldComponent.ExternalState() + + let doneIsEnabled: Signal = inputState.valueSignal + |> map { value in + return !value.isEmpty + } + + let doneInProgressPromise = ValuePromise(false) + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Monetization_Withdraw_EnterPassword_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.Monetization_Withdraw_EnterPassword_Text)) + ) + )) + + var applyImpl: (() -> Void)? + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder, + isSecureTextEntry: true, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) + + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) + } var dismissImpl: (() -> Void)? - var proceedImpl: (() -> Void)? - - let disposable = MetaDisposable() - - let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.Monetization_Withdraw_EnterPassword_Title, text: presentationData.strings.Monetization_Withdraw_EnterPassword_Text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?() - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Monetization_Withdraw_EnterPassword_Done, action: { - proceedImpl?() - })]) - - contentNode.complete = { - proceedImpl?() - } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.theme = presentationData.theme - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - disposable.dispose() - } - dismissImpl = { [weak controller, weak contentNode] in - contentNode?.dismissInput() - controller?.dismissAnimated() - } - proceedImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - contentNode.updateIsChecking(true) - - let signal = context.engine.peers.requestStarsRevenueWithdrawalUrl(peerId: peerId, ton: true, amount: nil, password: contentNode.password) - disposable.set((signal |> deliverOnMainQueue).start(next: { url in + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.Monetization_Withdraw_EnterPassword_Done, type: .default, action: { + applyImpl?() + }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get()) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + applyImpl = { + doneInProgressPromise.set(true) + + let _ = (context.engine.peers.requestStarsRevenueWithdrawalUrl(peerId: peerId, ton: true, amount: nil, password: inputState.value) + |> deliverOnMainQueue).start(next: { url in dismissImpl?() completion(url) - }, error: { [weak contentNode] error in + }, error: { error in var errorTextAndActions: (String, [TextAlertAction])? switch error { - case .invalidPassword: - contentNode?.animateError() - case .limitExceeded: - errorTextAndActions = (presentationData.strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - default: - errorTextAndActions = (presentationData.strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + case .invalidPassword: + inputState.animateError() + case .limitExceeded: + errorTextAndActions = (strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) + default: + errorTextAndActions = (strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) } - contentNode?.updateIsChecking(false) - + doneInProgressPromise.set(false) + if let (text, actions) = errorTextAndActions { dismissImpl?() present(textAlertController(context: context, title: nil, text: text, actions: actions), nil) } - })) + }) } - - return controller + dismissImpl = { [weak alertController] in + alertController?.dismiss(completion: nil) + } + return alertController } public func revenueWithdrawalController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, initialError: RequestStarsRevenueWithdrawalError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (String) -> Void) -> ViewController { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - let theme = AlertControllerTheme(presentationData: presentationData) + let strings = presentationData.strings - var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.OwnershipTransfer_SecurityCheck, font: Font.semibold(presentationData.listsFontSize.itemListBaseFontSize), textColor: theme.primaryColor, paragraphAlignment: .center) + var title: String? = strings.OwnershipTransfer_SecurityCheck + var text = strings.Monetization_Withdraw_SecurityRequirements - var text = presentationData.strings.Monetization_Withdraw_SecurityRequirements - let textFontSize = presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0 - - var actions: [TextAlertAction] = [] + var actions: [AlertScreen.Action] = [ + .init(title: strings.Common_OK, type: .default) + ] switch initialError { - case .requestPassword: - return confirmRevenueWithdrawalController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, present: present, completion: completion) - case .twoStepAuthTooFresh, .authSessionTooFresh: - text = text + presentationData.strings.Monetization_Withdraw_ComeBackLater - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] - case .twoStepAuthMissing: - actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.OwnershipTransfer_SetupTwoStepAuth, action: { + case .requestPassword: + return confirmRevenueWithdrawalController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, present: present, completion: completion) + case .twoStepAuthTooFresh, .authSessionTooFresh: + text = text + strings.Monetization_Withdraw_ComeBackLater + case .twoStepAuthMissing: + actions = [ + .init(title: strings.OwnershipTransfer_SetupTwoStepAuth, type: .default, action: { let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in if shouldDismiss { controller.dismiss() } }) present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})] - default: - title = nil - text = presentationData.strings.Login_UnknownError - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] + }), + .init(title: strings.Common_Cancel) + ] + default: + title = nil + text = strings.Login_UnknownError } - let body = MarkdownAttributeSet(font: Font.regular(textFontSize), textColor: theme.primaryColor) - let bold = MarkdownAttributeSet(font: Font.semibold(textFontSize), textColor: theme.primaryColor) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - return richTextAlertController(context: context, title: title, text: attributedText, actions: actions) + return AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + title: title, + text: text, + actions: actions + ) } diff --git a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift index 1ab05b98..f0fa8796 100644 --- a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift +++ b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift @@ -113,7 +113,7 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) } func asyncLayout() -> (_ item: StarsTransactionItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { diff --git a/submodules/StatisticsUI/Sources/StatsGraphItem.swift b/submodules/StatisticsUI/Sources/StatsGraphItem.swift index 7f8d1c85..0ab64e45 100644 --- a/submodules/StatisticsUI/Sources/StatsGraphItem.swift +++ b/submodules/StatisticsUI/Sources/StatsGraphItem.swift @@ -133,7 +133,7 @@ public final class StatsGraphItemNode: ListViewItemNode { self.activityIndicator = ActivityIndicator(type: ActivityIndicatorType.custom(.black, 16.0, 2.0, false)) self.activityIndicator.isHidden = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.chartContainerNode.addSubnode(self.chartNode) self.chartContainerNode.addSubnode(self.activityIndicator) diff --git a/submodules/StatisticsUI/Sources/StatsMessageItem.swift b/submodules/StatisticsUI/Sources/StatsMessageItem.swift index 1a2bf60b..641da989 100644 --- a/submodules/StatisticsUI/Sources/StatsMessageItem.swift +++ b/submodules/StatisticsUI/Sources/StatsMessageItem.swift @@ -184,7 +184,7 @@ final class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.containerNode.addSubnode(self.contextSourceNode) self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode diff --git a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift index a5b9d6d2..78952691 100644 --- a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift +++ b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift @@ -327,7 +327,7 @@ class StatsOverviewItemNode: ListViewItemNode { self.bottomLeftItem = ValueItemNode() self.bottomRightItem = ValueItemNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.clipsToBounds = true diff --git a/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift b/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift index 3a87d208..c652c059 100644 --- a/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift +++ b/submodules/StatisticsUI/Sources/TransactionInfoScreen.swift @@ -12,11 +12,11 @@ import SheetComponent import BundleIconComponent import BalancedTextComponent import MultilineTextComponent -import SolidRoundedButtonComponent -import LottieComponent +import ButtonComponent import AccountContext import TelegramStringFormatting import PremiumPeerShortcutComponent +import GlassBarButtonComponent private final class SheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -55,18 +55,6 @@ private final class SheetContent: CombinedComponent { } final class State: ComponentState { - var cachedCloseImage: (UIImage, PresentationTheme)? - - let playOnce = ActionSlot() - private var didPlayAnimation = false - - func playAnimationIfNeeded() { - guard !self.didPlayAnimation else { - return - } - self.didPlayAnimation = true - self.playOnce.invoke(Void()) - } } func makeState() -> State { @@ -74,25 +62,23 @@ private final class SheetContent: CombinedComponent { } static var body: Body { - let closeButton = Child(Button.self) + let closeButton = Child(GlassBarButtonComponent.self) let amount = Child(MultilineTextComponent.self) let title = Child(MultilineTextComponent.self) let date = Child(MultilineTextComponent.self) let peerShortcut = Child(PremiumPeerShortcutComponent.self) - let actionButton = Child(SolidRoundedButtonComponent.self) + let actionButton = Child(ButtonComponent.self) return { context in let environment = context.environment[EnvironmentType.self] let component = context.component - let state = context.state let theme = environment.theme let strings = environment.strings let dateTimeFormat = component.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat - let sideInset: CGFloat = 16.0 + environment.safeInsets.left let textSideInset: CGFloat = 32.0 + environment.safeInsets.left let titleFont = Font.semibold(17.0) @@ -103,26 +89,27 @@ private final class SheetContent: CombinedComponent { var contentSize = CGSize(width: context.availableSize.width, height: 45.0) - let closeImage: UIImage - if let (image, theme) = state.cachedCloseImage, theme === environment.theme { - closeImage = image - } else { - closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)! - state.cachedCloseImage = (closeImage, theme) - } - let closeButton = closeButton.update( - component: Button( - content: AnyComponent(Image(image: closeImage)), - action: { [weak component] in - component?.dismiss() + component: GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + component.dismiss() } ), - availableSize: CGSize(width: 30.0, height: 30.0), + availableSize: CGSize(width: 40.0, height: 40.0), transition: .immediate ) context.add(closeButton - .position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0)) + .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0)) ) let amountString: NSMutableAttributedString @@ -255,27 +242,30 @@ private final class SheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + peerShortcut.size.height / 2.0)) ) contentSize.height += peerShortcut.size.height - contentSize.height += 50.0 + contentSize.height += 32.0 } else { - contentSize.height += 45.0 + contentSize.height += 27.0 } + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let actionButton = actionButton.update( - component: SolidRoundedButtonComponent( - title: buttonTitle, - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: theme.list.itemCheckColors.fillColor, - backgroundColors: [], - foregroundColor: theme.list.itemCheckColors.foregroundColor + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(ButtonTextContentComponent( + text: buttonTitle, + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor + )) ), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: 10.0, - gloss: false, - iconName: nil, - animationName: nil, - iconPosition: .left, action: { component.dismiss() if let explorerUrl { @@ -283,18 +273,14 @@ private final class SheetContent: CombinedComponent { } } ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), transition: context.transition ) context.add(actionButton .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0)) ) contentSize.height += actionButton.size.height - contentSize.height += 22.0 - - contentSize.height += environment.safeInsets.bottom - - state.playAnimationIfNeeded() + contentSize.height += buttonInsets.bottom return contentSize } @@ -360,6 +346,7 @@ private final class SheetContainerComponent: CombinedComponent { }) } )), + style: .glass, backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), followContentSizeChanges: true, externalState: sheetExternalState, diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 4597ed97..1e32fd99 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -1676,18 +1676,23 @@ private final class StickerPackContainer: ASDisplayNode { self.onLoading() var loadedCount = 0 - var error = false + var fetchingCount = 0 for content in contents { - if case .result = content { + switch content { + case .result: loadedCount += 1 - } else if case .none = content { - error = true + case .fetching: + fetchingCount += 1 + default: + break } } - if error { - self.onError() - } else if loadedCount == contents.count { + if fetchingCount == 0 { + if loadedCount == 0 { + self.onError() + return + } self.onReady() if !contents.isEmpty && self.currentStickerPacks.isEmpty { diff --git a/submodules/TabBarUI/BUILD b/submodules/TabBarUI/BUILD index a2bed9e0..2a3e1999 100644 --- a/submodules/TabBarUI/BUILD +++ b/submodules/TabBarUI/BUILD @@ -19,6 +19,7 @@ swift_library( "//submodules/ComponentFlow", "//submodules/Components/ComponentDisplayAdapters", "//submodules/TelegramUI/Components/TabBarComponent", + "//submodules/TelegramUI/Components/GlassControls", ], visibility = [ "//visibility:public", diff --git a/submodules/TabBarUI/Sources/TabBarContollerNode.swift b/submodules/TabBarUI/Sources/TabBarContollerNode.swift index abee8621..c4de51c3 100644 --- a/submodules/TabBarUI/Sources/TabBarContollerNode.swift +++ b/submodules/TabBarUI/Sources/TabBarContollerNode.swift @@ -6,27 +6,25 @@ import TelegramPresentationData import ComponentFlow import ComponentDisplayAdapters import TabBarComponent - -private extension ToolbarTheme { - convenience init(theme: PresentationTheme) { - self.init(barBackgroundColor: theme.rootController.tabBar.backgroundColor, barSeparatorColor: .clear, barTextColor: theme.rootController.tabBar.textColor, barSelectedTextColor: theme.rootController.tabBar.selectedTextColor) - } -} +import GlassControls final class TabBarControllerNode: ASDisplayNode { private struct Params: Equatable { let layout: ContainerViewLayout let toolbar: Toolbar? let isTabBarHidden: Bool + let currentControllerSearchState: ViewController.TabBarSearchState? init( layout: ContainerViewLayout, toolbar: Toolbar?, - isTabBarHidden: Bool + isTabBarHidden: Bool, + currentControllerSearchState: ViewController.TabBarSearchState? ) { self.layout = layout self.toolbar = toolbar self.isTabBarHidden = isTabBarHidden + self.currentControllerSearchState = currentControllerSearchState } } @@ -51,33 +49,36 @@ final class TabBarControllerNode: ASDisplayNode { } private var theme: PresentationTheme + private var strings: PresentationStrings private let itemSelected: (Int, Bool, [ASDisplayNode]) -> Void private let contextAction: (Int, ContextExtractedContentContainingView, ContextGesture) -> Void private let tabBarView = ComponentView() private let disabledOverlayNode: ASDisplayNode - private var toolbarNode: ToolbarNode? + private var toolbar: ComponentView? private let toolbarActionSelected: (ToolbarActionOption) -> Void private let disabledPressed: () -> Void + private let activateSearch: () -> Void + private let deactivateSearch: () -> Void private(set) var tabBarItems: [TabBarNodeItem] = [] private(set) var selectedIndex: Int = 0 - private(set) var currentControllerNode: ASDisplayNode? + private weak var currentController: ViewController? private var layoutResult: LayoutResult? private var isUpdateRequested: Bool = false private var isChangingSelectedIndex: Bool = false - func setCurrentControllerNode(_ node: ASDisplayNode?) -> () -> Void { - guard node !== self.currentControllerNode else { + func setCurrentController(_ controller: ViewController?) -> () -> Void { + guard controller !== self.currentController else { return {} } - let previousNode = self.currentControllerNode - self.currentControllerNode = node - if let currentControllerNode = self.currentControllerNode { + let previousNode = self.currentController?.displayNode + self.currentController = controller + if let currentControllerNode = self.currentController?.displayNode { if let previousNode { self.insertSubnode(currentControllerNode, aboveSubnode: previousNode) } else { @@ -89,14 +90,22 @@ final class TabBarControllerNode: ASDisplayNode { } return { [weak self, weak previousNode] in - if previousNode !== self?.currentControllerNode { + if previousNode !== self?.currentController?.displayNode { previousNode?.removeFromSupernode() } } } + + var currentSearchNode: ASDisplayNode? { + if let tabBarComponentView = self.tabBarView.view as? TabBarComponent.View { + return tabBarComponentView.currentSearchNode + } + return nil + } - init(theme: PresentationTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingView, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void, disabledPressed: @escaping () -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingView, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void, disabledPressed: @escaping () -> Void, activateSearch: @escaping () -> Void, deactivateSearch: @escaping () -> Void) { self.theme = theme + self.strings = strings self.itemSelected = itemSelected self.contextAction = contextAction self.disabledOverlayNode = ASDisplayNode() @@ -104,7 +113,9 @@ final class TabBarControllerNode: ASDisplayNode { self.disabledOverlayNode.alpha = 0.0 self.toolbarActionSelected = toolbarActionSelected self.disabledPressed = disabledPressed - + self.activateSearch = activateSearch + self.deactivateSearch = deactivateSearch + super.init() self.setViewBlock({ @@ -146,7 +157,6 @@ final class TabBarControllerNode: ASDisplayNode { self.backgroundColor = theme.list.plainBackgroundColor self.disabledOverlayNode.backgroundColor = theme.rootController.tabBar.backgroundColor.withAlphaComponent(0.5) - self.toolbarNode?.updateTheme(ToolbarTheme(theme: theme)) self.requestUpdate() } @@ -163,7 +173,7 @@ final class TabBarControllerNode: ASDisplayNode { } func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) -> CGFloat { - let params = Params(layout: layout, toolbar: toolbar, isTabBarHidden: self.tabBarHidden) + let params = Params(layout: layout, toolbar: toolbar, isTabBarHidden: self.tabBarHidden, currentControllerSearchState: self.currentController?.tabBarSearchState) if let layoutResult = self.layoutResult, layoutResult.params == params { return layoutResult.bottomInset } else { @@ -179,18 +189,27 @@ final class TabBarControllerNode: ASDisplayNode { } private func updateImpl(params: Params, transition: ContainedViewLayoutTransition) -> CGFloat { - var options: ContainerViewLayoutInsetOptions = [] - if params.layout.metrics.widthClass == .regular { - options.insert(.input) + var panelsBottomInset: CGFloat = params.layout.insets(options: []).bottom + if params.layout.metrics.widthClass == .regular, let inputHeight = params.layout.inputHeight, inputHeight != 0.0 { + panelsBottomInset = inputHeight + 8.0 } - - var bottomInset: CGFloat = params.layout.insets(options: options).bottom - if bottomInset == 0.0 { - bottomInset = 8.0 + if panelsBottomInset == 0.0 { + panelsBottomInset = 8.0 } else { - bottomInset = max(bottomInset, 8.0) + panelsBottomInset = max(panelsBottomInset, 8.0) + } + + var tabBarBottomInset: CGFloat = panelsBottomInset + if let currentController = self.currentController { + if let tabBarSearchState = currentController.tabBarSearchState, tabBarSearchState.isActive, let inputHeight = params.layout.inputHeight, inputHeight != 0.0 { + tabBarBottomInset = max(tabBarBottomInset, inputHeight + 8.0) + } + } + + var sideInset: CGFloat = 12.0 + if tabBarBottomInset <= 28.0 { + sideInset = 20.0 } - let sideInset: CGFloat = 20.0 var selectedId: AnyHashable? if self.selectedIndex < self.tabBarItems.count { @@ -208,6 +227,7 @@ final class TabBarControllerNode: ASDisplayNode { transition: tabBarTransition, component: AnyComponent(TabBarComponent( theme: self.theme, + strings: self.strings, items: self.tabBarItems.map { item in let itemId = AnyHashable(ObjectIdentifier(item.item)) return TabBarComponent.Item( @@ -230,13 +250,30 @@ final class TabBarControllerNode: ASDisplayNode { } ) }, + search: self.currentController?.tabBarSearchState.flatMap { tabBarSearchState in + return TabBarComponent.Search( + isActive: tabBarSearchState.isActive, + activate: { [weak self] in + guard let self else { + return + } + self.activateSearch() + }, + deactivate: { [weak self] in + guard let self else { + return + } + self.deactivateSearch() + } + ) + }, selectedId: selectedId, - isTablet: params.layout.metrics.isTablet + outerInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: tabBarBottomInset, right: sideInset) )), environment: {}, containerSize: CGSize(width: params.layout.size.width - sideInset * 2.0, height: 100.0) ) - let tabBarFrame = CGRect(origin: CGPoint(x: floor((params.layout.size.width - tabBarSize.width) * 0.5), y: params.layout.size.height - (self.tabBarHidden ? 0.0 : (tabBarSize.height + bottomInset))), size: tabBarSize) + let tabBarFrame = CGRect(origin: CGPoint(x: floor((params.layout.size.width - tabBarSize.width) * 0.5), y: params.layout.size.height - (self.tabBarHidden ? 0.0 : (tabBarSize.height + tabBarBottomInset))), size: tabBarSize) if let tabBarComponentView = self.tabBarView.view { if tabBarComponentView.superview == nil { @@ -248,34 +285,90 @@ final class TabBarControllerNode: ASDisplayNode { transition.updateFrame(node: self.disabledOverlayNode, frame: tabBarFrame) - let toolbarHeight = 50.0 + params.layout.insets(options: options).bottom - let toolbarFrame = CGRect(origin: CGPoint(x: 0.0, y: params.layout.size.height - toolbarHeight), size: CGSize(width: params.layout.size.width, height: toolbarHeight)) + let toolbarHeight = 44.0 + let toolbarFrame = CGRect(origin: CGPoint(x: sideInset, y: params.layout.size.height - panelsBottomInset - toolbarHeight), size: CGSize(width: params.layout.size.width - sideInset * 2.0, height: toolbarHeight)) - if let toolbar = params.toolbar { - if let toolbarNode = self.toolbarNode { - transition.updateFrame(node: toolbarNode, frame: toolbarFrame) - toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition) + if let toolbarData = params.toolbar { + let toolbar: ComponentView + var toolbarTransition = ComponentTransition(transition) + if let current = self.toolbar { + toolbar = current } else { - let toolbarNode = ToolbarNode(theme: ToolbarTheme(theme: self.theme), displaySeparator: true, left: { [weak self] in - self?.toolbarActionSelected(.left) - }, right: { [weak self] in - self?.toolbarActionSelected(.right) - }, middle: { [weak self] in - self?.toolbarActionSelected(.middle) - }) - toolbarNode.frame = toolbarFrame - toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate) - self.addSubnode(toolbarNode) - self.toolbarNode = toolbarNode - if transition.isAnimated { - toolbarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } + toolbar = ComponentView() + self.toolbar = toolbar + toolbarTransition = .immediate + } + + let _ = toolbar.update( + transition: toolbarTransition, + component: AnyComponent(GlassControlPanelComponent( + theme: self.theme, + leftItem: toolbarData.leftAction.flatMap { value in + return GlassControlPanelComponent.Item( + items: [GlassControlGroupComponent.Item( + id: "left_" + value.title, + content: .text(value.title), + action: value.isEnabled ? { [weak self] in + guard let self else { + return + } + self.toolbarActionSelected(.left) + } : nil + )], + background: .panel + ) + }, + centralItem: toolbarData.middleAction.flatMap { value in + return GlassControlPanelComponent.Item( + items: [GlassControlGroupComponent.Item( + id: "right_" + value.title, + content: .text(value.title), + action: value.isEnabled ? { [weak self] in + guard let self else { + return + } + self.toolbarActionSelected(.middle) + } : nil + )], + background: .panel + ) + }, + rightItem: toolbarData.rightAction.flatMap { value in + return GlassControlPanelComponent.Item( + items: [GlassControlGroupComponent.Item( + id: "right_" + value.title, + content: .text(value.title), + action: value.isEnabled ? { [weak self] in + guard let self else { + return + } + self.toolbarActionSelected(.right) + } : nil + )], + background: .panel + ) + }, + centerAlignmentIfPossible: true + )), + environment: {}, + containerSize: toolbarFrame.size + ) + + if let toolbarView = toolbar.view { + if toolbarView.superview == nil { + self.view.addSubview(toolbarView) + toolbarView.alpha = 0.0 + } + toolbarTransition.setFrame(view: toolbarView, frame: toolbarFrame) + ComponentTransition(transition).setAlpha(view: toolbarView, alpha: 1.0) + } + } else if let toolbar = self.toolbar { + self.toolbar = nil + if let toolbarView = toolbar.view { + ComponentTransition(transition).setAlpha(view: toolbarView, alpha: 0.0, completion: { [weak toolbarView] _ in + toolbarView?.removeFromSuperview() + }) } - } else if let toolbarNode = self.toolbarNode { - self.toolbarNode = nil - transition.updateAlpha(node: toolbarNode, alpha: 0.0, completion: { [weak toolbarNode] _ in - toolbarNode?.removeFromSupernode() - }) } return params.layout.size.height - tabBarFrame.minY diff --git a/submodules/TabBarUI/Sources/TabBarController.swift b/submodules/TabBarUI/Sources/TabBarController.swift index a3f7fe08..111fe208 100644 --- a/submodules/TabBarUI/Sources/TabBarController.swift +++ b/submodules/TabBarUI/Sources/TabBarController.swift @@ -93,9 +93,11 @@ open class TabBarControllerImpl: ViewController, TabBarController { private let pendingControllerDisposable = MetaDisposable() private var theme: PresentationTheme + private var strings: PresentationStrings - public init(theme: PresentationTheme) { + public init(theme: PresentationTheme, strings: PresentationStrings) { self.theme = theme + self.strings = strings super.init(navigationBarPresentationData: nil) @@ -152,7 +154,7 @@ open class TabBarControllerImpl: ViewController, TabBarController { } override open func loadDisplayNode() { - self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index, longTap, itemNodes in + self.displayNode = TabBarControllerNode(theme: self.theme, strings: self.strings, itemSelected: { [weak self] index, longTap, itemNodes in if let strongSelf = self { if longTap, let controller = strongSelf.controllers[index] as? TabBarContainedController { controller.presentTabBarPreviewingController(sourceNodes: itemNodes) @@ -235,6 +237,16 @@ open class TabBarControllerImpl: ViewController, TabBarController { self?.currentController?.toolbarActionSelected(action: action) }, disabledPressed: { [weak self] in self?.currentController?.tabBarDisabledAction() + }, activateSearch: { [weak self] in + guard let self else { + return + } + self.currentController?.tabBarActivateSearch() + }, deactivateSearch: { [weak self] in + guard let self else { + return + } + self.currentController?.tabBarDeactivateSearch() }) self.updateSelectedIndex() @@ -263,7 +275,8 @@ open class TabBarControllerImpl: ViewController, TabBarController { } if let currentController = self.currentController { currentController.willMove(toParent: nil) - //self.tabBarControllerNode.currentControllerNode = nil + currentController.tabBarSearchStateUpdated = nil + currentController.currentTabBarSearchNode = nil if animated { currentController.view.layer.animateScale(from: 1.0, to: transitionScale, duration: 0.12, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { completed in @@ -286,7 +299,7 @@ open class TabBarControllerImpl: ViewController, TabBarController { currentController.willMove(toParent: self) self.addChild(currentController) - let commit = self.tabBarControllerNode.setCurrentControllerNode(currentController.displayNode) + let commit = self.tabBarControllerNode.setCurrentController(currentController) if animated { currentController.view.layer.animateScale(from: transitionScale, to: 1.0, duration: 0.15, delay: 0.1, timingFunction: kCAMediaTimingFunctionSpring) currentController.view.layer.allowsGroupOpacity = true @@ -303,6 +316,22 @@ open class TabBarControllerImpl: ViewController, TabBarController { currentController.displayNode.recursivelyEnsureDisplaySynchronously(true) self.statusBar.statusBarStyle = currentController.statusBar.statusBarStyle + + currentController.tabBarSearchStateUpdated = { [weak self] transition in + guard let self else { + return + } + if let layout = self.validLayout { + self.containerLayoutUpdated(layout, transition: transition) + } + } + + currentController.currentTabBarSearchNode = { [weak self] in + guard let self else { + return nil + } + return self.tabBarControllerNode.currentSearchNode + } } if let layout = self.validLayout { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 1f0fce87..455ff48a 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -417,6 +417,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1279654347] = { return Api.InputMedia.parse_inputMediaPhoto($0) } dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) } dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) } + dict[-207018934] = { return Api.InputMedia.parse_inputMediaStakeDice($0) } dict[-1979852936] = { return Api.InputMedia.parse_inputMediaStory($0) } dict[-1614454818] = { return Api.InputMedia.parse_inputMediaTodo($0) } dict[58495792] = { return Api.InputMedia.parse_inputMediaUploadedDocument($0) } @@ -432,6 +433,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1548122514] = { return Api.InputNotifyPeer.parse_inputNotifyForumTopic($0) } dict[-1195615476] = { return Api.InputNotifyPeer.parse_inputNotifyPeer($0) } dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) } + dict[1528613672] = { return Api.InputPasskeyCredential.parse_inputPasskeyCredentialFirebasePNV($0) } dict[1009235855] = { return Api.InputPasskeyCredential.parse_inputPasskeyCredentialPublicKey($0) } dict[-1021329078] = { return Api.InputPasskeyResponse.parse_inputPasskeyResponseLogin($0) } dict[1046713180] = { return Api.InputPasskeyResponse.parse_inputPasskeyResponseRegister($0) } @@ -572,7 +574,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[1235637404] = { return Api.MediaArea.parse_mediaAreaWeather($0) } dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) } - dict[-1188071729] = { return Api.Message.parse_message($0) } + dict[-1665888023] = { return Api.Message.parse_message($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[2055212554] = { return Api.Message.parse_messageService($0) } dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) } @@ -660,7 +662,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1386050360] = { return Api.MessageExtendedMedia.parse_messageExtendedMediaPreview($0) } dict[1313731771] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) } dict[1882335561] = { return Api.MessageMedia.parse_messageMediaContact($0) } - dict[1065280907] = { return Api.MessageMedia.parse_messageMediaDice($0) } + dict[147581959] = { return Api.MessageMedia.parse_messageMediaDice($0) } dict[1389939929] = { return Api.MessageMedia.parse_messageMediaDocument($0) } dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) } dict[-38694904] = { return Api.MessageMedia.parse_messageMediaGame($0) } @@ -1127,6 +1129,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-302247650] = { return Api.Update.parse_updateDraftMessage($0) } dict[457133559] = { return Api.Update.parse_updateEditChannelMessage($0) } dict[-469536605] = { return Api.Update.parse_updateEditMessage($0) } + dict[-73640838] = { return Api.Update.parse_updateEmojiGameInfo($0) } dict[386986326] = { return Api.Update.parse_updateEncryptedChatTyping($0) } dict[956179895] = { return Api.Update.parse_updateEncryptedMessagesRead($0) } dict[-1264392051] = { return Api.Update.parse_updateEncryption($0) } @@ -1417,6 +1420,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-253500010] = { return Api.messages.Dialogs.parse_dialogsNotModified($0) } dict[1910543603] = { return Api.messages.Dialogs.parse_dialogsSlice($0) } dict[-1506535550] = { return Api.messages.DiscussionMessage.parse_discussionMessage($0) } + dict[1155883043] = { return Api.messages.EmojiGameInfo.parse_emojiGameDiceInfo($0) } + dict[1508266805] = { return Api.messages.EmojiGameInfo.parse_emojiGameUnavailable($0) } + dict[-634726841] = { return Api.messages.EmojiGameOutcome.parse_emojiGameOutcome($0) } dict[-2011186869] = { return Api.messages.EmojiGroups.parse_emojiGroups($0) } dict[1874111879] = { return Api.messages.EmojiGroups.parse_emojiGroupsNotModified($0) } dict[410107472] = { return Api.messages.ExportedChatInvite.parse_exportedChatInvite($0) } @@ -2573,6 +2579,10 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.DiscussionMessage: _1.serialize(buffer, boxed) + case let _1 as Api.messages.EmojiGameInfo: + _1.serialize(buffer, boxed) + case let _1 as Api.messages.EmojiGameOutcome: + _1.serialize(buffer, boxed) case let _1 as Api.messages.EmojiGroups: _1.serialize(buffer, boxed) case let _1 as Api.messages.ExportedChatInvite: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 2177c01e..1056c070 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -24,12 +24,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.AccountDaysTTL.accountDaysTTL(days: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.AccountDaysTTL.accountDaysTTL(days: _1!) } } @@ -88,12 +84,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.AttachMenuBot.attachMenuBot(flags: _1!, botId: _2!, shortName: _3!, peerTypes: _4, icons: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.AttachMenuBot.attachMenuBot(flags: _1!, botId: _2!, shortName: _3!, peerTypes: _4, icons: _5!) } } @@ -144,12 +140,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.AttachMenuBotIcon.attachMenuBotIcon(flags: _1!, name: _2!, icon: _3!, colors: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.AttachMenuBotIcon.attachMenuBotIcon(flags: _1!, name: _2!, icon: _3!, colors: _4) } } @@ -184,12 +179,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.AttachMenuBotIconColor.attachMenuBotIconColor(name: _1!, color: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.AttachMenuBotIconColor.attachMenuBotIconColor(name: _1!, color: _2!) } } @@ -249,12 +241,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.AttachMenuBots.attachMenuBots(hash: _1!, bots: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.AttachMenuBots.attachMenuBots(hash: _1!, bots: _2!, users: _3!) } public static func parse_attachMenuBotsNotModified(_ reader: BufferReader) -> AttachMenuBots? { return Api.AttachMenuBots.attachMenuBotsNotModified @@ -300,12 +290,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.AttachMenuBotsBot.attachMenuBotsBot(bot: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.AttachMenuBotsBot.attachMenuBotsBot(bot: _1!, users: _2!) } } @@ -420,12 +407,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.AuctionBidLevel.auctionBidLevel(pos: _1!, amount: _2!, date: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.AuctionBidLevel.auctionBidLevel(pos: _1!, amount: _2!, date: _3!) } } @@ -504,12 +489,20 @@ public extension Api { let _c11 = _11 != nil let _c12 = _12 != nil let _c13 = _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.Authorization.authorization(flags: _1!, hash: _2!, deviceModel: _3!, platform: _4!, systemVersion: _5!, apiId: _6!, appName: _7!, appVersion: _8!, dateCreated: _9!, dateActive: _10!, ip: _11!, country: _12!, region: _13!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + return Api.Authorization.authorization(flags: _1!, hash: _2!, deviceModel: _3!, platform: _4!, systemVersion: _5!, apiId: _6!, appName: _7!, appVersion: _8!, dateCreated: _9!, dateActive: _10!, ip: _11!, country: _12!, region: _13!) } } @@ -564,12 +557,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.AutoDownloadSettings.autoDownloadSettings(flags: _1!, photoSizeMax: _2!, videoSizeMax: _3!, fileSizeMax: _4!, videoUploadMaxbitrate: _5!, smallQueueActiveOperationsMax: _6!, largeQueueActiveOperationsMax: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.AutoDownloadSettings.autoDownloadSettings(flags: _1!, photoSizeMax: _2!, videoSizeMax: _3!, fileSizeMax: _4!, videoUploadMaxbitrate: _5!, smallQueueActiveOperationsMax: _6!, largeQueueActiveOperationsMax: _7!) } } @@ -608,12 +603,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.AutoSaveException.autoSaveException(peer: _1!, settings: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.AutoSaveException.autoSaveException(peer: _1!, settings: _2!) } } @@ -648,12 +640,9 @@ public extension Api { if Int(_1!) & Int(1 << 2) != 0 {_2 = reader.readInt64() } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil - if _c1 && _c2 { - return Api.AutoSaveSettings.autoSaveSettings(flags: _1!, videoMaxSize: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.AutoSaveSettings.autoSaveSettings(flags: _1!, videoMaxSize: _2) } } @@ -704,12 +693,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.AvailableEffect.availableEffect(flags: _1!, id: _2!, emoticon: _3!, staticIconId: _4, effectStickerId: _5!, effectAnimationId: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.AvailableEffect.availableEffect(flags: _1!, id: _2!, emoticon: _3!, staticIconId: _4, effectStickerId: _5!, effectAnimationId: _6) } } @@ -790,12 +780,17 @@ public extension Api { let _c8 = _8 != nil let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.AvailableReaction.availableReaction(flags: _1!, reaction: _2!, title: _3!, staticIcon: _4!, appearAnimation: _5!, selectAnimation: _6!, activateAnimation: _7!, effectAnimation: _8!, aroundAnimation: _9, centerIcon: _10) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + return Api.AvailableReaction.availableReaction(flags: _1!, reaction: _2!, title: _3!, staticIcon: _4!, appearAnimation: _5!, selectAnimation: _6!, activateAnimation: _7!, effectAnimation: _8!, aroundAnimation: _9, centerIcon: _10) } } @@ -830,12 +825,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.BankCardOpenUrl.bankCardOpenUrl(url: _1!, name: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.BankCardOpenUrl.bankCardOpenUrl(url: _1!, name: _2!) } } @@ -954,12 +946,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Birthday.birthday(flags: _1!, day: _2!, month: _3!, year: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Birthday.birthday(flags: _1!, day: _2!, month: _3!, year: _4) } } @@ -1062,12 +1053,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Boost.boost(flags: _1!, id: _2!, userId: _3, giveawayMsgId: _4, date: _5!, expires: _6!, usedGiftSlug: _7, multiplier: _8, stars: _9) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.Boost.boost(flags: _1!, id: _2!, userId: _3, giveawayMsgId: _4, date: _5!, expires: _6!, usedGiftSlug: _7, multiplier: _8, stars: _9) } } @@ -1143,12 +1138,16 @@ public extension Api { let _c7 = _7 != nil let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.BotApp.botApp(flags: _1!, id: _2!, accessHash: _3!, shortName: _4!, title: _5!, description: _6!, photo: _7!, document: _8, hash: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.BotApp.botApp(flags: _1!, id: _2!, accessHash: _3!, shortName: _4!, title: _5!, description: _6!, photo: _7!, document: _8, hash: _9!) } public static func parse_botAppNotModified(_ reader: BufferReader) -> BotApp? { return Api.BotApp.botAppNotModified @@ -1202,12 +1201,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.BotAppSettings.botAppSettings(flags: _1!, placeholderPath: _2, backgroundColor: _3, backgroundDarkColor: _4, headerColor: _5, headerDarkColor: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.BotAppSettings.botAppSettings(flags: _1!, placeholderPath: _2, backgroundColor: _3, backgroundDarkColor: _4, headerColor: _5, headerDarkColor: _6) } } diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index 6da06977..08d007a9 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -138,12 +138,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputFileLocation.inputDocumentFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, thumbSize: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputFileLocation.inputDocumentFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, thumbSize: _4!) } public static func parse_inputEncryptedFileLocation(_ reader: BufferReader) -> InputFileLocation? { var _1: Int64? @@ -152,12 +151,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputFileLocation.inputEncryptedFileLocation(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputFileLocation.inputEncryptedFileLocation(id: _1!, accessHash: _2!) } public static func parse_inputFileLocation(_ reader: BufferReader) -> InputFileLocation? { var _1: Int64? @@ -172,12 +168,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputFileLocation.inputFileLocation(volumeId: _1!, localId: _2!, secret: _3!, fileReference: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputFileLocation.inputFileLocation(volumeId: _1!, localId: _2!, secret: _3!, fileReference: _4!) } public static func parse_inputGroupCallStream(_ reader: BufferReader) -> InputFileLocation? { var _1: Int32? @@ -200,12 +195,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputFileLocation.inputGroupCallStream(flags: _1!, call: _2!, timeMs: _3!, scale: _4!, videoChannel: _5, videoQuality: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.InputFileLocation.inputGroupCallStream(flags: _1!, call: _2!, timeMs: _3!, scale: _4!, videoChannel: _5, videoQuality: _6) } public static func parse_inputPeerPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? { var _1: Int32? @@ -219,12 +215,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputFileLocation.inputPeerPhotoFileLocation(flags: _1!, peer: _2!, photoId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputFileLocation.inputPeerPhotoFileLocation(flags: _1!, peer: _2!, photoId: _3!) } public static func parse_inputPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? { var _1: Int64? @@ -239,12 +233,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputFileLocation.inputPhotoFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, thumbSize: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputFileLocation.inputPhotoFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, thumbSize: _4!) } public static func parse_inputPhotoLegacyFileLocation(_ reader: BufferReader) -> InputFileLocation? { var _1: Int64? @@ -265,12 +258,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputFileLocation.inputPhotoLegacyFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, volumeId: _4!, localId: _5!, secret: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.InputFileLocation.inputPhotoLegacyFileLocation(id: _1!, accessHash: _2!, fileReference: _3!, volumeId: _4!, localId: _5!, secret: _6!) } public static func parse_inputSecureFileLocation(_ reader: BufferReader) -> InputFileLocation? { var _1: Int64? @@ -279,12 +273,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputFileLocation.inputSecureFileLocation(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputFileLocation.inputSecureFileLocation(id: _1!, accessHash: _2!) } public static func parse_inputStickerSetThumb(_ reader: BufferReader) -> InputFileLocation? { var _1: Api.InputStickerSet? @@ -295,12 +286,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputFileLocation.inputStickerSetThumb(stickerset: _1!, thumbVersion: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputFileLocation.inputStickerSetThumb(stickerset: _1!, thumbVersion: _2!) } public static func parse_inputTakeoutFileLocation(_ reader: BufferReader) -> InputFileLocation? { return Api.InputFileLocation.inputTakeoutFileLocation @@ -340,12 +328,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputFolderPeer.inputFolderPeer(peer: _1!, folderId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputFolderPeer.inputFolderPeer(peer: _1!, folderId: _2!) } } @@ -390,12 +375,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputGame.inputGameID(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputGame.inputGameID(id: _1!, accessHash: _2!) } public static func parse_inputGameShortName(_ reader: BufferReader) -> InputGame? { var _1: Api.InputUser? @@ -406,12 +388,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputGame.inputGameShortName(botId: _1!, shortName: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputGame.inputGameShortName(botId: _1!, shortName: _2!) } } @@ -463,12 +442,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputGeoPoint.inputGeoPoint(flags: _1!, lat: _2!, long: _3!, accuracyRadius: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputGeoPoint.inputGeoPoint(flags: _1!, lat: _2!, long: _3!, accuracyRadius: _4) } public static func parse_inputGeoPointEmpty(_ reader: BufferReader) -> InputGeoPoint? { return Api.InputGeoPoint.inputGeoPointEmpty @@ -524,34 +502,23 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputGroupCall.inputGroupCall(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputGroupCall.inputGroupCall(id: _1!, accessHash: _2!) } public static func parse_inputGroupCallInviteMessage(_ reader: BufferReader) -> InputGroupCall? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.InputGroupCall.inputGroupCallInviteMessage(msgId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputGroupCall.inputGroupCallInviteMessage(msgId: _1!) } public static func parse_inputGroupCallSlug(_ reader: BufferReader) -> InputGroupCall? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputGroupCall.inputGroupCallSlug(slug: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputGroupCall.inputGroupCallSlug(slug: _1!) } } @@ -731,23 +698,16 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputInvoice.inputInvoiceBusinessBotTransferStars(bot: _1!, stars: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputInvoice.inputInvoiceBusinessBotTransferStars(bot: _1!, stars: _2!) } public static func parse_inputInvoiceChatInviteSubscription(_ reader: BufferReader) -> InputInvoice? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputInvoice.inputInvoiceChatInviteSubscription(hash: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputInvoice.inputInvoiceChatInviteSubscription(hash: _1!) } public static func parse_inputInvoiceMessage(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputPeer? @@ -758,12 +718,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputInvoice.inputInvoiceMessage(peer: _1!, msgId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputInvoice.inputInvoiceMessage(peer: _1!, msgId: _2!) } public static func parse_inputInvoicePremiumAuthCode(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputStorePaymentPurpose? @@ -771,12 +728,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputStorePaymentPurpose } let _c1 = _1 != nil - if _c1 { - return Api.InputInvoice.inputInvoicePremiumAuthCode(purpose: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputInvoice.inputInvoicePremiumAuthCode(purpose: _1!) } public static func parse_inputInvoicePremiumGiftCode(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputStorePaymentPurpose? @@ -789,12 +742,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputInvoice.inputInvoicePremiumGiftCode(purpose: _1!, option: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputInvoice.inputInvoicePremiumGiftCode(purpose: _1!, option: _2!) } public static func parse_inputInvoicePremiumGiftStars(_ reader: BufferReader) -> InputInvoice? { var _1: Int32? @@ -813,23 +763,18 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputInvoice.inputInvoicePremiumGiftStars(flags: _1!, userId: _2!, months: _3!, message: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputInvoice.inputInvoicePremiumGiftStars(flags: _1!, userId: _2!, months: _3!, message: _4) } public static func parse_inputInvoiceSlug(_ reader: BufferReader) -> InputInvoice? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputInvoice.inputInvoiceSlug(slug: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputInvoice.inputInvoiceSlug(slug: _1!) } public static func parse_inputInvoiceStarGift(_ reader: BufferReader) -> InputInvoice? { var _1: Int32? @@ -848,12 +793,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputInvoice.inputInvoiceStarGift(flags: _1!, peer: _2!, giftId: _3!, message: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputInvoice.inputInvoiceStarGift(flags: _1!, peer: _2!, giftId: _3!, message: _4) } public static func parse_inputInvoiceStarGiftAuctionBid(_ reader: BufferReader) -> InputInvoice? { var _1: Int32? @@ -875,12 +819,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputInvoice.inputInvoiceStarGiftAuctionBid(flags: _1!, peer: _2, giftId: _3!, bidAmount: _4!, message: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputInvoice.inputInvoiceStarGiftAuctionBid(flags: _1!, peer: _2, giftId: _3!, bidAmount: _4!, message: _5) } public static func parse_inputInvoiceStarGiftDropOriginalDetails(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputSavedStarGift? @@ -888,12 +832,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputSavedStarGift } let _c1 = _1 != nil - if _c1 { - return Api.InputInvoice.inputInvoiceStarGiftDropOriginalDetails(stargift: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputInvoice.inputInvoiceStarGiftDropOriginalDetails(stargift: _1!) } public static func parse_inputInvoiceStarGiftPrepaidUpgrade(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputPeer? @@ -904,12 +844,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputInvoice.inputInvoiceStarGiftPrepaidUpgrade(peer: _1!, hash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputInvoice.inputInvoiceStarGiftPrepaidUpgrade(peer: _1!, hash: _2!) } public static func parse_inputInvoiceStarGiftResale(_ reader: BufferReader) -> InputInvoice? { var _1: Int32? @@ -923,12 +860,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputInvoice.inputInvoiceStarGiftResale(flags: _1!, slug: _2!, toId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputInvoice.inputInvoiceStarGiftResale(flags: _1!, slug: _2!, toId: _3!) } public static func parse_inputInvoiceStarGiftTransfer(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputSavedStarGift? @@ -941,12 +876,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputInvoice.inputInvoiceStarGiftTransfer(stargift: _1!, toId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputInvoice.inputInvoiceStarGiftTransfer(stargift: _1!, toId: _2!) } public static func parse_inputInvoiceStarGiftUpgrade(_ reader: BufferReader) -> InputInvoice? { var _1: Int32? @@ -957,12 +889,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputInvoice.inputInvoiceStarGiftUpgrade(flags: _1!, stargift: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputInvoice.inputInvoiceStarGiftUpgrade(flags: _1!, stargift: _2!) } public static func parse_inputInvoiceStars(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputStorePaymentPurpose? @@ -970,12 +899,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputStorePaymentPurpose } let _c1 = _1 != nil - if _c1 { - return Api.InputInvoice.inputInvoiceStars(purpose: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputInvoice.inputInvoiceStars(purpose: _1!) } } diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index 4914c64e..f44bd4e7 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -13,6 +13,7 @@ public extension Api { case inputMediaPhoto(flags: Int32, id: Api.InputPhoto, ttlSeconds: Int32?) case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?) case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?) + case inputMediaStakeDice(gameHash: String, tonAmount: Int64, clientSeed: Buffer) case inputMediaStory(peer: Api.InputPeer, id: Int32) case inputMediaTodo(todo: Api.TodoList) case inputMediaUploadedDocument(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, mimeType: String, attributes: [Api.DocumentAttribute], stickers: [Api.InputDocument]?, videoCover: Api.InputPhoto?, videoTimestamp: Int32?, ttlSeconds: Int32?) @@ -148,6 +149,14 @@ public extension Api { item.serialize(buffer, true) }} break + case .inputMediaStakeDice(let gameHash, let tonAmount, let clientSeed): + if boxed { + buffer.appendInt32(-207018934) + } + serializeString(gameHash, buffer: buffer, boxed: false) + serializeInt64(tonAmount, buffer: buffer, boxed: false) + serializeBytes(clientSeed, buffer: buffer, boxed: false) + break case .inputMediaStory(let peer, let id): if boxed { buffer.appendInt32(-1979852936) @@ -245,6 +254,8 @@ public extension Api { return ("inputMediaPhotoExternal", [("flags", flags as Any), ("url", url as Any), ("ttlSeconds", ttlSeconds as Any)]) case .inputMediaPoll(let flags, let poll, let correctAnswers, let solution, let solutionEntities): return ("inputMediaPoll", [("flags", flags as Any), ("poll", poll as Any), ("correctAnswers", correctAnswers as Any), ("solution", solution as Any), ("solutionEntities", solutionEntities as Any)]) + case .inputMediaStakeDice(let gameHash, let tonAmount, let clientSeed): + return ("inputMediaStakeDice", [("gameHash", gameHash as Any), ("tonAmount", tonAmount as Any), ("clientSeed", clientSeed as Any)]) case .inputMediaStory(let peer, let id): return ("inputMediaStory", [("peer", peer as Any), ("id", id as Any)]) case .inputMediaTodo(let todo): @@ -273,23 +284,18 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputMedia.inputMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, vcard: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputMedia.inputMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, vcard: _4!) } public static func parse_inputMediaDice(_ reader: BufferReader) -> InputMedia? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputMedia.inputMediaDice(emoticon: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputMedia.inputMediaDice(emoticon: _1!) } public static func parse_inputMediaDocument(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -314,12 +320,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputMedia.inputMediaDocument(flags: _1!, id: _2!, videoCover: _3, videoTimestamp: _4, ttlSeconds: _5, query: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.InputMedia.inputMediaDocument(flags: _1!, id: _2!, videoCover: _3, videoTimestamp: _4, ttlSeconds: _5, query: _6) } public static func parse_inputMediaDocumentExternal(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -339,12 +346,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputMedia.inputMediaDocumentExternal(flags: _1!, url: _2!, ttlSeconds: _3, videoCover: _4, videoTimestamp: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputMedia.inputMediaDocumentExternal(flags: _1!, url: _2!, ttlSeconds: _3, videoCover: _4, videoTimestamp: _5) } public static func parse_inputMediaEmpty(_ reader: BufferReader) -> InputMedia? { return Api.InputMedia.inputMediaEmpty @@ -355,12 +362,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputGame } let _c1 = _1 != nil - if _c1 { - return Api.InputMedia.inputMediaGame(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputMedia.inputMediaGame(id: _1!) } public static func parse_inputMediaGeoLive(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -380,12 +383,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputMedia.inputMediaGeoLive(flags: _1!, geoPoint: _2!, heading: _3, period: _4, proximityNotificationRadius: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputMedia.inputMediaGeoLive(flags: _1!, geoPoint: _2!, heading: _3, period: _4, proximityNotificationRadius: _5) } public static func parse_inputMediaGeoPoint(_ reader: BufferReader) -> InputMedia? { var _1: Api.InputGeoPoint? @@ -393,12 +396,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint } let _c1 = _1 != nil - if _c1 { - return Api.InputMedia.inputMediaGeoPoint(geoPoint: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputMedia.inputMediaGeoPoint(geoPoint: _1!) } public static func parse_inputMediaInvoice(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -439,12 +438,17 @@ public extension Api { let _c8 = _8 != nil let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.InputMedia.inputMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, invoice: _5!, payload: _6!, provider: _7, providerData: _8!, startParam: _9, extendedMedia: _10) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + return Api.InputMedia.inputMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, invoice: _5!, payload: _6!, provider: _7, providerData: _8!, startParam: _9, extendedMedia: _10) } public static func parse_inputMediaPaidMedia(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -461,12 +465,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputMedia.inputMediaPaidMedia(flags: _1!, starsAmount: _2!, extendedMedia: _3!, payload: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputMedia.inputMediaPaidMedia(flags: _1!, starsAmount: _2!, extendedMedia: _3!, payload: _4) } public static func parse_inputMediaPhoto(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -480,12 +483,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputMedia.inputMediaPhoto(flags: _1!, id: _2!, ttlSeconds: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputMedia.inputMediaPhoto(flags: _1!, id: _2!, ttlSeconds: _3) } public static func parse_inputMediaPhotoExternal(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -497,12 +498,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputMedia.inputMediaPhotoExternal(flags: _1!, url: _2!, ttlSeconds: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputMedia.inputMediaPhotoExternal(flags: _1!, url: _2!, ttlSeconds: _3) } public static func parse_inputMediaPoll(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -526,12 +525,27 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputMedia.inputMediaPoll(flags: _1!, poll: _2!, correctAnswers: _3, solution: _4, solutionEntities: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputMedia.inputMediaPoll(flags: _1!, poll: _2!, correctAnswers: _3, solution: _4, solutionEntities: _5) + } + public static func parse_inputMediaStakeDice(_ reader: BufferReader) -> InputMedia? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputMedia.inputMediaStakeDice(gameHash: _1!, tonAmount: _2!, clientSeed: _3!) } public static func parse_inputMediaStory(_ reader: BufferReader) -> InputMedia? { var _1: Api.InputPeer? @@ -542,12 +556,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputMedia.inputMediaStory(peer: _1!, id: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputMedia.inputMediaStory(peer: _1!, id: _2!) } public static func parse_inputMediaTodo(_ reader: BufferReader) -> InputMedia? { var _1: Api.TodoList? @@ -555,12 +566,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.TodoList } let _c1 = _1 != nil - if _c1 { - return Api.InputMedia.inputMediaTodo(todo: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputMedia.inputMediaTodo(todo: _1!) } public static func parse_inputMediaUploadedDocument(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -600,12 +607,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.InputMedia.inputMediaUploadedDocument(flags: _1!, file: _2!, thumb: _3, mimeType: _4!, attributes: _5!, stickers: _6, videoCover: _7, videoTimestamp: _8, ttlSeconds: _9) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.InputMedia.inputMediaUploadedDocument(flags: _1!, file: _2!, thumb: _3, mimeType: _4!, attributes: _5!, stickers: _6, videoCover: _7, videoTimestamp: _8, ttlSeconds: _9) } public static func parse_inputMediaUploadedPhoto(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -624,12 +635,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputMedia.inputMediaUploadedPhoto(flags: _1!, file: _2!, stickers: _3, ttlSeconds: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputMedia.inputMediaUploadedPhoto(flags: _1!, file: _2!, stickers: _3, ttlSeconds: _4) } public static func parse_inputMediaVenue(_ reader: BufferReader) -> InputMedia? { var _1: Api.InputGeoPoint? @@ -652,12 +662,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputMedia.inputMediaVenue(geoPoint: _1!, title: _2!, address: _3!, provider: _4!, venueId: _5!, venueType: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.InputMedia.inputMediaVenue(geoPoint: _1!, title: _2!, address: _3!, provider: _4!, venueId: _5!, venueType: _6!) } public static func parse_inputMediaWebPage(_ reader: BufferReader) -> InputMedia? { var _1: Int32? @@ -666,12 +677,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputMedia.inputMediaWebPage(flags: _1!, url: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputMedia.inputMediaWebPage(flags: _1!, url: _2!) } } @@ -733,23 +741,16 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputMessage.inputMessageCallbackQuery(id: _1!, queryId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputMessage.inputMessageCallbackQuery(id: _1!, queryId: _2!) } public static func parse_inputMessageID(_ reader: BufferReader) -> InputMessage? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.InputMessage.inputMessageID(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputMessage.inputMessageID(id: _1!) } public static func parse_inputMessagePinned(_ reader: BufferReader) -> InputMessage? { return Api.InputMessage.inputMessagePinned @@ -758,12 +759,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.InputMessage.inputMessageReplyTo(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputMessage.inputMessageReplyTo(id: _1!) } } @@ -842,12 +839,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputNotifyPeer.inputNotifyForumTopic(peer: _1!, topMsgId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputNotifyPeer.inputNotifyForumTopic(peer: _1!, topMsgId: _2!) } public static func parse_inputNotifyPeer(_ reader: BufferReader) -> InputNotifyPeer? { var _1: Api.InputPeer? @@ -855,12 +849,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputPeer } let _c1 = _1 != nil - if _c1 { - return Api.InputNotifyPeer.inputNotifyPeer(peer: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputNotifyPeer.inputNotifyPeer(peer: _1!) } public static func parse_inputNotifyUsers(_ reader: BufferReader) -> InputNotifyPeer? { return Api.InputNotifyPeer.inputNotifyUsers @@ -870,10 +860,17 @@ public extension Api { } public extension Api { enum InputPasskeyCredential: TypeConstructorDescription { + case inputPasskeyCredentialFirebasePNV(pnvToken: String) case inputPasskeyCredentialPublicKey(id: String, rawId: String, response: Api.InputPasskeyResponse) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { + case .inputPasskeyCredentialFirebasePNV(let pnvToken): + if boxed { + buffer.appendInt32(1528613672) + } + serializeString(pnvToken, buffer: buffer, boxed: false) + break case .inputPasskeyCredentialPublicKey(let id, let rawId, let response): if boxed { buffer.appendInt32(1009235855) @@ -887,11 +884,20 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { + case .inputPasskeyCredentialFirebasePNV(let pnvToken): + return ("inputPasskeyCredentialFirebasePNV", [("pnvToken", pnvToken as Any)]) case .inputPasskeyCredentialPublicKey(let id, let rawId, let response): return ("inputPasskeyCredentialPublicKey", [("id", id as Any), ("rawId", rawId as Any), ("response", response as Any)]) } } + public static func parse_inputPasskeyCredentialFirebasePNV(_ reader: BufferReader) -> InputPasskeyCredential? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if !_c1 { return nil } + return Api.InputPasskeyCredential.inputPasskeyCredentialFirebasePNV(pnvToken: _1!) + } public static func parse_inputPasskeyCredentialPublicKey(_ reader: BufferReader) -> InputPasskeyCredential? { var _1: String? _1 = parseString(reader) @@ -904,12 +910,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputPasskeyCredential.inputPasskeyCredentialPublicKey(id: _1!, rawId: _2!, response: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputPasskeyCredential.inputPasskeyCredentialPublicKey(id: _1!, rawId: _2!, response: _3!) } } @@ -964,12 +968,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputPasskeyResponse.inputPasskeyResponseLogin(clientData: _1!, authenticatorData: _2!, signature: _3!, userHandle: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputPasskeyResponse.inputPasskeyResponseLogin(clientData: _1!, authenticatorData: _2!, signature: _3!, userHandle: _4!) } public static func parse_inputPasskeyResponseRegister(_ reader: BufferReader) -> InputPasskeyResponse? { var _1: Api.DataJSON? @@ -980,122 +983,9 @@ public extension Api { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputPasskeyResponse.inputPasskeyResponseRegister(clientData: _1!, attestationData: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum InputPaymentCredentials: TypeConstructorDescription { - case inputPaymentCredentials(flags: Int32, data: Api.DataJSON) - case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON) - case inputPaymentCredentialsGooglePay(paymentToken: Api.DataJSON) - case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputPaymentCredentials(let flags, let data): - if boxed { - buffer.appendInt32(873977640) - } - serializeInt32(flags, buffer: buffer, boxed: false) - data.serialize(buffer, true) - break - case .inputPaymentCredentialsApplePay(let paymentData): - if boxed { - buffer.appendInt32(178373535) - } - paymentData.serialize(buffer, true) - break - case .inputPaymentCredentialsGooglePay(let paymentToken): - if boxed { - buffer.appendInt32(-1966921727) - } - paymentToken.serialize(buffer, true) - break - case .inputPaymentCredentialsSaved(let id, let tmpPassword): - if boxed { - buffer.appendInt32(-1056001329) - } - serializeString(id, buffer: buffer, boxed: false) - serializeBytes(tmpPassword, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputPaymentCredentials(let flags, let data): - return ("inputPaymentCredentials", [("flags", flags as Any), ("data", data as Any)]) - case .inputPaymentCredentialsApplePay(let paymentData): - return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData as Any)]) - case .inputPaymentCredentialsGooglePay(let paymentToken): - return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken as Any)]) - case .inputPaymentCredentialsSaved(let id, let tmpPassword): - return ("inputPaymentCredentialsSaved", [("id", id as Any), ("tmpPassword", tmpPassword as Any)]) - } - } - - public static func parse_inputPaymentCredentials(_ reader: BufferReader) -> InputPaymentCredentials? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.DataJSON? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputPaymentCredentials.inputPaymentCredentials(flags: _1!, data: _2!) - } - else { - return nil - } - } - public static func parse_inputPaymentCredentialsApplePay(_ reader: BufferReader) -> InputPaymentCredentials? { - var _1: Api.DataJSON? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - let _c1 = _1 != nil - if _c1 { - return Api.InputPaymentCredentials.inputPaymentCredentialsApplePay(paymentData: _1!) - } - else { - return nil - } - } - public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? { - var _1: Api.DataJSON? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - let _c1 = _1 != nil - if _c1 { - return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!) - } - else { - return nil - } - } - public static func parse_inputPaymentCredentialsSaved(_ reader: BufferReader) -> InputPaymentCredentials? { - var _1: String? - _1 = parseString(reader) - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputPaymentCredentials.inputPaymentCredentialsSaved(id: _1!, tmpPassword: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputPasskeyResponse.inputPasskeyResponseRegister(clientData: _1!, attestationData: _2!) } } diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index ae88c149..42fc616a 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -1,3 +1,99 @@ +public extension Api { + enum InputPaymentCredentials: TypeConstructorDescription { + case inputPaymentCredentials(flags: Int32, data: Api.DataJSON) + case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON) + case inputPaymentCredentialsGooglePay(paymentToken: Api.DataJSON) + case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputPaymentCredentials(let flags, let data): + if boxed { + buffer.appendInt32(873977640) + } + serializeInt32(flags, buffer: buffer, boxed: false) + data.serialize(buffer, true) + break + case .inputPaymentCredentialsApplePay(let paymentData): + if boxed { + buffer.appendInt32(178373535) + } + paymentData.serialize(buffer, true) + break + case .inputPaymentCredentialsGooglePay(let paymentToken): + if boxed { + buffer.appendInt32(-1966921727) + } + paymentToken.serialize(buffer, true) + break + case .inputPaymentCredentialsSaved(let id, let tmpPassword): + if boxed { + buffer.appendInt32(-1056001329) + } + serializeString(id, buffer: buffer, boxed: false) + serializeBytes(tmpPassword, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputPaymentCredentials(let flags, let data): + return ("inputPaymentCredentials", [("flags", flags as Any), ("data", data as Any)]) + case .inputPaymentCredentialsApplePay(let paymentData): + return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData as Any)]) + case .inputPaymentCredentialsGooglePay(let paymentToken): + return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken as Any)]) + case .inputPaymentCredentialsSaved(let id, let tmpPassword): + return ("inputPaymentCredentialsSaved", [("id", id as Any), ("tmpPassword", tmpPassword as Any)]) + } + } + + public static func parse_inputPaymentCredentials(_ reader: BufferReader) -> InputPaymentCredentials? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DataJSON? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputPaymentCredentials.inputPaymentCredentials(flags: _1!, data: _2!) + } + public static func parse_inputPaymentCredentialsApplePay(_ reader: BufferReader) -> InputPaymentCredentials? { + var _1: Api.DataJSON? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + let _c1 = _1 != nil + if !_c1 { return nil } + return Api.InputPaymentCredentials.inputPaymentCredentialsApplePay(paymentData: _1!) + } + public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? { + var _1: Api.DataJSON? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + let _c1 = _1 != nil + if !_c1 { return nil } + return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!) + } + public static func parse_inputPaymentCredentialsSaved(_ reader: BufferReader) -> InputPaymentCredentials? { + var _1: String? + _1 = parseString(reader) + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputPaymentCredentials.inputPaymentCredentialsSaved(id: _1!, tmpPassword: _2!) + } + + } +} public extension Api { indirect enum InputPeer: TypeConstructorDescription { case inputPeerChannel(channelId: Int64, accessHash: Int64) @@ -87,12 +183,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputPeer.inputPeerChannel(channelId: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputPeer.inputPeerChannel(channelId: _1!, accessHash: _2!) } public static func parse_inputPeerChannelFromMessage(_ reader: BufferReader) -> InputPeer? { var _1: Api.InputPeer? @@ -106,23 +199,17 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputPeer.inputPeerChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputPeer.inputPeerChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!) } public static func parse_inputPeerChat(_ reader: BufferReader) -> InputPeer? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.InputPeer.inputPeerChat(chatId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputPeer.inputPeerChat(chatId: _1!) } public static func parse_inputPeerEmpty(_ reader: BufferReader) -> InputPeer? { return Api.InputPeer.inputPeerEmpty @@ -137,12 +224,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputPeer.inputPeerUser(userId: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputPeer.inputPeerUser(userId: _1!, accessHash: _2!) } public static func parse_inputPeerUserFromMessage(_ reader: BufferReader) -> InputPeer? { var _1: Api.InputPeer? @@ -156,12 +240,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputPeer.inputPeerUserFromMessage(peer: _1!, msgId: _2!, userId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputPeer.inputPeerUserFromMessage(peer: _1!, msgId: _2!, userId: _3!) } } @@ -232,12 +314,15 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 6) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 8) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, sound: _5, storiesMuted: _6, storiesHideSender: _7, storiesSound: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, sound: _5, storiesMuted: _6, storiesHideSender: _7, storiesSound: _8) } } @@ -272,12 +357,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputPhoneCall.inputPhoneCall(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputPhoneCall.inputPhoneCall(id: _1!, accessHash: _2!) } } @@ -325,12 +407,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputPhoto.inputPhoto(id: _1!, accessHash: _2!, fileReference: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputPhoto.inputPhoto(id: _1!, accessHash: _2!, fileReference: _3!) } public static func parse_inputPhotoEmpty(_ reader: BufferReader) -> InputPhoto? { return Api.InputPhoto.inputPhotoEmpty @@ -671,12 +751,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil - if _c1 { - return Api.InputPrivacyRule.inputPrivacyValueAllowChatParticipants(chats: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputPrivacyRule.inputPrivacyValueAllowChatParticipants(chats: _1!) } public static func parse_inputPrivacyValueAllowCloseFriends(_ reader: BufferReader) -> InputPrivacyRule? { return Api.InputPrivacyRule.inputPrivacyValueAllowCloseFriends @@ -693,12 +769,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) } let _c1 = _1 != nil - if _c1 { - return Api.InputPrivacyRule.inputPrivacyValueAllowUsers(users: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputPrivacyRule.inputPrivacyValueAllowUsers(users: _1!) } public static func parse_inputPrivacyValueDisallowAll(_ reader: BufferReader) -> InputPrivacyRule? { return Api.InputPrivacyRule.inputPrivacyValueDisallowAll @@ -712,12 +784,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil - if _c1 { - return Api.InputPrivacyRule.inputPrivacyValueDisallowChatParticipants(chats: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputPrivacyRule.inputPrivacyValueDisallowChatParticipants(chats: _1!) } public static func parse_inputPrivacyValueDisallowContacts(_ reader: BufferReader) -> InputPrivacyRule? { return Api.InputPrivacyRule.inputPrivacyValueDisallowContacts @@ -728,12 +796,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) } let _c1 = _1 != nil - if _c1 { - return Api.InputPrivacyRule.inputPrivacyValueDisallowUsers(users: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputPrivacyRule.inputPrivacyValueDisallowUsers(users: _1!) } } diff --git a/submodules/TelegramApi/Sources/Api13.swift b/submodules/TelegramApi/Sources/Api13.swift index 43504702..fa5243cf 100644 --- a/submodules/TelegramApi/Sources/Api13.swift +++ b/submodules/TelegramApi/Sources/Api13.swift @@ -33,23 +33,15 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputQuickReplyShortcut.inputQuickReplyShortcut(shortcut: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputQuickReplyShortcut.inputQuickReplyShortcut(shortcut: _1!) } public static func parse_inputQuickReplyShortcutId(_ reader: BufferReader) -> InputQuickReplyShortcut? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.InputQuickReplyShortcut.inputQuickReplyShortcutId(shortcutId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputQuickReplyShortcut.inputQuickReplyShortcutId(shortcutId: _1!) } } @@ -141,12 +133,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.InputReplyTo.inputReplyToMessage(flags: _1!, replyToMsgId: _2!, topMsgId: _3, replyToPeerId: _4, quoteText: _5, quoteEntities: _6, quoteOffset: _7, monoforumPeerId: _8, todoItemId: _9) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.InputReplyTo.inputReplyToMessage(flags: _1!, replyToMsgId: _2!, topMsgId: _3, replyToPeerId: _4, quoteText: _5, quoteEntities: _6, quoteOffset: _7, monoforumPeerId: _8, todoItemId: _9) } public static func parse_inputReplyToMonoForum(_ reader: BufferReader) -> InputReplyTo? { var _1: Api.InputPeer? @@ -154,12 +150,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputPeer } let _c1 = _1 != nil - if _c1 { - return Api.InputReplyTo.inputReplyToMonoForum(monoforumPeerId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputReplyTo.inputReplyToMonoForum(monoforumPeerId: _1!) } public static func parse_inputReplyToStory(_ reader: BufferReader) -> InputReplyTo? { var _1: Api.InputPeer? @@ -170,12 +162,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputReplyTo.inputReplyToStory(peer: _1!, storyId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputReplyTo.inputReplyToStory(peer: _1!, storyId: _2!) } } @@ -230,34 +219,23 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputSavedStarGift.inputSavedStarGiftChat(peer: _1!, savedId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputSavedStarGift.inputSavedStarGiftChat(peer: _1!, savedId: _2!) } public static func parse_inputSavedStarGiftSlug(_ reader: BufferReader) -> InputSavedStarGift? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputSavedStarGift.inputSavedStarGiftSlug(slug: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputSavedStarGift.inputSavedStarGiftSlug(slug: _1!) } public static func parse_inputSavedStarGiftUser(_ reader: BufferReader) -> InputSavedStarGift? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.InputSavedStarGift.inputSavedStarGiftUser(msgId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputSavedStarGift.inputSavedStarGiftUser(msgId: _1!) } } @@ -305,12 +283,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputSecureFile.inputSecureFile(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputSecureFile.inputSecureFile(id: _1!, accessHash: _2!) } public static func parse_inputSecureFileUploaded(_ reader: BufferReader) -> InputSecureFile? { var _1: Int64? @@ -328,12 +303,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputSecureFile.inputSecureFileUploaded(id: _1!, parts: _2!, md5Checksum: _3!, fileHash: _4!, secret: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputSecureFile.inputSecureFileUploaded(id: _1!, parts: _2!, md5Checksum: _3!, fileHash: _4!, secret: _5!) } } @@ -420,12 +395,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.InputSecureValue.inputSecureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.InputSecureValue.inputSecureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9) } } @@ -480,12 +459,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputSingleMedia.inputSingleMedia(flags: _1!, media: _2!, randomId: _3!, message: _4!, entities: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputSingleMedia.inputSingleMedia(flags: _1!, media: _2!, randomId: _3!, message: _4!, entities: _5) } } @@ -525,23 +504,15 @@ public extension Api { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.InputStarGiftAuction.inputStarGiftAuction(giftId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputStarGiftAuction.inputStarGiftAuction(giftId: _1!) } public static func parse_inputStarGiftAuctionSlug(_ reader: BufferReader) -> InputStarGiftAuction? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputStarGiftAuction.inputStarGiftAuctionSlug(slug: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputStarGiftAuction.inputStarGiftAuctionSlug(slug: _1!) } } @@ -576,12 +547,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputStarsTransaction.inputStarsTransaction(flags: _1!, id: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputStarsTransaction.inputStarsTransaction(flags: _1!, id: _2!) } } @@ -718,12 +686,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputStickerSet.inputStickerSetDice(emoticon: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputStickerSet.inputStickerSetDice(emoticon: _1!) } public static func parse_inputStickerSetEmojiChannelDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? { return Api.InputStickerSet.inputStickerSetEmojiChannelDefaultStatuses @@ -747,12 +711,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputStickerSet.inputStickerSetID(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputStickerSet.inputStickerSetID(id: _1!, accessHash: _2!) } public static func parse_inputStickerSetPremiumGifts(_ reader: BufferReader) -> InputStickerSet? { return Api.InputStickerSet.inputStickerSetPremiumGifts @@ -761,12 +722,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputStickerSet.inputStickerSetShortName(shortName: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputStickerSet.inputStickerSetShortName(shortName: _1!) } public static func parse_inputStickerSetTonGifts(_ reader: BufferReader) -> InputStickerSet? { return Api.InputStickerSet.inputStickerSetTonGifts @@ -820,12 +777,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputStickerSetItem.inputStickerSetItem(flags: _1!, document: _2!, emoji: _3!, maskCoords: _4, keywords: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputStickerSetItem.inputStickerSetItem(flags: _1!, document: _2!, emoji: _3!, maskCoords: _4, keywords: _5) } } @@ -867,12 +824,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputDocument } let _c1 = _1 != nil - if _c1 { - return Api.InputStickeredMedia.inputStickeredMediaDocument(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputStickeredMedia.inputStickeredMediaDocument(id: _1!) } public static func parse_inputStickeredMediaPhoto(_ reader: BufferReader) -> InputStickeredMedia? { var _1: Api.InputPhoto? @@ -880,12 +833,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputPhoto } let _c1 = _1 != nil - if _c1 { - return Api.InputStickeredMedia.inputStickeredMediaPhoto(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputStickeredMedia.inputStickeredMediaPhoto(id: _1!) } } @@ -1047,12 +996,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputStorePaymentPurpose.inputStorePaymentAuthCode(flags: _1!, phoneNumber: _2!, phoneCodeHash: _3!, currency: _4!, amount: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputStorePaymentPurpose.inputStorePaymentAuthCode(flags: _1!, phoneNumber: _2!, phoneCodeHash: _3!, currency: _4!, amount: _5!) } public static func parse_inputStorePaymentGiftPremium(_ reader: BufferReader) -> InputStorePaymentPurpose? { var _1: Api.InputUser? @@ -1066,12 +1015,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputStorePaymentPurpose.inputStorePaymentGiftPremium(userId: _1!, currency: _2!, amount: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputStorePaymentPurpose.inputStorePaymentGiftPremium(userId: _1!, currency: _2!, amount: _3!) } public static func parse_inputStorePaymentPremiumGiftCode(_ reader: BufferReader) -> InputStorePaymentPurpose? { var _1: Int32? @@ -1098,12 +1045,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiftCode(flags: _1!, users: _2!, boostPeer: _3, currency: _4!, amount: _5!, message: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiftCode(flags: _1!, users: _2!, boostPeer: _3, currency: _4!, amount: _5!, message: _6) } public static func parse_inputStorePaymentPremiumGiveaway(_ reader: BufferReader) -> InputStorePaymentPurpose? { var _1: Int32? @@ -1139,23 +1087,23 @@ public extension Api { let _c7 = _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiveaway(flags: _1!, boostPeer: _2!, additionalPeers: _3, countriesIso2: _4, prizeDescription: _5, randomId: _6!, untilDate: _7!, currency: _8!, amount: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiveaway(flags: _1!, boostPeer: _2!, additionalPeers: _3, countriesIso2: _4, prizeDescription: _5, randomId: _6!, untilDate: _7!, currency: _8!, amount: _9!) } public static func parse_inputStorePaymentPremiumSubscription(_ reader: BufferReader) -> InputStorePaymentPurpose? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.InputStorePaymentPurpose.inputStorePaymentPremiumSubscription(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputStorePaymentPurpose.inputStorePaymentPremiumSubscription(flags: _1!) } public static func parse_inputStorePaymentStarsGift(_ reader: BufferReader) -> InputStorePaymentPurpose? { var _1: Api.InputUser? @@ -1172,12 +1120,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputStorePaymentPurpose.inputStorePaymentStarsGift(userId: _1!, stars: _2!, currency: _3!, amount: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputStorePaymentPurpose.inputStorePaymentStarsGift(userId: _1!, stars: _2!, currency: _3!, amount: _4!) } public static func parse_inputStorePaymentStarsGiveaway(_ reader: BufferReader) -> InputStorePaymentPurpose? { var _1: Int32? @@ -1219,12 +1166,18 @@ public extension Api { let _c9 = _9 != nil let _c10 = _10 != nil let _c11 = _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.InputStorePaymentPurpose.inputStorePaymentStarsGiveaway(flags: _1!, stars: _2!, boostPeer: _3!, additionalPeers: _4, countriesIso2: _5, prizeDescription: _6, randomId: _7!, untilDate: _8!, currency: _9!, amount: _10!, users: _11!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + return Api.InputStorePaymentPurpose.inputStorePaymentStarsGiveaway(flags: _1!, stars: _2!, boostPeer: _3!, additionalPeers: _4, countriesIso2: _5, prizeDescription: _6, randomId: _7!, untilDate: _8!, currency: _9!, amount: _10!, users: _11!) } public static func parse_inputStorePaymentStarsTopup(_ reader: BufferReader) -> InputStorePaymentPurpose? { var _1: Int32? @@ -1244,12 +1197,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputStorePaymentPurpose.inputStorePaymentStarsTopup(flags: _1!, stars: _2!, currency: _3!, amount: _4!, spendPurposePeer: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputStorePaymentPurpose.inputStorePaymentStarsTopup(flags: _1!, stars: _2!, currency: _3!, amount: _4!, spendPurposePeer: _5) } } diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index 5f436a06..4b917cf6 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -37,23 +37,16 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputTheme.inputTheme(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputTheme.inputTheme(id: _1!, accessHash: _2!) } public static func parse_inputThemeSlug(_ reader: BufferReader) -> InputTheme? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputTheme.inputThemeSlug(slug: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputTheme.inputThemeSlug(slug: _1!) } } @@ -120,12 +113,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.InputThemeSettings.inputThemeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6, wallpaperSettings: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.InputThemeSettings.inputThemeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6, wallpaperSettings: _7) } } @@ -189,12 +184,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputUser.inputUser(userId: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputUser.inputUser(userId: _1!, accessHash: _2!) } public static func parse_inputUserEmpty(_ reader: BufferReader) -> InputUser? { return Api.InputUser.inputUserEmpty @@ -211,12 +203,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputUser.inputUserFromMessage(peer: _1!, msgId: _2!, userId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputUser.inputUserFromMessage(peer: _1!, msgId: _2!, userId: _3!) } public static func parse_inputUserSelf(_ reader: BufferReader) -> InputUser? { return Api.InputUser.inputUserSelf @@ -272,34 +262,23 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputWallPaper.inputWallPaper(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputWallPaper.inputWallPaper(id: _1!, accessHash: _2!) } public static func parse_inputWallPaperNoFile(_ reader: BufferReader) -> InputWallPaper? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.InputWallPaper.inputWallPaperNoFile(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputWallPaper.inputWallPaperNoFile(id: _1!) } public static func parse_inputWallPaperSlug(_ reader: BufferReader) -> InputWallPaper? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputWallPaper.inputWallPaperSlug(slug: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputWallPaper.inputWallPaperSlug(slug: _1!) } } @@ -348,12 +327,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputWebDocument.inputWebDocument(url: _1!, size: _2!, mimeType: _3!, attributes: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputWebDocument.inputWebDocument(url: _1!, size: _2!, mimeType: _3!, attributes: _4!) } } @@ -422,12 +400,11 @@ public extension Api { let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputWebFileLocation.inputWebFileAudioAlbumThumbLocation(flags: _1!, document: _2, title: _3, performer: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputWebFileLocation.inputWebFileAudioAlbumThumbLocation(flags: _1!, document: _2, title: _3, performer: _4) } public static func parse_inputWebFileGeoPointLocation(_ reader: BufferReader) -> InputWebFileLocation? { var _1: Api.InputGeoPoint? @@ -450,12 +427,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputWebFileLocation.inputWebFileGeoPointLocation(geoPoint: _1!, accessHash: _2!, w: _3!, h: _4!, zoom: _5!, scale: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.InputWebFileLocation.inputWebFileGeoPointLocation(geoPoint: _1!, accessHash: _2!, w: _3!, h: _4!, zoom: _5!, scale: _6!) } public static func parse_inputWebFileLocation(_ reader: BufferReader) -> InputWebFileLocation? { var _1: String? @@ -464,12 +442,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputWebFileLocation.inputWebFileLocation(url: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputWebFileLocation.inputWebFileLocation(url: _1!, accessHash: _2!) } } @@ -536,12 +511,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 10) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Invoice.invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5, termsUrl: _6, subscriptionPeriod: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.Invoice.invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5, termsUrl: _6, subscriptionPeriod: _7) } } @@ -578,12 +555,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.JSONObjectValue.jsonObjectValue(key: _1!, value: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.JSONObjectValue.jsonObjectValue(key: _1!, value: _2!) } } @@ -669,12 +643,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.JSONValue.self) } let _c1 = _1 != nil - if _c1 { - return Api.JSONValue.jsonArray(value: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.JSONValue.jsonArray(value: _1!) } public static func parse_jsonBool(_ reader: BufferReader) -> JSONValue? { var _1: Api.Bool? @@ -682,12 +652,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Bool } let _c1 = _1 != nil - if _c1 { - return Api.JSONValue.jsonBool(value: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.JSONValue.jsonBool(value: _1!) } public static func parse_jsonNull(_ reader: BufferReader) -> JSONValue? { return Api.JSONValue.jsonNull @@ -696,12 +662,8 @@ public extension Api { var _1: Double? _1 = reader.readDouble() let _c1 = _1 != nil - if _c1 { - return Api.JSONValue.jsonNumber(value: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.JSONValue.jsonNumber(value: _1!) } public static func parse_jsonObject(_ reader: BufferReader) -> JSONValue? { var _1: [Api.JSONObjectValue]? @@ -709,23 +671,15 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.JSONObjectValue.self) } let _c1 = _1 != nil - if _c1 { - return Api.JSONValue.jsonObject(value: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.JSONValue.jsonObject(value: _1!) } public static func parse_jsonString(_ reader: BufferReader) -> JSONValue? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.JSONValue.jsonString(value: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.JSONValue.jsonString(value: _1!) } } @@ -955,12 +909,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.KeyboardButton.inputKeyboardButtonRequestPeer(flags: _1!, text: _2!, buttonId: _3!, peerType: _4!, maxQuantity: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.KeyboardButton.inputKeyboardButtonRequestPeer(flags: _1!, text: _2!, buttonId: _3!, peerType: _4!, maxQuantity: _5!) } public static func parse_inputKeyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { var _1: Int32? @@ -980,12 +934,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.KeyboardButton.inputKeyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, bot: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.KeyboardButton.inputKeyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, bot: _5!) } public static func parse_inputKeyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { var _1: String? @@ -996,34 +950,23 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.inputKeyboardButtonUserProfile(text: _1!, userId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.KeyboardButton.inputKeyboardButtonUserProfile(text: _1!, userId: _2!) } public static func parse_keyboardButton(_ reader: BufferReader) -> KeyboardButton? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButton(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.KeyboardButton.keyboardButton(text: _1!) } public static func parse_keyboardButtonBuy(_ reader: BufferReader) -> KeyboardButton? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonBuy(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.KeyboardButton.keyboardButtonBuy(text: _1!) } public static func parse_keyboardButtonCallback(_ reader: BufferReader) -> KeyboardButton? { var _1: Int32? @@ -1035,12 +978,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonCallback(flags: _1!, text: _2!, data: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.KeyboardButton.keyboardButtonCallback(flags: _1!, text: _2!, data: _3!) } public static func parse_keyboardButtonCopy(_ reader: BufferReader) -> KeyboardButton? { var _1: String? @@ -1049,34 +990,23 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonCopy(text: _1!, copyText: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.KeyboardButton.keyboardButtonCopy(text: _1!, copyText: _2!) } public static func parse_keyboardButtonGame(_ reader: BufferReader) -> KeyboardButton? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonGame(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.KeyboardButton.keyboardButtonGame(text: _1!) } public static func parse_keyboardButtonRequestGeoLocation(_ reader: BufferReader) -> KeyboardButton? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonRequestGeoLocation(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.KeyboardButton.keyboardButtonRequestGeoLocation(text: _1!) } public static func parse_keyboardButtonRequestPeer(_ reader: BufferReader) -> KeyboardButton? { var _1: String? @@ -1093,23 +1023,18 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonRequestPeer(text: _1!, buttonId: _2!, peerType: _3!, maxQuantity: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.KeyboardButton.keyboardButtonRequestPeer(text: _1!, buttonId: _2!, peerType: _3!, maxQuantity: _4!) } public static func parse_keyboardButtonRequestPhone(_ reader: BufferReader) -> KeyboardButton? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButton.keyboardButtonRequestPhone(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.KeyboardButton.keyboardButtonRequestPhone(text: _1!) } public static func parse_keyboardButtonRequestPoll(_ reader: BufferReader) -> KeyboardButton? { var _1: Int32? @@ -1123,12 +1048,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonRequestPoll(flags: _1!, quiz: _2, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.KeyboardButton.keyboardButtonRequestPoll(flags: _1!, quiz: _2, text: _3!) } public static func parse_keyboardButtonSimpleWebView(_ reader: BufferReader) -> KeyboardButton? { var _1: String? @@ -1137,12 +1060,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonSimpleWebView(text: _1!, url: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.KeyboardButton.keyboardButtonSimpleWebView(text: _1!, url: _2!) } public static func parse_keyboardButtonSwitchInline(_ reader: BufferReader) -> KeyboardButton? { var _1: Int32? @@ -1159,12 +1079,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonSwitchInline(flags: _1!, text: _2!, query: _3!, peerTypes: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.KeyboardButton.keyboardButtonSwitchInline(flags: _1!, text: _2!, query: _3!, peerTypes: _4) } public static func parse_keyboardButtonUrl(_ reader: BufferReader) -> KeyboardButton? { var _1: String? @@ -1173,12 +1092,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonUrl(text: _1!, url: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.KeyboardButton.keyboardButtonUrl(text: _1!, url: _2!) } public static func parse_keyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { var _1: Int32? @@ -1196,12 +1112,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.KeyboardButton.keyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, buttonId: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.KeyboardButton.keyboardButtonUrlAuth(flags: _1!, text: _2!, fwdText: _3, url: _4!, buttonId: _5!) } public static func parse_keyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { var _1: String? @@ -1210,12 +1126,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonUserProfile(text: _1!, userId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.KeyboardButton.keyboardButtonUserProfile(text: _1!, userId: _2!) } public static func parse_keyboardButtonWebView(_ reader: BufferReader) -> KeyboardButton? { var _1: String? @@ -1224,12 +1137,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.KeyboardButton.keyboardButtonWebView(text: _1!, url: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.KeyboardButton.keyboardButtonWebView(text: _1!, url: _2!) } } diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 4bb0329f..6b606c80 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -30,12 +30,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButton.self) } let _c1 = _1 != nil - if _c1 { - return Api.KeyboardButtonRow.keyboardButtonRow(buttons: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.KeyboardButtonRow.keyboardButtonRow(buttons: _1!) } } @@ -70,12 +66,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.LabeledPrice.labeledPrice(label: _1!, amount: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.LabeledPrice.labeledPrice(label: _1!, amount: _2!) } } @@ -124,12 +117,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.LangPackDifference.langPackDifference(langCode: _1!, fromVersion: _2!, version: _3!, strings: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.LangPackDifference.langPackDifference(langCode: _1!, fromVersion: _2!, version: _3!, strings: _4!) } } @@ -192,12 +184,16 @@ public extension Api { let _c7 = _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.LangPackLanguage.langPackLanguage(flags: _1!, name: _2!, nativeName: _3!, langCode: _4!, baseLangCode: _5, pluralCode: _6!, stringsCount: _7!, translatedCount: _8!, translationsUrl: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.LangPackLanguage.langPackLanguage(flags: _1!, name: _2!, nativeName: _3!, langCode: _4!, baseLangCode: _5, pluralCode: _6!, stringsCount: _7!, translatedCount: _8!, translationsUrl: _9!) } } @@ -257,23 +253,16 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.LangPackString.langPackString(key: _1!, value: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.LangPackString.langPackString(key: _1!, value: _2!) } public static func parse_langPackStringDeleted(_ reader: BufferReader) -> LangPackString? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.LangPackString.langPackStringDeleted(key: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.LangPackString.langPackStringDeleted(key: _1!) } public static func parse_langPackStringPluralized(_ reader: BufferReader) -> LangPackString? { var _1: Int32? @@ -300,12 +289,15 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.LangPackString.langPackStringPluralized(flags: _1!, key: _2!, zeroValue: _3, oneValue: _4, twoValue: _5, fewValue: _6, manyValue: _7, otherValue: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.LangPackString.langPackStringPluralized(flags: _1!, key: _2!, zeroValue: _3, oneValue: _4, twoValue: _5, fewValue: _6, manyValue: _7, otherValue: _8!) } } @@ -348,12 +340,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MaskCoords.maskCoords(n: _1!, x: _2!, y: _3!, zoom: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MaskCoords.maskCoords(n: _1!, x: _2!, y: _3!, zoom: _4!) } } @@ -488,12 +479,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MediaArea.inputMediaAreaChannelPost(coordinates: _1!, channel: _2!, msgId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MediaArea.inputMediaAreaChannelPost(coordinates: _1!, channel: _2!, msgId: _3!) } public static func parse_inputMediaAreaVenue(_ reader: BufferReader) -> MediaArea? { var _1: Api.MediaAreaCoordinates? @@ -507,12 +496,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MediaArea.inputMediaAreaVenue(coordinates: _1!, queryId: _2!, resultId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MediaArea.inputMediaAreaVenue(coordinates: _1!, queryId: _2!, resultId: _3!) } public static func parse_mediaAreaChannelPost(_ reader: BufferReader) -> MediaArea? { var _1: Api.MediaAreaCoordinates? @@ -526,12 +513,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MediaArea.mediaAreaChannelPost(coordinates: _1!, channelId: _2!, msgId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MediaArea.mediaAreaChannelPost(coordinates: _1!, channelId: _2!, msgId: _3!) } public static func parse_mediaAreaGeoPoint(_ reader: BufferReader) -> MediaArea? { var _1: Int32? @@ -552,12 +537,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MediaArea.mediaAreaGeoPoint(flags: _1!, coordinates: _2!, geo: _3!, address: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MediaArea.mediaAreaGeoPoint(flags: _1!, coordinates: _2!, geo: _3!, address: _4) } public static func parse_mediaAreaStarGift(_ reader: BufferReader) -> MediaArea? { var _1: Api.MediaAreaCoordinates? @@ -568,12 +552,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MediaArea.mediaAreaStarGift(coordinates: _1!, slug: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MediaArea.mediaAreaStarGift(coordinates: _1!, slug: _2!) } public static func parse_mediaAreaSuggestedReaction(_ reader: BufferReader) -> MediaArea? { var _1: Int32? @@ -589,12 +570,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MediaArea.mediaAreaSuggestedReaction(flags: _1!, coordinates: _2!, reaction: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MediaArea.mediaAreaSuggestedReaction(flags: _1!, coordinates: _2!, reaction: _3!) } public static func parse_mediaAreaUrl(_ reader: BufferReader) -> MediaArea? { var _1: Api.MediaAreaCoordinates? @@ -605,12 +584,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MediaArea.mediaAreaUrl(coordinates: _1!, url: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MediaArea.mediaAreaUrl(coordinates: _1!, url: _2!) } public static func parse_mediaAreaVenue(_ reader: BufferReader) -> MediaArea? { var _1: Api.MediaAreaCoordinates? @@ -638,12 +614,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MediaArea.mediaAreaVenue(coordinates: _1!, geo: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!, venueType: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.MediaArea.mediaAreaVenue(coordinates: _1!, geo: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!, venueType: _7!) } public static func parse_mediaAreaWeather(_ reader: BufferReader) -> MediaArea? { var _1: Api.MediaAreaCoordinates? @@ -660,12 +638,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MediaArea.mediaAreaWeather(coordinates: _1!, emoji: _2!, temperatureC: _3!, color: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MediaArea.mediaAreaWeather(coordinates: _1!, emoji: _2!, temperatureC: _3!, color: _4!) } } @@ -720,27 +697,29 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MediaAreaCoordinates.mediaAreaCoordinates(flags: _1!, x: _2!, y: _3!, w: _4!, h: _5!, rotation: _6!, radius: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.MediaAreaCoordinates.mediaAreaCoordinates(flags: _1!, x: _2!, y: _3!, w: _4!, h: _5!, rotation: _6!, radius: _7) } } } public extension Api { indirect enum Message: TypeConstructorDescription { - case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?, suggestedPost: Api.SuggestedPost?, scheduleRepeatPeriod: Int32?) + case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?, suggestedPost: Api.SuggestedPost?, scheduleRepeatPeriod: Int32?, summaryFromLanguage: String?) case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?) case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, savedPeerId: Api.Peer?, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost, let scheduleRepeatPeriod): + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost, let scheduleRepeatPeriod, let summaryFromLanguage): if boxed { - buffer.appendInt32(-1188071729) + buffer.appendInt32(-1665888023) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -782,6 +761,7 @@ public extension Api { if Int(flags2) & Int(1 << 6) != 0 {serializeInt64(paidMessageStars!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 7) != 0 {suggestedPost!.serialize(buffer, true)} if Int(flags2) & Int(1 << 10) != 0 {serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 11) != 0 {serializeString(summaryFromLanguage!, buffer: buffer, boxed: false)} break case .messageEmpty(let flags, let id, let peerId): if boxed { @@ -811,8 +791,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost, let scheduleRepeatPeriod): - return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any), ("paidMessageStars", paidMessageStars as Any), ("suggestedPost", suggestedPost as Any), ("scheduleRepeatPeriod", scheduleRepeatPeriod as Any)]) + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost, let scheduleRepeatPeriod, let summaryFromLanguage): + return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any), ("paidMessageStars", paidMessageStars as Any), ("suggestedPost", suggestedPost as Any), ("scheduleRepeatPeriod", scheduleRepeatPeriod as Any), ("summaryFromLanguage", summaryFromLanguage as Any)]) case .messageEmpty(let flags, let id, let peerId): return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)]) case .messageService(let flags, let id, let fromId, let peerId, let savedPeerId, let replyTo, let date, let action, let reactions, let ttlPeriod): @@ -911,6 +891,8 @@ public extension Api { } } var _32: Int32? if Int(_2!) & Int(1 << 10) != 0 {_32 = reader.readInt32() } + var _33: String? + if Int(_2!) & Int(1 << 11) != 0 {_33 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -943,12 +925,41 @@ public extension Api { let _c30 = (Int(_2!) & Int(1 << 6) == 0) || _30 != nil let _c31 = (Int(_2!) & Int(1 << 7) == 0) || _31 != nil let _c32 = (Int(_2!) & Int(1 << 10) == 0) || _32 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 { - return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28, reportDeliveryUntilDate: _29, paidMessageStars: _30, suggestedPost: _31, scheduleRepeatPeriod: _32) - } - else { - return nil - } + let _c33 = (Int(_2!) & Int(1 << 11) == 0) || _33 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + if !_c20 { return nil } + if !_c21 { return nil } + if !_c22 { return nil } + if !_c23 { return nil } + if !_c24 { return nil } + if !_c25 { return nil } + if !_c26 { return nil } + if !_c27 { return nil } + if !_c28 { return nil } + if !_c29 { return nil } + if !_c30 { return nil } + if !_c31 { return nil } + if !_c32 { return nil } + if !_c33 { return nil } + return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28, reportDeliveryUntilDate: _29, paidMessageStars: _30, suggestedPost: _31, scheduleRepeatPeriod: _32, summaryFromLanguage: _33) } public static func parse_messageEmpty(_ reader: BufferReader) -> Message? { var _1: Int32? @@ -962,12 +973,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Message.messageEmpty(flags: _1!, id: _2!, peerId: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Message.messageEmpty(flags: _1!, id: _2!, peerId: _3) } public static func parse_messageService(_ reader: BufferReader) -> Message? { var _1: Int32? @@ -1012,12 +1021,17 @@ public extension Api { let _c8 = _8 != nil let _c9 = (Int(_1!) & Int(1 << 20) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 25) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, savedPeerId: _5, replyTo: _6, date: _7!, action: _8!, reactions: _9, ttlPeriod: _10) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, savedPeerId: _5, replyTo: _6, date: _7!, action: _8!, reactions: _9, ttlPeriod: _10) } } @@ -1735,12 +1749,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionBoostApply(boosts: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionBoostApply(boosts: _1!) } public static func parse_messageActionBotAllowed(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -1754,23 +1764,17 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionBotAllowed(flags: _1!, domain: _2, app: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageAction.messageActionBotAllowed(flags: _1!, domain: _2, app: _3) } public static func parse_messageActionChannelCreate(_ reader: BufferReader) -> MessageAction? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChannelCreate(title: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionChannelCreate(title: _1!) } public static func parse_messageActionChannelMigrateFrom(_ reader: BufferReader) -> MessageAction? { var _1: String? @@ -1779,12 +1783,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionChannelMigrateFrom(title: _1!, chatId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionChannelMigrateFrom(title: _1!, chatId: _2!) } public static func parse_messageActionChatAddUser(_ reader: BufferReader) -> MessageAction? { var _1: [Int64]? @@ -1792,12 +1793,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatAddUser(users: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionChatAddUser(users: _1!) } public static func parse_messageActionChatCreate(_ reader: BufferReader) -> MessageAction? { var _1: String? @@ -1808,12 +1805,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionChatCreate(title: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionChatCreate(title: _1!, users: _2!) } public static func parse_messageActionChatDeletePhoto(_ reader: BufferReader) -> MessageAction? { return Api.MessageAction.messageActionChatDeletePhoto @@ -1822,12 +1816,8 @@ public extension Api { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatDeleteUser(userId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionChatDeleteUser(userId: _1!) } public static func parse_messageActionChatEditPhoto(_ reader: BufferReader) -> MessageAction? { var _1: Api.Photo? @@ -1835,34 +1825,22 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Photo } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatEditPhoto(photo: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionChatEditPhoto(photo: _1!) } public static func parse_messageActionChatEditTitle(_ reader: BufferReader) -> MessageAction? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatEditTitle(title: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionChatEditTitle(title: _1!) } public static func parse_messageActionChatJoinedByLink(_ reader: BufferReader) -> MessageAction? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatJoinedByLink(inviterId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionChatJoinedByLink(inviterId: _1!) } public static func parse_messageActionChatJoinedByRequest(_ reader: BufferReader) -> MessageAction? { return Api.MessageAction.messageActionChatJoinedByRequest @@ -1871,12 +1849,8 @@ public extension Api { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatMigrateTo(channelId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionChatMigrateTo(channelId: _1!) } public static func parse_messageActionConferenceCall(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -1893,12 +1867,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionConferenceCall(flags: _1!, callId: _2!, duration: _3, otherParticipants: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MessageAction.messageActionConferenceCall(flags: _1!, callId: _2!, duration: _3, otherParticipants: _4) } public static func parse_messageActionContactSignUp(_ reader: BufferReader) -> MessageAction? { return Api.MessageAction.messageActionContactSignUp @@ -1907,12 +1880,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionCustomAction(message: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionCustomAction(message: _1!) } public static func parse_messageActionEmpty(_ reader: BufferReader) -> MessageAction? { return Api.MessageAction.messageActionEmpty @@ -1924,12 +1893,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionGameScore(gameId: _1!, score: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionGameScore(gameId: _1!, score: _2!) } public static func parse_messageActionGeoProximityReached(_ reader: BufferReader) -> MessageAction? { var _1: Api.Peer? @@ -1945,12 +1911,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionGeoProximityReached(fromId: _1!, toId: _2!, distance: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageAction.messageActionGeoProximityReached(fromId: _1!, toId: _2!, distance: _3!) } public static func parse_messageActionGiftCode(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -1984,12 +1948,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.MessageAction.messageActionGiftCode(flags: _1!, boostPeer: _2, days: _3!, slug: _4!, currency: _5, amount: _6, cryptoCurrency: _7, cryptoAmount: _8, message: _9) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.MessageAction.messageActionGiftCode(flags: _1!, boostPeer: _2, days: _3!, slug: _4!, currency: _5, amount: _6, cryptoCurrency: _7, cryptoAmount: _8, message: _9) } public static func parse_messageActionGiftPremium(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2015,12 +1983,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageAction.messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, days: _4!, cryptoCurrency: _5, cryptoAmount: _6, message: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.MessageAction.messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, days: _4!, cryptoCurrency: _5, cryptoAmount: _6, message: _7) } public static func parse_messageActionGiftStars(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2044,12 +2014,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageAction.messageActionGiftStars(flags: _1!, currency: _2!, amount: _3!, stars: _4!, cryptoCurrency: _5, cryptoAmount: _6, transactionId: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.MessageAction.messageActionGiftStars(flags: _1!, currency: _2!, amount: _3!, stars: _4!, cryptoCurrency: _5, cryptoAmount: _6, transactionId: _7) } public static func parse_messageActionGiftTon(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2070,12 +2042,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageAction.messageActionGiftTon(flags: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, transactionId: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.MessageAction.messageActionGiftTon(flags: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, transactionId: _6) } public static func parse_messageActionGiveawayLaunch(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2084,12 +2057,9 @@ public extension Api { if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt64() } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionGiveawayLaunch(flags: _1!, stars: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionGiveawayLaunch(flags: _1!, stars: _2) } public static func parse_messageActionGiveawayResults(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2101,12 +2071,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionGiveawayResults(flags: _1!, winnersCount: _2!, unclaimedCount: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageAction.messageActionGiveawayResults(flags: _1!, winnersCount: _2!, unclaimedCount: _3!) } public static func parse_messageActionGroupCall(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2120,12 +2088,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionGroupCall(flags: _1!, call: _2!, duration: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageAction.messageActionGroupCall(flags: _1!, call: _2!, duration: _3) } public static func parse_messageActionGroupCallScheduled(_ reader: BufferReader) -> MessageAction? { var _1: Api.InputGroupCall? @@ -2136,12 +2102,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionGroupCallScheduled(call: _1!, scheduleDate: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionGroupCallScheduled(call: _1!, scheduleDate: _2!) } public static func parse_messageActionHistoryClear(_ reader: BufferReader) -> MessageAction? { return Api.MessageAction.messageActionHistoryClear @@ -2157,12 +2120,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionInviteToGroupCall(call: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionInviteToGroupCall(call: _1!, users: _2!) } public static func parse_messageActionPaidMessagesPrice(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2171,12 +2131,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionPaidMessagesPrice(flags: _1!, stars: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionPaidMessagesPrice(flags: _1!, stars: _2!) } public static func parse_messageActionPaidMessagesRefunded(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2185,12 +2142,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionPaidMessagesRefunded(count: _1!, stars: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionPaidMessagesRefunded(count: _1!, stars: _2!) } public static func parse_messageActionPaymentRefunded(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2215,12 +2169,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageAction.messageActionPaymentRefunded(flags: _1!, peer: _2!, currency: _3!, totalAmount: _4!, payload: _5, charge: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.MessageAction.messageActionPaymentRefunded(flags: _1!, peer: _2!, currency: _3!, totalAmount: _4!, payload: _5, charge: _6!) } public static func parse_messageActionPaymentSent(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2238,12 +2193,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageAction.messageActionPaymentSent(flags: _1!, currency: _2!, totalAmount: _3!, invoiceSlug: _4, subscriptionUntilDate: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.MessageAction.messageActionPaymentSent(flags: _1!, currency: _2!, totalAmount: _3!, invoiceSlug: _4, subscriptionUntilDate: _5) } public static func parse_messageActionPaymentSentMe(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2274,12 +2229,15 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c7 = _7 != nil let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.MessageAction.messageActionPaymentSentMe(flags: _1!, currency: _2!, totalAmount: _3!, payload: _4!, info: _5, shippingOptionId: _6, charge: _7!, subscriptionUntilDate: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.MessageAction.messageActionPaymentSentMe(flags: _1!, currency: _2!, totalAmount: _3!, payload: _4!, info: _5, shippingOptionId: _6, charge: _7!, subscriptionUntilDate: _8) } public static func parse_messageActionPhoneCall(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2296,12 +2254,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionPhoneCall(flags: _1!, callId: _2!, reason: _3, duration: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MessageAction.messageActionPhoneCall(flags: _1!, callId: _2!, reason: _3, duration: _4) } public static func parse_messageActionPinMessage(_ reader: BufferReader) -> MessageAction? { return Api.MessageAction.messageActionPinMessage @@ -2324,12 +2281,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageAction.messageActionPrizeStars(flags: _1!, stars: _2!, transactionId: _3!, boostPeer: _4!, giveawayMsgId: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.MessageAction.messageActionPrizeStars(flags: _1!, stars: _2!, transactionId: _3!, boostPeer: _4!, giveawayMsgId: _5!) } public static func parse_messageActionRequestedPeer(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2340,12 +2297,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionRequestedPeer(buttonId: _1!, peers: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionRequestedPeer(buttonId: _1!, peers: _2!) } public static func parse_messageActionRequestedPeerSentMe(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2356,12 +2310,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionRequestedPeerSentMe(buttonId: _1!, peers: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionRequestedPeerSentMe(buttonId: _1!, peers: _2!) } public static func parse_messageActionScreenshotTaken(_ reader: BufferReader) -> MessageAction? { return Api.MessageAction.messageActionScreenshotTaken @@ -2372,12 +2323,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValueType.self) } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSecureValuesSent(types: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionSecureValuesSent(types: _1!) } public static func parse_messageActionSecureValuesSentMe(_ reader: BufferReader) -> MessageAction? { var _1: [Api.SecureValue]? @@ -2390,12 +2337,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionSecureValuesSentMe(values: _1!, credentials: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionSecureValuesSentMe(values: _1!, credentials: _2!) } public static func parse_messageActionSetChatTheme(_ reader: BufferReader) -> MessageAction? { var _1: Api.ChatTheme? @@ -2403,12 +2347,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.ChatTheme } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSetChatTheme(theme: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionSetChatTheme(theme: _1!) } public static func parse_messageActionSetChatWallPaper(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2419,12 +2359,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionSetChatWallPaper(flags: _1!, wallpaper: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionSetChatWallPaper(flags: _1!, wallpaper: _2!) } public static func parse_messageActionSetMessagesTTL(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2436,12 +2373,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionSetMessagesTTL(flags: _1!, period: _2!, autoSettingFrom: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageAction.messageActionSetMessagesTTL(flags: _1!, period: _2!, autoSettingFrom: _3) } public static func parse_messageActionStarGift(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2493,12 +2428,20 @@ public extension Api { let _c11 = (Int(_1!) & Int(1 << 15) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 18) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 19) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, upgradeMsgId: _5, upgradeStars: _6, fromId: _7, peer: _8, savedId: _9, prepaidUpgradeHash: _10, giftMsgId: _11, toId: _12, giftNum: _13) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, upgradeMsgId: _5, upgradeStars: _6, fromId: _7, peer: _8, savedId: _9, prepaidUpgradeHash: _10, giftMsgId: _11, toId: _12, giftNum: _13) } public static func parse_messageActionStarGiftPurchaseOffer(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2517,12 +2460,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionStarGiftPurchaseOffer(flags: _1!, gift: _2!, price: _3!, expiresAt: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MessageAction.messageActionStarGiftPurchaseOffer(flags: _1!, gift: _2!, price: _3!, expiresAt: _4!) } public static func parse_messageActionStarGiftPurchaseOfferDeclined(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2538,12 +2480,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionStarGiftPurchaseOfferDeclined(flags: _1!, gift: _2!, price: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageAction.messageActionStarGiftPurchaseOfferDeclined(flags: _1!, gift: _2!, price: _3!) } public static func parse_messageActionStarGiftUnique(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2587,12 +2527,18 @@ public extension Api { let _c9 = (Int(_1!) & Int(1 << 9) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 10) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.MessageAction.messageActionStarGiftUnique(flags: _1!, gift: _2!, canExportAt: _3, transferStars: _4, fromId: _5, peer: _6, savedId: _7, resaleAmount: _8, canTransferAt: _9, canResellAt: _10, dropOriginalDetailsStars: _11) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + return Api.MessageAction.messageActionStarGiftUnique(flags: _1!, gift: _2!, canExportAt: _3, transferStars: _4, fromId: _5, peer: _6, savedId: _7, resaleAmount: _8, canTransferAt: _9, canResellAt: _10, dropOriginalDetailsStars: _11) } public static func parse_messageActionSuggestBirthday(_ reader: BufferReader) -> MessageAction? { var _1: Api.Birthday? @@ -2600,12 +2546,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Birthday } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSuggestBirthday(birthday: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionSuggestBirthday(birthday: _1!) } public static func parse_messageActionSuggestProfilePhoto(_ reader: BufferReader) -> MessageAction? { var _1: Api.Photo? @@ -2613,12 +2555,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Photo } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSuggestProfilePhoto(photo: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionSuggestProfilePhoto(photo: _1!) } public static func parse_messageActionSuggestedPostApproval(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2635,23 +2573,18 @@ public extension Api { let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionSuggestedPostApproval(flags: _1!, rejectComment: _2, scheduleDate: _3, price: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MessageAction.messageActionSuggestedPostApproval(flags: _1!, rejectComment: _2, scheduleDate: _3, price: _4) } public static func parse_messageActionSuggestedPostRefund(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSuggestedPostRefund(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionSuggestedPostRefund(flags: _1!) } public static func parse_messageActionSuggestedPostSuccess(_ reader: BufferReader) -> MessageAction? { var _1: Api.StarsAmount? @@ -2659,12 +2592,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.StarsAmount } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSuggestedPostSuccess(price: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionSuggestedPostSuccess(price: _1!) } public static func parse_messageActionTodoAppendTasks(_ reader: BufferReader) -> MessageAction? { var _1: [Api.TodoItem]? @@ -2672,12 +2601,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TodoItem.self) } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionTodoAppendTasks(list: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionTodoAppendTasks(list: _1!) } public static func parse_messageActionTodoCompletions(_ reader: BufferReader) -> MessageAction? { var _1: [Int32]? @@ -2690,12 +2615,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionTodoCompletions(completed: _1!, incompleted: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionTodoCompletions(completed: _1!, incompleted: _2!) } public static func parse_messageActionTopicCreate(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2710,12 +2632,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionTopicCreate(flags: _1!, title: _2!, iconColor: _3!, iconEmojiId: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MessageAction.messageActionTopicCreate(flags: _1!, title: _2!, iconColor: _3!, iconEmojiId: _4) } public static func parse_messageActionTopicEdit(_ reader: BufferReader) -> MessageAction? { var _1: Int32? @@ -2737,23 +2658,19 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageAction.messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4, hidden: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.MessageAction.messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4, hidden: _5) } public static func parse_messageActionWebViewDataSent(_ reader: BufferReader) -> MessageAction? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionWebViewDataSent(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageAction.messageActionWebViewDataSent(text: _1!) } public static func parse_messageActionWebViewDataSentMe(_ reader: BufferReader) -> MessageAction? { var _1: String? @@ -2762,12 +2679,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionWebViewDataSentMe(text: _1!, data: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageAction.messageActionWebViewDataSentMe(text: _1!, data: _2!) } } diff --git a/submodules/TelegramApi/Sources/Api16.swift b/submodules/TelegramApi/Sources/Api16.swift index 90af1af4..1cec7d62 100644 --- a/submodules/TelegramApi/Sources/Api16.swift +++ b/submodules/TelegramApi/Sources/Api16.swift @@ -239,12 +239,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.inputMessageEntityMentionName(offset: _1!, length: _2!, userId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageEntity.inputMessageEntityMentionName(offset: _1!, length: _2!, userId: _3!) } public static func parse_messageEntityBankCard(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -253,12 +251,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityBankCard(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityBankCard(offset: _1!, length: _2!) } public static func parse_messageEntityBlockquote(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -270,12 +265,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityBlockquote(flags: _1!, offset: _2!, length: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageEntity.messageEntityBlockquote(flags: _1!, offset: _2!, length: _3!) } public static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -284,12 +277,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityBold(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityBold(offset: _1!, length: _2!) } public static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -298,12 +288,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) } public static func parse_messageEntityCashtag(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -312,12 +299,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityCashtag(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityCashtag(offset: _1!, length: _2!) } public static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -326,12 +310,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityCode(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityCode(offset: _1!, length: _2!) } public static func parse_messageEntityCustomEmoji(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -343,12 +324,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityCustomEmoji(offset: _1!, length: _2!, documentId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageEntity.messageEntityCustomEmoji(offset: _1!, length: _2!, documentId: _3!) } public static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -357,12 +336,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) } public static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -371,12 +347,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) } public static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -385,12 +358,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) } public static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -399,12 +369,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityMention(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityMention(offset: _1!, length: _2!) } public static func parse_messageEntityMentionName(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -416,12 +383,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityMentionName(offset: _1!, length: _2!, userId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageEntity.messageEntityMentionName(offset: _1!, length: _2!, userId: _3!) } public static func parse_messageEntityPhone(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -430,12 +395,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityPhone(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityPhone(offset: _1!, length: _2!) } public static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -447,12 +409,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) } public static func parse_messageEntitySpoiler(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -461,12 +421,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntitySpoiler(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntitySpoiler(offset: _1!, length: _2!) } public static func parse_messageEntityStrike(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -475,12 +432,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityStrike(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityStrike(offset: _1!, length: _2!) } public static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -492,12 +446,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) } public static func parse_messageEntityUnderline(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -506,12 +458,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityUnderline(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityUnderline(offset: _1!, length: _2!) } public static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -520,12 +469,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) } public static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { var _1: Int32? @@ -534,12 +480,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) } } @@ -585,12 +528,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.MessageMedia } let _c1 = _1 != nil - if _c1 { - return Api.MessageExtendedMedia.messageExtendedMedia(media: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageExtendedMedia.messageExtendedMedia(media: _1!) } public static func parse_messageExtendedMediaPreview(_ reader: BufferReader) -> MessageExtendedMedia? { var _1: Int32? @@ -610,12 +549,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageExtendedMedia.messageExtendedMediaPreview(flags: _1!, w: _2, h: _3, thumb: _4, videoDuration: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.MessageExtendedMedia.messageExtendedMediaPreview(flags: _1!, w: _2, h: _3, thumb: _4, videoDuration: _5) } } @@ -696,12 +635,19 @@ public extension Api { let _c10 = (Int(_1!) & Int(1 << 9) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 10) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.MessageFwdHeader.messageFwdHeader(flags: _1!, fromId: _2, fromName: _3, date: _4!, channelPost: _5, postAuthor: _6, savedFromPeer: _7, savedFromMsgId: _8, savedFromId: _9, savedFromName: _10, savedDate: _11, psaType: _12) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + return Api.MessageFwdHeader.messageFwdHeader(flags: _1!, fromId: _2, fromName: _3, date: _4!, channelPost: _5, postAuthor: _6, savedFromPeer: _7, savedFromMsgId: _8, savedFromId: _9, savedFromName: _10, savedDate: _11, psaType: _12) } } @@ -709,7 +655,7 @@ public extension Api { public extension Api { indirect enum MessageMedia: TypeConstructorDescription { case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64) - case messageMediaDice(value: Int32, emoticon: String) + case messageMediaDice(flags: Int32, value: Int32, emoticon: String, gameOutcome: Api.messages.EmojiGameOutcome?) case messageMediaDocument(flags: Int32, document: Api.Document?, altDocuments: [Api.Document]?, videoCover: Api.Photo?, videoTimestamp: Int32?, ttlSeconds: Int32?) case messageMediaEmpty case messageMediaGame(game: Api.Game) @@ -740,12 +686,14 @@ public extension Api { serializeString(vcard, buffer: buffer, boxed: false) serializeInt64(userId, buffer: buffer, boxed: false) break - case .messageMediaDice(let value, let emoticon): + case .messageMediaDice(let flags, let value, let emoticon, let gameOutcome): if boxed { - buffer.appendInt32(1065280907) + buffer.appendInt32(147581959) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(value, buffer: buffer, boxed: false) serializeString(emoticon, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {gameOutcome!.serialize(buffer, true)} break case .messageMediaDocument(let flags, let document, let altDocuments, let videoCover, let videoTimestamp, let ttlSeconds): if boxed { @@ -930,8 +878,8 @@ public extension Api { switch self { case .messageMediaContact(let phoneNumber, let firstName, let lastName, let vcard, let userId): return ("messageMediaContact", [("phoneNumber", phoneNumber as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("vcard", vcard as Any), ("userId", userId as Any)]) - case .messageMediaDice(let value, let emoticon): - return ("messageMediaDice", [("value", value as Any), ("emoticon", emoticon as Any)]) + case .messageMediaDice(let flags, let value, let emoticon, let gameOutcome): + return ("messageMediaDice", [("flags", flags as Any), ("value", value as Any), ("emoticon", emoticon as Any), ("gameOutcome", gameOutcome as Any)]) case .messageMediaDocument(let flags, let document, let altDocuments, let videoCover, let videoTimestamp, let ttlSeconds): return ("messageMediaDocument", [("flags", flags as Any), ("document", document as Any), ("altDocuments", altDocuments as Any), ("videoCover", videoCover as Any), ("videoTimestamp", videoTimestamp as Any), ("ttlSeconds", ttlSeconds as Any)]) case .messageMediaEmpty: @@ -985,26 +933,33 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageMedia.messageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, vcard: _4!, userId: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.MessageMedia.messageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, vcard: _4!, userId: _5!) } public static func parse_messageMediaDice(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: Api.messages.EmojiGameOutcome? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameOutcome + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageMedia.messageMediaDice(value: _1!, emoticon: _2!) - } - else { - return nil - } + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MessageMedia.messageMediaDice(flags: _1!, value: _2!, emoticon: _3!, gameOutcome: _4) } public static func parse_messageMediaDocument(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? @@ -1031,12 +986,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 10) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageMedia.messageMediaDocument(flags: _1!, document: _2, altDocuments: _3, videoCover: _4, videoTimestamp: _5, ttlSeconds: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.MessageMedia.messageMediaDocument(flags: _1!, document: _2, altDocuments: _3, videoCover: _4, videoTimestamp: _5, ttlSeconds: _6) } public static func parse_messageMediaEmpty(_ reader: BufferReader) -> MessageMedia? { return Api.MessageMedia.messageMediaEmpty @@ -1047,12 +1003,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Game } let _c1 = _1 != nil - if _c1 { - return Api.MessageMedia.messageMediaGame(game: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageMedia.messageMediaGame(game: _1!) } public static func parse_messageMediaGeo(_ reader: BufferReader) -> MessageMedia? { var _1: Api.GeoPoint? @@ -1060,12 +1012,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint } let _c1 = _1 != nil - if _c1 { - return Api.MessageMedia.messageMediaGeo(geo: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessageMedia.messageMediaGeo(geo: _1!) } public static func parse_messageMediaGeoLive(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? @@ -1085,12 +1033,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageMedia.messageMediaGeoLive(flags: _1!, geo: _2!, heading: _3, period: _4!, proximityNotificationRadius: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.MessageMedia.messageMediaGeoLive(flags: _1!, geo: _2!, heading: _3, period: _4!, proximityNotificationRadius: _5) } public static func parse_messageMediaGiveaway(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? @@ -1121,12 +1069,15 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.MessageMedia.messageMediaGiveaway(flags: _1!, channels: _2!, countriesIso2: _3, prizeDescription: _4, quantity: _5!, months: _6, stars: _7, untilDate: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.MessageMedia.messageMediaGiveaway(flags: _1!, channels: _2!, countriesIso2: _3, prizeDescription: _4, quantity: _5!, months: _6, stars: _7, untilDate: _8!) } public static func parse_messageMediaGiveawayResults(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? @@ -1164,12 +1115,18 @@ public extension Api { let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil let _c11 = _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.MessageMedia.messageMediaGiveawayResults(flags: _1!, channelId: _2!, additionalPeersCount: _3, launchMsgId: _4!, winnersCount: _5!, unclaimedCount: _6!, winners: _7!, months: _8, stars: _9, prizeDescription: _10, untilDate: _11!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + return Api.MessageMedia.messageMediaGiveawayResults(flags: _1!, channelId: _2!, additionalPeersCount: _3, launchMsgId: _4!, winnersCount: _5!, unclaimedCount: _6!, winners: _7!, months: _8, stars: _9, prizeDescription: _10, untilDate: _11!) } public static func parse_messageMediaInvoice(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? @@ -1203,12 +1160,16 @@ public extension Api { let _c7 = _7 != nil let _c8 = _8 != nil let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.MessageMedia.messageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, receiptMsgId: _5, currency: _6!, totalAmount: _7!, startParam: _8!, extendedMedia: _9) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.MessageMedia.messageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, receiptMsgId: _5, currency: _6!, totalAmount: _7!, startParam: _8!, extendedMedia: _9) } public static func parse_messageMediaPaidMedia(_ reader: BufferReader) -> MessageMedia? { var _1: Int64? @@ -1219,12 +1180,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageMedia.messageMediaPaidMedia(starsAmount: _1!, extendedMedia: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageMedia.messageMediaPaidMedia(starsAmount: _1!, extendedMedia: _2!) } public static func parse_messageMediaPhoto(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? @@ -1238,12 +1196,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageMedia.messageMediaPhoto(flags: _1!, photo: _2, ttlSeconds: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageMedia.messageMediaPhoto(flags: _1!, photo: _2, ttlSeconds: _3) } public static func parse_messageMediaPoll(_ reader: BufferReader) -> MessageMedia? { var _1: Api.Poll? @@ -1256,12 +1212,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageMedia.messageMediaPoll(poll: _1!, results: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageMedia.messageMediaPoll(poll: _1!, results: _2!) } public static func parse_messageMediaStory(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? @@ -1280,12 +1233,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageMedia.messageMediaStory(flags: _1!, peer: _2!, id: _3!, story: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MessageMedia.messageMediaStory(flags: _1!, peer: _2!, id: _3!, story: _4) } public static func parse_messageMediaToDo(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? @@ -1301,12 +1253,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageMedia.messageMediaToDo(flags: _1!, todo: _2!, completions: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageMedia.messageMediaToDo(flags: _1!, todo: _2!, completions: _3) } public static func parse_messageMediaUnsupported(_ reader: BufferReader) -> MessageMedia? { return Api.MessageMedia.messageMediaUnsupported @@ -1332,12 +1282,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageMedia.messageMediaVenue(geo: _1!, title: _2!, address: _3!, provider: _4!, venueId: _5!, venueType: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.MessageMedia.messageMediaVenue(geo: _1!, title: _2!, address: _3!, provider: _4!, venueId: _5!, venueType: _6!) } public static func parse_messageMediaVideoStream(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? @@ -1348,12 +1299,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageMedia.messageMediaVideoStream(flags: _1!, call: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageMedia.messageMediaVideoStream(flags: _1!, call: _2!) } public static func parse_messageMediaWebPage(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? @@ -1364,12 +1312,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageMedia.messageMediaWebPage(flags: _1!, webpage: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageMedia.messageMediaWebPage(flags: _1!, webpage: _2!) } } diff --git a/submodules/TelegramApi/Sources/Api17.swift b/submodules/TelegramApi/Sources/Api17.swift index abefb9f7..5cc52b37 100644 --- a/submodules/TelegramApi/Sources/Api17.swift +++ b/submodules/TelegramApi/Sources/Api17.swift @@ -40,12 +40,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessagePeerReaction.messagePeerReaction(flags: _1!, peerId: _2!, date: _3!, reaction: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MessagePeerReaction.messagePeerReaction(flags: _1!, peerId: _2!, date: _3!, reaction: _4!) } } @@ -111,12 +110,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessagePeerVote.messagePeerVote(peer: _1!, option: _2!, date: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessagePeerVote.messagePeerVote(peer: _1!, option: _2!, date: _3!) } public static func parse_messagePeerVoteInputOption(_ reader: BufferReader) -> MessagePeerVote? { var _1: Api.Peer? @@ -127,12 +124,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessagePeerVote.messagePeerVoteInputOption(peer: _1!, date: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessagePeerVote.messagePeerVoteInputOption(peer: _1!, date: _2!) } public static func parse_messagePeerVoteMultiple(_ reader: BufferReader) -> MessagePeerVote? { var _1: Api.Peer? @@ -148,12 +142,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessagePeerVote.messagePeerVoteMultiple(peer: _1!, options: _2!, date: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessagePeerVote.messagePeerVoteMultiple(peer: _1!, options: _2!, date: _3!) } } @@ -188,12 +180,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageRange.messageRange(minId: _1!, maxId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageRange.messageRange(minId: _1!, maxId: _2!) } } @@ -254,12 +243,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageReactions.messageReactions(flags: _1!, results: _2!, recentReactions: _3, topReactors: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MessageReactions.messageReactions(flags: _1!, results: _2!, recentReactions: _3, topReactors: _4) } } @@ -300,12 +288,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageReactor.messageReactor(flags: _1!, peerId: _2, count: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.MessageReactor.messageReactor(flags: _1!, peerId: _2, count: _3!) } } @@ -366,12 +352,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageReplies.messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5, maxId: _6, readMaxId: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.MessageReplies.messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5, maxId: _6, readMaxId: _7) } } @@ -460,12 +448,17 @@ public extension Api { let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.MessageReplyHeader.messageReplyHeader(flags: _1!, replyToMsgId: _2, replyToPeerId: _3, replyFrom: _4, replyMedia: _5, replyToTopId: _6, quoteText: _7, quoteEntities: _8, quoteOffset: _9, todoItemId: _10) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + return Api.MessageReplyHeader.messageReplyHeader(flags: _1!, replyToMsgId: _2, replyToPeerId: _3, replyFrom: _4, replyMedia: _5, replyToTopId: _6, quoteText: _7, quoteEntities: _8, quoteOffset: _9, todoItemId: _10) } public static func parse_messageReplyStoryHeader(_ reader: BufferReader) -> MessageReplyHeader? { var _1: Api.Peer? @@ -476,12 +469,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageReplyHeader.messageReplyStoryHeader(peer: _1!, storyId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageReplyHeader.messageReplyStoryHeader(peer: _1!, storyId: _2!) } } @@ -516,12 +506,9 @@ public extension Api { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageReportOption.messageReportOption(text: _1!, option: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MessageReportOption.messageReportOption(text: _1!, option: _2!) } } @@ -566,12 +553,11 @@ public extension Api { let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageViews.messageViews(flags: _1!, views: _2, forwards: _3, replies: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.MessageViews.messageViews(flags: _1!, views: _2, forwards: _3, replies: _4) } } @@ -770,12 +756,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.MessagesFilter.inputMessagesFilterPhoneCalls(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.MessagesFilter.inputMessagesFilterPhoneCalls(flags: _1!) } public static func parse_inputMessagesFilterPhotoVideo(_ reader: BufferReader) -> MessagesFilter? { return Api.MessagesFilter.inputMessagesFilterPhotoVideo @@ -834,12 +816,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MissingInvitee.missingInvitee(flags: _1!, userId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.MissingInvitee.missingInvitee(flags: _1!, userId: _2!) } } @@ -892,12 +871,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MyBoost.myBoost(flags: _1!, slot: _2!, peer: _3, date: _4!, expires: _5!, cooldownUntilDate: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.MyBoost.myBoost(flags: _1!, slot: _2!, peer: _3, date: _4!, expires: _5!, cooldownUntilDate: _6) } } @@ -936,12 +916,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.NearestDc.nearestDc(country: _1!, thisDc: _2!, nearestDc: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.NearestDc.nearestDc(country: _1!, thisDc: _2!, nearestDc: _3!) } } diff --git a/submodules/TelegramApi/Sources/Api18.swift b/submodules/TelegramApi/Sources/Api18.swift index 11ea4e2a..f14c2c27 100644 --- a/submodules/TelegramApi/Sources/Api18.swift +++ b/submodules/TelegramApi/Sources/Api18.swift @@ -58,12 +58,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.NotificationSound.notificationSoundLocal(title: _1!, data: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.NotificationSound.notificationSoundLocal(title: _1!, data: _2!) } public static func parse_notificationSoundNone(_ reader: BufferReader) -> NotificationSound? { return Api.NotificationSound.notificationSoundNone @@ -72,12 +69,8 @@ public extension Api { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.NotificationSound.notificationSoundRingtone(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.NotificationSound.notificationSoundRingtone(id: _1!) } } @@ -156,12 +149,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.NotifyPeer.notifyForumTopic(peer: _1!, topMsgId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.NotifyPeer.notifyForumTopic(peer: _1!, topMsgId: _2!) } public static func parse_notifyPeer(_ reader: BufferReader) -> NotifyPeer? { var _1: Api.Peer? @@ -169,12 +159,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Peer } let _c1 = _1 != nil - if _c1 { - return Api.NotifyPeer.notifyPeer(peer: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.NotifyPeer.notifyPeer(peer: _1!) } public static func parse_notifyUsers(_ reader: BufferReader) -> NotifyPeer? { return Api.NotifyPeer.notifyUsers @@ -208,12 +194,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.OutboxReadDate.outboxReadDate(date: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.OutboxReadDate.outboxReadDate(date: _1!) } } @@ -282,12 +264,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Page.page(flags: _1!, url: _2!, blocks: _3!, photos: _4!, documents: _5!, views: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.Page.page(flags: _1!, url: _2!, blocks: _3!, photos: _4!, documents: _5!, views: _6) } } @@ -636,12 +619,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockAnchor(name: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockAnchor(name: _1!) } public static func parse_pageBlockAudio(_ reader: BufferReader) -> PageBlock? { var _1: Int64? @@ -652,12 +631,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockAudio(audioId: _1!, caption: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageBlock.pageBlockAudio(audioId: _1!, caption: _2!) } public static func parse_pageBlockAuthorDate(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -668,12 +644,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockAuthorDate(author: _1!, publishedDate: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageBlock.pageBlockAuthorDate(author: _1!, publishedDate: _2!) } public static func parse_pageBlockBlockquote(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -686,12 +659,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockBlockquote(text: _1!, caption: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageBlock.pageBlockBlockquote(text: _1!, caption: _2!) } public static func parse_pageBlockChannel(_ reader: BufferReader) -> PageBlock? { var _1: Api.Chat? @@ -699,12 +669,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Chat } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockChannel(channel: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockChannel(channel: _1!) } public static func parse_pageBlockCollage(_ reader: BufferReader) -> PageBlock? { var _1: [Api.PageBlock]? @@ -717,12 +683,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockCollage(items: _1!, caption: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageBlock.pageBlockCollage(items: _1!, caption: _2!) } public static func parse_pageBlockCover(_ reader: BufferReader) -> PageBlock? { var _1: Api.PageBlock? @@ -730,12 +693,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.PageBlock } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockCover(cover: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockCover(cover: _1!) } public static func parse_pageBlockDetails(_ reader: BufferReader) -> PageBlock? { var _1: Int32? @@ -751,12 +710,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PageBlock.pageBlockDetails(flags: _1!, blocks: _2!, title: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.PageBlock.pageBlockDetails(flags: _1!, blocks: _2!, title: _3!) } public static func parse_pageBlockDivider(_ reader: BufferReader) -> PageBlock? { return Api.PageBlock.pageBlockDivider @@ -785,12 +742,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PageBlock.pageBlockEmbed(flags: _1!, url: _2, html: _3, posterPhotoId: _4, w: _5, h: _6, caption: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.PageBlock.pageBlockEmbed(flags: _1!, url: _2, html: _3, posterPhotoId: _4, w: _5, h: _6, caption: _7!) } public static func parse_pageBlockEmbedPost(_ reader: BufferReader) -> PageBlock? { var _1: String? @@ -818,12 +777,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PageBlock.pageBlockEmbedPost(url: _1!, webpageId: _2!, authorPhotoId: _3!, author: _4!, date: _5!, blocks: _6!, caption: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.PageBlock.pageBlockEmbedPost(url: _1!, webpageId: _2!, authorPhotoId: _3!, author: _4!, date: _5!, blocks: _6!, caption: _7!) } public static func parse_pageBlockFooter(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -831,12 +792,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockFooter(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockFooter(text: _1!) } public static func parse_pageBlockHeader(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -844,12 +801,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockHeader(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockHeader(text: _1!) } public static func parse_pageBlockKicker(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -857,12 +810,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockKicker(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockKicker(text: _1!) } public static func parse_pageBlockList(_ reader: BufferReader) -> PageBlock? { var _1: [Api.PageListItem]? @@ -870,12 +819,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageListItem.self) } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockList(items: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockList(items: _1!) } public static func parse_pageBlockMap(_ reader: BufferReader) -> PageBlock? { var _1: Api.GeoPoint? @@ -897,12 +842,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PageBlock.pageBlockMap(geo: _1!, zoom: _2!, w: _3!, h: _4!, caption: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.PageBlock.pageBlockMap(geo: _1!, zoom: _2!, w: _3!, h: _4!, caption: _5!) } public static func parse_pageBlockOrderedList(_ reader: BufferReader) -> PageBlock? { var _1: [Api.PageListOrderedItem]? @@ -910,12 +855,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageListOrderedItem.self) } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockOrderedList(items: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockOrderedList(items: _1!) } public static func parse_pageBlockParagraph(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -923,12 +864,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockParagraph(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockParagraph(text: _1!) } public static func parse_pageBlockPhoto(_ reader: BufferReader) -> PageBlock? { var _1: Int32? @@ -948,12 +885,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PageBlock.pageBlockPhoto(flags: _1!, photoId: _2!, caption: _3!, url: _4, webpageId: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.PageBlock.pageBlockPhoto(flags: _1!, photoId: _2!, caption: _3!, url: _4, webpageId: _5) } public static func parse_pageBlockPreformatted(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -964,12 +901,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockPreformatted(text: _1!, language: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageBlock.pageBlockPreformatted(text: _1!, language: _2!) } public static func parse_pageBlockPullquote(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -982,12 +916,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockPullquote(text: _1!, caption: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageBlock.pageBlockPullquote(text: _1!, caption: _2!) } public static func parse_pageBlockRelatedArticles(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -1000,12 +931,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockRelatedArticles(title: _1!, articles: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageBlock.pageBlockRelatedArticles(title: _1!, articles: _2!) } public static func parse_pageBlockSlideshow(_ reader: BufferReader) -> PageBlock? { var _1: [Api.PageBlock]? @@ -1018,12 +946,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockSlideshow(items: _1!, caption: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageBlock.pageBlockSlideshow(items: _1!, caption: _2!) } public static func parse_pageBlockSubheader(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -1031,12 +956,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockSubheader(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockSubheader(text: _1!) } public static func parse_pageBlockSubtitle(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -1044,12 +965,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockSubtitle(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockSubtitle(text: _1!) } public static func parse_pageBlockTable(_ reader: BufferReader) -> PageBlock? { var _1: Int32? @@ -1065,12 +982,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PageBlock.pageBlockTable(flags: _1!, title: _2!, rows: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.PageBlock.pageBlockTable(flags: _1!, title: _2!, rows: _3!) } public static func parse_pageBlockTitle(_ reader: BufferReader) -> PageBlock? { var _1: Api.RichText? @@ -1078,12 +993,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockTitle(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageBlock.pageBlockTitle(text: _1!) } public static func parse_pageBlockUnsupported(_ reader: BufferReader) -> PageBlock? { return Api.PageBlock.pageBlockUnsupported @@ -1100,12 +1011,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PageBlock.pageBlockVideo(flags: _1!, videoId: _2!, caption: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.PageBlock.pageBlockVideo(flags: _1!, videoId: _2!, caption: _3!) } } diff --git a/submodules/TelegramApi/Sources/Api19.swift b/submodules/TelegramApi/Sources/Api19.swift index 2cb2a538..76f9fd8e 100644 --- a/submodules/TelegramApi/Sources/Api19.swift +++ b/submodules/TelegramApi/Sources/Api19.swift @@ -32,12 +32,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageCaption.pageCaption(text: _1!, credit: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageCaption.pageCaption(text: _1!, credit: _2!) } } @@ -83,12 +80,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) } let _c1 = _1 != nil - if _c1 { - return Api.PageListItem.pageListItemBlocks(blocks: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageListItem.pageListItemBlocks(blocks: _1!) } public static func parse_pageListItemText(_ reader: BufferReader) -> PageListItem? { var _1: Api.RichText? @@ -96,12 +89,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.PageListItem.pageListItemText(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageListItem.pageListItemText(text: _1!) } } @@ -152,12 +141,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageListOrderedItem.pageListOrderedItemBlocks(num: _1!, blocks: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageListOrderedItem.pageListOrderedItemBlocks(num: _1!, blocks: _2!) } public static func parse_pageListOrderedItemText(_ reader: BufferReader) -> PageListOrderedItem? { var _1: String? @@ -168,12 +154,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageListOrderedItem.pageListOrderedItemText(num: _1!, text: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PageListOrderedItem.pageListOrderedItemText(num: _1!, text: _2!) } } @@ -232,12 +215,15 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PageRelatedArticle.pageRelatedArticle(flags: _1!, url: _2!, webpageId: _3!, title: _4, description: _5, photoId: _6, author: _7, publishedDate: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.PageRelatedArticle.pageRelatedArticle(flags: _1!, url: _2!, webpageId: _3!, title: _4, description: _5, photoId: _6, author: _7, publishedDate: _8) } } @@ -282,12 +268,11 @@ public extension Api { let _c2 = (Int(_1!) & Int(1 << 7) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PageTableCell.pageTableCell(flags: _1!, text: _2, colspan: _3, rowspan: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PageTableCell.pageTableCell(flags: _1!, text: _2, colspan: _3, rowspan: _4) } } @@ -324,12 +309,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageTableCell.self) } let _c1 = _1 != nil - if _c1 { - return Api.PageTableRow.pageTableRow(cells: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PageTableRow.pageTableRow(cells: _1!) } } @@ -386,12 +367,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputPeer } let _c1 = _1 != nil - if _c1 { - return Api.PaidReactionPrivacy.paidReactionPrivacyPeer(peer: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PaidReactionPrivacy.paidReactionPrivacyPeer(peer: _1!) } } @@ -442,12 +419,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Passkey.passkey(flags: _1!, id: _2!, name: _3!, date: _4!, softwareEmojiId: _5, lastUsageDate: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.Passkey.passkey(flags: _1!, id: _2!, name: _3!, date: _4!, softwareEmojiId: _5, lastUsageDate: _6) } } @@ -499,12 +477,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PasswordKdfAlgo.passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1: _1!, salt2: _2!, g: _3!, p: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PasswordKdfAlgo.passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1: _1!, salt2: _2!, g: _3!, p: _4!) } public static func parse_passwordKdfAlgoUnknown(_ reader: BufferReader) -> PasswordKdfAlgo? { return Api.PasswordKdfAlgo.passwordKdfAlgoUnknown @@ -542,12 +519,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PaymentCharge.paymentCharge(id: _1!, providerChargeId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PaymentCharge.paymentCharge(id: _1!, providerChargeId: _2!) } } @@ -582,12 +556,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PaymentFormMethod.paymentFormMethod(url: _1!, title: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PaymentFormMethod.paymentFormMethod(url: _1!, title: _2!) } } @@ -636,12 +607,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PaymentRequestedInfo.paymentRequestedInfo(flags: _1!, name: _2, phone: _3, email: _4, shippingAddress: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.PaymentRequestedInfo.paymentRequestedInfo(flags: _1!, name: _2, phone: _3, email: _4, shippingAddress: _5) } } @@ -676,12 +647,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PaymentSavedCredentials.paymentSavedCredentialsCard(id: _1!, title: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PaymentSavedCredentials.paymentSavedCredentialsCard(id: _1!, title: _2!) } } @@ -730,34 +698,22 @@ public extension Api { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Peer.peerChannel(channelId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Peer.peerChannel(channelId: _1!) } public static func parse_peerChat(_ reader: BufferReader) -> Peer? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Peer.peerChat(chatId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Peer.peerChat(chatId: _1!) } public static func parse_peerUser(_ reader: BufferReader) -> Peer? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Peer.peerUser(userId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Peer.peerUser(userId: _1!) } } @@ -794,12 +750,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PeerBlocked.peerBlocked(peerId: _1!, date: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PeerBlocked.peerBlocked(peerId: _1!, date: _2!) } } @@ -865,12 +818,8 @@ public extension Api { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.PeerColor.inputPeerColorCollectible(collectibleId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PeerColor.inputPeerColorCollectible(collectibleId: _1!) } public static func parse_peerColor(_ reader: BufferReader) -> PeerColor? { var _1: Int32? @@ -882,12 +831,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.PeerColor.peerColor(flags: _1!, color: _2, backgroundEmojiId: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.PeerColor.peerColor(flags: _1!, color: _2, backgroundEmojiId: _3) } public static func parse_peerColorCollectible(_ reader: BufferReader) -> PeerColor? { var _1: Int32? @@ -918,12 +865,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PeerColor.peerColorCollectible(flags: _1!, collectibleId: _2!, giftEmojiId: _3!, backgroundEmojiId: _4!, accentColor: _5!, colors: _6!, darkAccentColor: _7, darkColors: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.PeerColor.peerColorCollectible(flags: _1!, collectibleId: _2!, giftEmojiId: _3!, backgroundEmojiId: _4!, accentColor: _5!, colors: _6!, darkAccentColor: _7, darkColors: _8) } } @@ -973,23 +923,17 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PeerLocated.peerLocated(peer: _1!, expires: _2!, distance: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.PeerLocated.peerLocated(peer: _1!, expires: _2!, distance: _3!) } public static func parse_peerSelfLocated(_ reader: BufferReader) -> PeerLocated? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.PeerLocated.peerSelfLocated(expires: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PeerLocated.peerSelfLocated(expires: _1!) } } @@ -1084,12 +1028,19 @@ public extension Api { let _c10 = (Int(_1!) & Int(1 << 8) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 9) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 10) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.PeerNotifySettings.peerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, iosSound: _5, androidSound: _6, otherSound: _7, storiesMuted: _8, storiesHideSender: _9, storiesIosSound: _10, storiesAndroidSound: _11, storiesOtherSound: _12) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + return Api.PeerNotifySettings.peerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, iosSound: _5, androidSound: _6, otherSound: _7, storiesMuted: _8, storiesHideSender: _9, storiesIosSound: _10, storiesAndroidSound: _11, storiesOtherSound: _12) } } @@ -1160,12 +1111,18 @@ public extension Api { let _c9 = (Int(_1!) & Int(1 << 16) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 17) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 18) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6, chargePaidMessageStars: _7, registrationMonth: _8, phoneCountry: _9, nameChangeDate: _10, photoChangeDate: _11) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6, chargePaidMessageStars: _7, registrationMonth: _8, phoneCountry: _9, nameChangeDate: _10, photoChangeDate: _11) } } @@ -1216,12 +1173,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PeerStories.peerStories(flags: _1!, peer: _2!, maxReadId: _3, stories: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PeerStories.peerStories(flags: _1!, peer: _2!, maxReadId: _3, stories: _4!) } } @@ -1268,12 +1224,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PendingSuggestion.pendingSuggestion(suggestion: _1!, title: _2!, description: _3!, url: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PendingSuggestion.pendingSuggestion(suggestion: _1!, title: _2!, description: _3!, url: _4!) } } @@ -1427,12 +1382,19 @@ public extension Api { let _c10 = _10 != nil let _c11 = _11 != nil let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.PhoneCall.phoneCall(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAOrB: _7!, keyFingerprint: _8!, protocol: _9!, connections: _10!, startDate: _11!, customParameters: _12) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + return Api.PhoneCall.phoneCall(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAOrB: _7!, keyFingerprint: _8!, protocol: _9!, connections: _10!, startDate: _11!, customParameters: _12) } public static func parse_phoneCallAccepted(_ reader: BufferReader) -> PhoneCall? { var _1: Int32? @@ -1461,12 +1423,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.PhoneCall.phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!) } public static func parse_phoneCallDiscarded(_ reader: BufferReader) -> PhoneCall? { var _1: Int32? @@ -1483,23 +1448,18 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhoneCall.phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PhoneCall.phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4) } public static func parse_phoneCallEmpty(_ reader: BufferReader) -> PhoneCall? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.PhoneCall.phoneCallEmpty(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PhoneCall.phoneCallEmpty(id: _1!) } public static func parse_phoneCallRequested(_ reader: BufferReader) -> PhoneCall? { var _1: Int32? @@ -1528,12 +1488,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.PhoneCall.phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!) } public static func parse_phoneCallWaiting(_ reader: BufferReader) -> PhoneCall? { var _1: Int32? @@ -1562,12 +1525,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.PhoneCall.phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8) } } diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index 2c7da602..3bc728b1 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -46,12 +46,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.BotBusinessConnection.botBusinessConnection(flags: _1!, connectionId: _2!, userId: _3!, dcId: _4!, date: _5!, rights: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.BotBusinessConnection.botBusinessConnection(flags: _1!, connectionId: _2!, userId: _3!, dcId: _4!, date: _5!, rights: _6) } } @@ -86,12 +87,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.BotCommand.botCommand(command: _1!, description: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.BotCommand.botCommand(command: _1!, description: _2!) } } @@ -188,12 +186,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputPeer } let _c1 = _1 != nil - if _c1 { - return Api.BotCommandScope.botCommandScopePeer(peer: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.BotCommandScope.botCommandScopePeer(peer: _1!) } public static func parse_botCommandScopePeerAdmins(_ reader: BufferReader) -> BotCommandScope? { var _1: Api.InputPeer? @@ -201,12 +195,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputPeer } let _c1 = _1 != nil - if _c1 { - return Api.BotCommandScope.botCommandScopePeerAdmins(peer: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.BotCommandScope.botCommandScopePeerAdmins(peer: _1!) } public static func parse_botCommandScopePeerUser(_ reader: BufferReader) -> BotCommandScope? { var _1: Api.InputPeer? @@ -219,12 +209,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.BotCommandScope.botCommandScopePeerUser(peer: _1!, userId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.BotCommandScope.botCommandScopePeerUser(peer: _1!, userId: _2!) } public static func parse_botCommandScopeUsers(_ reader: BufferReader) -> BotCommandScope? { return Api.BotCommandScope.botCommandScopeUsers @@ -310,12 +297,17 @@ public extension Api { let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 9) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.BotInfo.botInfo(flags: _1!, userId: _2, description: _3, descriptionPhoto: _4, descriptionDocument: _5, commands: _6, menuButton: _7, privacyPolicyUrl: _8, appSettings: _9, verifierSettings: _10) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + return Api.BotInfo.botInfo(flags: _1!, userId: _2, description: _3, descriptionPhoto: _4, descriptionDocument: _5, commands: _6, menuButton: _7, privacyPolicyUrl: _8, appSettings: _9, verifierSettings: _10) } } @@ -458,12 +450,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.BotInlineMessage.botInlineMessageMediaAuto(flags: _1!, message: _2!, entities: _3, replyMarkup: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.BotInlineMessage.botInlineMessageMediaAuto(flags: _1!, message: _2!, entities: _3, replyMarkup: _4) } public static func parse_botInlineMessageMediaContact(_ reader: BufferReader) -> BotInlineMessage? { var _1: Int32? @@ -486,12 +477,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.BotInlineMessage.botInlineMessageMediaContact(flags: _1!, phoneNumber: _2!, firstName: _3!, lastName: _4!, vcard: _5!, replyMarkup: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.BotInlineMessage.botInlineMessageMediaContact(flags: _1!, phoneNumber: _2!, firstName: _3!, lastName: _4!, vcard: _5!, replyMarkup: _6) } public static func parse_botInlineMessageMediaGeo(_ reader: BufferReader) -> BotInlineMessage? { var _1: Int32? @@ -516,12 +508,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.BotInlineMessage.botInlineMessageMediaGeo(flags: _1!, geo: _2!, heading: _3, period: _4, proximityNotificationRadius: _5, replyMarkup: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.BotInlineMessage.botInlineMessageMediaGeo(flags: _1!, geo: _2!, heading: _3, period: _4, proximityNotificationRadius: _5, replyMarkup: _6) } public static func parse_botInlineMessageMediaInvoice(_ reader: BufferReader) -> BotInlineMessage? { var _1: Int32? @@ -549,12 +542,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.BotInlineMessage.botInlineMessageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, currency: _5!, totalAmount: _6!, replyMarkup: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.BotInlineMessage.botInlineMessageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, currency: _5!, totalAmount: _6!, replyMarkup: _7) } public static func parse_botInlineMessageMediaVenue(_ reader: BufferReader) -> BotInlineMessage? { var _1: Int32? @@ -585,12 +580,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.BotInlineMessage.botInlineMessageMediaVenue(flags: _1!, geo: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!, venueType: _7!, replyMarkup: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.BotInlineMessage.botInlineMessageMediaVenue(flags: _1!, geo: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!, venueType: _7!, replyMarkup: _8) } public static func parse_botInlineMessageMediaWebPage(_ reader: BufferReader) -> BotInlineMessage? { var _1: Int32? @@ -612,12 +610,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.BotInlineMessage.botInlineMessageMediaWebPage(flags: _1!, message: _2!, entities: _3, url: _4!, replyMarkup: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.BotInlineMessage.botInlineMessageMediaWebPage(flags: _1!, message: _2!, entities: _3, url: _4!, replyMarkup: _5) } public static func parse_botInlineMessageText(_ reader: BufferReader) -> BotInlineMessage? { var _1: Int32? @@ -636,12 +634,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.BotInlineMessage.botInlineMessageText(flags: _1!, message: _2!, entities: _3, replyMarkup: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.BotInlineMessage.botInlineMessageText(flags: _1!, message: _2!, entities: _3, replyMarkup: _4) } } @@ -723,12 +720,15 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.BotInlineResult.botInlineMediaResult(flags: _1!, id: _2!, type: _3!, photo: _4, document: _5, title: _6, description: _7, sendMessage: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.BotInlineResult.botInlineMediaResult(flags: _1!, id: _2!, type: _3!, photo: _4, document: _5, title: _6, description: _7, sendMessage: _8!) } public static func parse_botInlineResult(_ reader: BufferReader) -> BotInlineResult? { var _1: Int32? @@ -764,12 +764,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.BotInlineResult.botInlineResult(flags: _1!, id: _2!, type: _3!, title: _4, description: _5, url: _6, thumb: _7, content: _8, sendMessage: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.BotInlineResult.botInlineResult(flags: _1!, id: _2!, type: _3!, title: _4, description: _5, url: _6, thumb: _7, content: _8, sendMessage: _9!) } } @@ -822,12 +826,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.BotMenuButton.botMenuButton(text: _1!, url: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.BotMenuButton.botMenuButton(text: _1!, url: _2!) } public static func parse_botMenuButtonCommands(_ reader: BufferReader) -> BotMenuButton? { return Api.BotMenuButton.botMenuButtonCommands @@ -870,12 +871,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.BotPreviewMedia.botPreviewMedia(date: _1!, media: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.BotPreviewMedia.botPreviewMedia(date: _1!, media: _2!) } } @@ -914,12 +912,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.BotVerification.botVerification(botId: _1!, icon: _2!, description: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.BotVerification.botVerification(botId: _1!, icon: _2!, description: _3!) } } @@ -962,12 +958,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.BotVerifierSettings.botVerifierSettings(flags: _1!, icon: _2!, company: _3!, customDescription: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.BotVerifierSettings.botVerifierSettings(flags: _1!, icon: _2!, company: _3!, customDescription: _4) } } @@ -1014,12 +1009,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.BusinessAwayMessage.businessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, recipients: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.BusinessAwayMessage.businessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, recipients: _4!) } } @@ -1075,12 +1069,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.BusinessAwayMessageSchedule.businessAwayMessageScheduleCustom(startDate: _1!, endDate: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.BusinessAwayMessageSchedule.businessAwayMessageScheduleCustom(startDate: _1!, endDate: _2!) } public static func parse_businessAwayMessageScheduleOutsideWorkHours(_ reader: BufferReader) -> BusinessAwayMessageSchedule? { return Api.BusinessAwayMessageSchedule.businessAwayMessageScheduleOutsideWorkHours @@ -1134,12 +1125,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 6) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.BusinessBotRecipients.businessBotRecipients(flags: _1!, users: _2, excludeUsers: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.BusinessBotRecipients.businessBotRecipients(flags: _1!, users: _2, excludeUsers: _3) } } @@ -1170,12 +1159,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.BusinessBotRights.businessBotRights(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.BusinessBotRights.businessBotRights(flags: _1!) } } @@ -1232,12 +1217,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.BusinessChatLink.businessChatLink(flags: _1!, link: _2!, message: _3!, entities: _4, title: _5, views: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.BusinessChatLink.businessChatLink(flags: _1!, link: _2!, message: _3!, entities: _4, title: _5, views: _6!) } } diff --git a/submodules/TelegramApi/Sources/Api20.swift b/submodules/TelegramApi/Sources/Api20.swift index 27e9c338..fff136f0 100644 --- a/submodules/TelegramApi/Sources/Api20.swift +++ b/submodules/TelegramApi/Sources/Api20.swift @@ -69,12 +69,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMigrateConferenceCall(slug: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMigrateConferenceCall(slug: _1!) } public static func parse_phoneCallDiscardReasonMissed(_ reader: BufferReader) -> PhoneCallDiscardReason? { return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMissed @@ -126,12 +122,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhoneCallProtocol.phoneCallProtocol(flags: _1!, minLayer: _2!, maxLayer: _3!, libraryVersions: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PhoneCallProtocol.phoneCallProtocol(flags: _1!, minLayer: _2!, maxLayer: _3!, libraryVersions: _4!) } } @@ -197,12 +192,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PhoneConnection.phoneConnection(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, peerTag: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.PhoneConnection.phoneConnection(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, peerTag: _6!) } public static func parse_phoneConnectionWebrtc(_ reader: BufferReader) -> PhoneConnection? { var _1: Int32? @@ -226,12 +222,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PhoneConnection.phoneConnectionWebrtc(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, username: _6!, password: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.PhoneConnection.phoneConnectionWebrtc(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, username: _6!, password: _7!) } } @@ -311,23 +309,22 @@ public extension Api { let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Photo.photo(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, sizes: _6!, videoSizes: _7, dcId: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.Photo.photo(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, sizes: _6!, videoSizes: _7, dcId: _8!) } public static func parse_photoEmpty(_ reader: BufferReader) -> Photo? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Photo.photoEmpty(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Photo.photoEmpty(id: _1!) } } @@ -427,12 +424,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhotoSize.photoCachedSize(type: _1!, w: _2!, h: _3!, bytes: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PhotoSize.photoCachedSize(type: _1!, w: _2!, h: _3!, bytes: _4!) } public static func parse_photoPathSize(_ reader: BufferReader) -> PhotoSize? { var _1: String? @@ -441,12 +437,9 @@ public extension Api { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PhotoSize.photoPathSize(type: _1!, bytes: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PhotoSize.photoPathSize(type: _1!, bytes: _2!) } public static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { var _1: String? @@ -461,23 +454,18 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhotoSize.photoSize(type: _1!, w: _2!, h: _3!, size: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PhotoSize.photoSize(type: _1!, w: _2!, h: _3!, size: _4!) } public static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.PhotoSize.photoSizeEmpty(type: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PhotoSize.photoSizeEmpty(type: _1!) } public static func parse_photoSizeProgressive(_ reader: BufferReader) -> PhotoSize? { var _1: String? @@ -494,12 +482,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhotoSize.photoSizeProgressive(type: _1!, w: _2!, h: _3!, sizes: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PhotoSize.photoSizeProgressive(type: _1!, w: _2!, h: _3!, sizes: _4!) } public static func parse_photoStrippedSize(_ reader: BufferReader) -> PhotoSize? { var _1: String? @@ -508,12 +495,9 @@ public extension Api { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PhotoSize.photoStrippedSize(type: _1!, bytes: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PhotoSize.photoStrippedSize(type: _1!, bytes: _2!) } } @@ -572,12 +556,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = (Int(_2!) & Int(1 << 4) == 0) || _5 != nil let _c6 = (Int(_2!) & Int(1 << 5) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Poll.poll(id: _1!, flags: _2!, question: _3!, answers: _4!, closePeriod: _5, closeDate: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.Poll.poll(id: _1!, flags: _2!, question: _3!, answers: _4!, closePeriod: _5, closeDate: _6) } } @@ -614,12 +599,9 @@ public extension Api { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PollAnswer.pollAnswer(text: _1!, option: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PollAnswer.pollAnswer(text: _1!, option: _2!) } } @@ -658,12 +640,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PollAnswerVoters.pollAnswerVoters(flags: _1!, option: _2!, voters: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.PollAnswerVoters.pollAnswerVoters(flags: _1!, option: _2!, voters: _3!) } } @@ -732,12 +712,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PollResults.pollResults(flags: _1!, results: _2, totalVoters: _3, recentVoters: _4, solution: _5, solutionEntities: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.PollResults.pollResults(flags: _1!, results: _2, totalVoters: _3, recentVoters: _4, solution: _5, solutionEntities: _6) } } @@ -772,12 +753,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PopularContact.popularContact(clientId: _1!, importers: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PopularContact.popularContact(clientId: _1!, importers: _2!) } } @@ -828,12 +806,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PostAddress.postAddress(streetLine1: _1!, streetLine2: _2!, city: _3!, state: _4!, countryIso2: _5!, postCode: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.PostAddress.postAddress(streetLine1: _1!, streetLine2: _2!, city: _3!, state: _4!, countryIso2: _5!, postCode: _6!) } } @@ -888,12 +867,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PostInteractionCounters.postInteractionCountersMessage(msgId: _1!, views: _2!, forwards: _3!, reactions: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PostInteractionCounters.postInteractionCountersMessage(msgId: _1!, views: _2!, forwards: _3!, reactions: _4!) } public static func parse_postInteractionCountersStory(_ reader: BufferReader) -> PostInteractionCounters? { var _1: Int32? @@ -908,12 +886,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PostInteractionCounters.postInteractionCountersStory(storyId: _1!, views: _2!, forwards: _3!, reactions: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PostInteractionCounters.postInteractionCountersStory(storyId: _1!, views: _2!, forwards: _3!, reactions: _4!) } } @@ -968,12 +945,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PremiumGiftCodeOption.premiumGiftCodeOption(flags: _1!, users: _2!, months: _3!, storeProduct: _4, storeQuantity: _5, currency: _6!, amount: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.PremiumGiftCodeOption.premiumGiftCodeOption(flags: _1!, users: _2!, months: _3!, storeProduct: _4, storeQuantity: _5, currency: _6!, amount: _7!) } } @@ -1028,12 +1007,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PremiumSubscriptionOption.premiumSubscriptionOption(flags: _1!, transaction: _2, months: _3!, currency: _4!, amount: _5!, botUrl: _6!, storeProduct: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.PremiumSubscriptionOption.premiumSubscriptionOption(flags: _1!, transaction: _2, months: _3!, currency: _4!, amount: _5!, botUrl: _6!, storeProduct: _7) } } @@ -1089,12 +1070,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PrepaidGiveaway.prepaidGiveaway(id: _1!, months: _2!, quantity: _3!, date: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.PrepaidGiveaway.prepaidGiveaway(id: _1!, months: _2!, quantity: _3!, date: _4!) } public static func parse_prepaidStarsGiveaway(_ reader: BufferReader) -> PrepaidGiveaway? { var _1: Int64? @@ -1112,12 +1092,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PrepaidGiveaway.prepaidStarsGiveaway(id: _1!, stars: _2!, quantity: _3!, boosts: _4!, date: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.PrepaidGiveaway.prepaidStarsGiveaway(id: _1!, stars: _2!, quantity: _3!, boosts: _4!, date: _5!) } } diff --git a/submodules/TelegramApi/Sources/Api21.swift b/submodules/TelegramApi/Sources/Api21.swift index 9d4e9060..a40db16a 100644 --- a/submodules/TelegramApi/Sources/Api21.swift +++ b/submodules/TelegramApi/Sources/Api21.swift @@ -147,12 +147,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueAllowChatParticipants(chats: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PrivacyRule.privacyValueAllowChatParticipants(chats: _1!) } public static func parse_privacyValueAllowCloseFriends(_ reader: BufferReader) -> PrivacyRule? { return Api.PrivacyRule.privacyValueAllowCloseFriends @@ -169,12 +165,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueAllowUsers(users: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PrivacyRule.privacyValueAllowUsers(users: _1!) } public static func parse_privacyValueDisallowAll(_ reader: BufferReader) -> PrivacyRule? { return Api.PrivacyRule.privacyValueDisallowAll @@ -188,12 +180,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueDisallowChatParticipants(chats: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PrivacyRule.privacyValueDisallowChatParticipants(chats: _1!) } public static func parse_privacyValueDisallowContacts(_ reader: BufferReader) -> PrivacyRule? { return Api.PrivacyRule.privacyValueDisallowContacts @@ -204,12 +192,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueDisallowUsers(users: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PrivacyRule.privacyValueDisallowUsers(users: _1!) } } @@ -364,12 +348,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Message } let _c1 = _1 != nil - if _c1 { - return Api.PublicForward.publicForwardMessage(message: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.PublicForward.publicForwardMessage(message: _1!) } public static func parse_publicForwardStory(_ reader: BufferReader) -> PublicForward? { var _1: Api.Peer? @@ -382,12 +362,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PublicForward.publicForwardStory(peer: _1!, story: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.PublicForward.publicForwardStory(peer: _1!, story: _2!) } } @@ -430,12 +407,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.QuickReply.quickReply(shortcutId: _1!, shortcut: _2!, topMessage: _3!, count: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.QuickReply.quickReply(shortcutId: _1!, shortcut: _2!, topMessage: _3!, count: _4!) } } @@ -493,23 +469,15 @@ public extension Api { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Reaction.reactionCustomEmoji(documentId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Reaction.reactionCustomEmoji(documentId: _1!) } public static func parse_reactionEmoji(_ reader: BufferReader) -> Reaction? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.Reaction.reactionEmoji(emoticon: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Reaction.reactionEmoji(emoticon: _1!) } public static func parse_reactionEmpty(_ reader: BufferReader) -> Reaction? { return Api.Reaction.reactionEmpty @@ -560,12 +528,11 @@ public extension Api { let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.ReactionCount.reactionCount(flags: _1!, chosenOrder: _2, reaction: _3!, count: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.ReactionCount.reactionCount(flags: _1!, chosenOrder: _2, reaction: _3!, count: _4!) } } @@ -660,12 +627,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.ReactionsNotifySettings.reactionsNotifySettings(flags: _1!, messagesNotifyFrom: _2, storiesNotifyFrom: _3, sound: _4!, showPreviews: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.ReactionsNotifySettings.reactionsNotifySettings(flags: _1!, messagesNotifyFrom: _2, storiesNotifyFrom: _3, sound: _4!, showPreviews: _5!) } } @@ -700,12 +667,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ReadParticipantDate.readParticipantDate(userId: _1!, date: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ReadParticipantDate.readParticipantDate(userId: _1!, date: _2!) } } diff --git a/submodules/TelegramApi/Sources/Api22.swift b/submodules/TelegramApi/Sources/Api22.swift index db61ed18..31bf060e 100644 --- a/submodules/TelegramApi/Sources/Api22.swift +++ b/submodules/TelegramApi/Sources/Api22.swift @@ -28,12 +28,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ReceivedNotifyMessage.receivedNotifyMessage(id: _1!, flags: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ReceivedNotifyMessage.receivedNotifyMessage(id: _1!, flags: _2!) } } @@ -107,12 +104,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlChat(url: _1!, chatId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.RecentMeUrl.recentMeUrlChat(url: _1!, chatId: _2!) } public static func parse_recentMeUrlChatInvite(_ reader: BufferReader) -> RecentMeUrl? { var _1: String? @@ -123,12 +117,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlChatInvite(url: _1!, chatInvite: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.RecentMeUrl.recentMeUrlChatInvite(url: _1!, chatInvite: _2!) } public static func parse_recentMeUrlStickerSet(_ reader: BufferReader) -> RecentMeUrl? { var _1: String? @@ -139,23 +130,16 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlStickerSet(url: _1!, set: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.RecentMeUrl.recentMeUrlStickerSet(url: _1!, set: _2!) } public static func parse_recentMeUrlUnknown(_ reader: BufferReader) -> RecentMeUrl? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.RecentMeUrl.recentMeUrlUnknown(url: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RecentMeUrl.recentMeUrlUnknown(url: _1!) } public static func parse_recentMeUrlUser(_ reader: BufferReader) -> RecentMeUrl? { var _1: String? @@ -164,12 +148,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlUser(url: _1!, userId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.RecentMeUrl.recentMeUrlUser(url: _1!, userId: _2!) } } @@ -204,12 +185,9 @@ public extension Api { if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - if _c1 && _c2 { - return Api.RecentStory.recentStory(flags: _1!, maxId: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.RecentStory.recentStory(flags: _1!, maxId: _2) } } @@ -280,12 +258,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButtonRow.self) } let _c1 = _1 != nil - if _c1 { - return Api.ReplyMarkup.replyInlineMarkup(rows: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ReplyMarkup.replyInlineMarkup(rows: _1!) } public static func parse_replyKeyboardForceReply(_ reader: BufferReader) -> ReplyMarkup? { var _1: Int32? @@ -294,23 +268,16 @@ public extension Api { if Int(_1!) & Int(1 << 3) != 0 {_2 = parseString(reader) } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - if _c1 && _c2 { - return Api.ReplyMarkup.replyKeyboardForceReply(flags: _1!, placeholder: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ReplyMarkup.replyKeyboardForceReply(flags: _1!, placeholder: _2) } public static func parse_replyKeyboardHide(_ reader: BufferReader) -> ReplyMarkup? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.ReplyMarkup.replyKeyboardHide(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ReplyMarkup.replyKeyboardHide(flags: _1!) } public static func parse_replyKeyboardMarkup(_ reader: BufferReader) -> ReplyMarkup? { var _1: Int32? @@ -324,12 +291,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.ReplyMarkup.replyKeyboardMarkup(flags: _1!, rows: _2!, placeholder: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.ReplyMarkup.replyKeyboardMarkup(flags: _1!, rows: _2!, placeholder: _3) } } @@ -523,12 +488,9 @@ public extension Api { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ReportResult.reportResultAddComment(flags: _1!, option: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ReportResult.reportResultAddComment(flags: _1!, option: _2!) } public static func parse_reportResultChooseOption(_ reader: BufferReader) -> ReportResult? { var _1: String? @@ -539,12 +501,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ReportResult.reportResultChooseOption(title: _1!, options: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ReportResult.reportResultChooseOption(title: _1!, options: _2!) } public static func parse_reportResultReported(_ reader: BufferReader) -> ReportResult? { return Api.ReportResult.reportResultReported @@ -620,12 +579,11 @@ public extension Api { let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.RequestPeerType.requestPeerTypeBroadcast(flags: _1!, hasUsername: _2, userAdminRights: _3, botAdminRights: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.RequestPeerType.requestPeerTypeBroadcast(flags: _1!, hasUsername: _2, userAdminRights: _3, botAdminRights: _4) } public static func parse_requestPeerTypeChat(_ reader: BufferReader) -> RequestPeerType? { var _1: Int32? @@ -651,12 +609,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 4) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.RequestPeerType.requestPeerTypeChat(flags: _1!, hasUsername: _2, forum: _3, userAdminRights: _4, botAdminRights: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.RequestPeerType.requestPeerTypeChat(flags: _1!, hasUsername: _2, forum: _3, userAdminRights: _4, botAdminRights: _5) } public static func parse_requestPeerTypeUser(_ reader: BufferReader) -> RequestPeerType? { var _1: Int32? @@ -672,12 +630,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.RequestPeerType.requestPeerTypeUser(flags: _1!, bot: _2, premium: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.RequestPeerType.requestPeerTypeUser(flags: _1!, bot: _2, premium: _3) } } @@ -752,12 +708,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.RequestedPeer.requestedPeerChannel(flags: _1!, channelId: _2!, title: _3, username: _4, photo: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.RequestedPeer.requestedPeerChannel(flags: _1!, channelId: _2!, title: _3, username: _4, photo: _5) } public static func parse_requestedPeerChat(_ reader: BufferReader) -> RequestedPeer? { var _1: Int32? @@ -774,12 +730,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.RequestedPeer.requestedPeerChat(flags: _1!, chatId: _2!, title: _3, photo: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.RequestedPeer.requestedPeerChat(flags: _1!, chatId: _2!, title: _3, photo: _4) } public static func parse_requestedPeerUser(_ reader: BufferReader) -> RequestedPeer? { var _1: Int32? @@ -802,12 +757,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.RequestedPeer.requestedPeerUser(flags: _1!, userId: _2!, firstName: _3, lastName: _4, username: _5, photo: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.RequestedPeer.requestedPeerUser(flags: _1!, userId: _2!, firstName: _3, lastName: _4, username: _5, photo: _6) } } @@ -859,12 +815,8 @@ public extension Api { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.RequirementToContact.requirementToContactPaidMessages(starsAmount: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RequirementToContact.requirementToContactPaidMessages(starsAmount: _1!) } public static func parse_requirementToContactPremium(_ reader: BufferReader) -> RequirementToContact? { return Api.RequirementToContact.requirementToContactPremium diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index de67bb1f..84004762 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -32,12 +32,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.RestrictionReason.restrictionReason(platform: _1!, reason: _2!, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.RestrictionReason.restrictionReason(platform: _1!, reason: _2!, text: _3!) } } @@ -219,12 +217,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RichText.textAnchor(text: _1!, name: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.RichText.textAnchor(text: _1!, name: _2!) } public static func parse_textBold(_ reader: BufferReader) -> RichText? { var _1: Api.RichText? @@ -232,12 +227,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.RichText.textBold(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RichText.textBold(text: _1!) } public static func parse_textConcat(_ reader: BufferReader) -> RichText? { var _1: [Api.RichText]? @@ -245,12 +236,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RichText.self) } let _c1 = _1 != nil - if _c1 { - return Api.RichText.textConcat(texts: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RichText.textConcat(texts: _1!) } public static func parse_textEmail(_ reader: BufferReader) -> RichText? { var _1: Api.RichText? @@ -261,12 +248,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RichText.textEmail(text: _1!, email: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.RichText.textEmail(text: _1!, email: _2!) } public static func parse_textEmpty(_ reader: BufferReader) -> RichText? { return Api.RichText.textEmpty @@ -277,12 +261,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.RichText.textFixed(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RichText.textFixed(text: _1!) } public static func parse_textImage(_ reader: BufferReader) -> RichText? { var _1: Int64? @@ -294,12 +274,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.RichText.textImage(documentId: _1!, w: _2!, h: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.RichText.textImage(documentId: _1!, w: _2!, h: _3!) } public static func parse_textItalic(_ reader: BufferReader) -> RichText? { var _1: Api.RichText? @@ -307,12 +285,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.RichText.textItalic(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RichText.textItalic(text: _1!) } public static func parse_textMarked(_ reader: BufferReader) -> RichText? { var _1: Api.RichText? @@ -320,12 +294,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.RichText.textMarked(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RichText.textMarked(text: _1!) } public static func parse_textPhone(_ reader: BufferReader) -> RichText? { var _1: Api.RichText? @@ -336,23 +306,16 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RichText.textPhone(text: _1!, phone: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.RichText.textPhone(text: _1!, phone: _2!) } public static func parse_textPlain(_ reader: BufferReader) -> RichText? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.RichText.textPlain(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RichText.textPlain(text: _1!) } public static func parse_textStrike(_ reader: BufferReader) -> RichText? { var _1: Api.RichText? @@ -360,12 +323,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.RichText.textStrike(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RichText.textStrike(text: _1!) } public static func parse_textSubscript(_ reader: BufferReader) -> RichText? { var _1: Api.RichText? @@ -373,12 +332,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.RichText.textSubscript(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RichText.textSubscript(text: _1!) } public static func parse_textSuperscript(_ reader: BufferReader) -> RichText? { var _1: Api.RichText? @@ -386,12 +341,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.RichText.textSuperscript(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RichText.textSuperscript(text: _1!) } public static func parse_textUnderline(_ reader: BufferReader) -> RichText? { var _1: Api.RichText? @@ -399,12 +350,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.RichText } let _c1 = _1 != nil - if _c1 { - return Api.RichText.textUnderline(text: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.RichText.textUnderline(text: _1!) } public static func parse_textUrl(_ reader: BufferReader) -> RichText? { var _1: Api.RichText? @@ -418,12 +365,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.RichText.textUrl(text: _1!, url: _2!, webpageId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.RichText.textUrl(text: _1!, url: _2!, webpageId: _3!) } } @@ -466,12 +411,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.SavedContact.savedPhoneContact(phone: _1!, firstName: _2!, lastName: _3!, date: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.SavedContact.savedPhoneContact(phone: _1!, firstName: _2!, lastName: _3!, date: _4!) } } @@ -545,12 +489,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.SavedDialog.monoForumDialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadReactionsCount: _7!, draft: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.SavedDialog.monoForumDialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadReactionsCount: _7!, draft: _8) } public static func parse_savedDialog(_ reader: BufferReader) -> SavedDialog? { var _1: Int32? @@ -564,12 +511,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SavedDialog.savedDialog(flags: _1!, peer: _2!, topMessage: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SavedDialog.savedDialog(flags: _1!, peer: _2!, topMessage: _3!) } } @@ -614,12 +559,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.SavedReactionTag.savedReactionTag(flags: _1!, reaction: _2!, title: _3, count: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.SavedReactionTag.savedReactionTag(flags: _1!, reaction: _2!, title: _3, count: _4!) } } @@ -726,12 +670,24 @@ public extension Api { let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil let _c16 = (Int(_1!) & Int(1 << 18) == 0) || _16 != nil let _c17 = (Int(_1!) & Int(1 << 19) == 0) || _17 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { - return Api.SavedStarGift.savedStarGift(flags: _1!, fromId: _2, date: _3!, gift: _4!, message: _5, msgId: _6, savedId: _7, convertStars: _8, upgradeStars: _9, canExportAt: _10, transferStars: _11, canTransferAt: _12, canResellAt: _13, collectionId: _14, prepaidUpgradeHash: _15, dropOriginalDetailsStars: _16, giftNum: _17) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + return Api.SavedStarGift.savedStarGift(flags: _1!, fromId: _2, date: _3!, gift: _4!, message: _5, msgId: _6, savedId: _7, convertStars: _8, upgradeStars: _9, canExportAt: _10, transferStars: _11, canTransferAt: _12, canResellAt: _13, collectionId: _14, prepaidUpgradeHash: _15, dropOriginalDetailsStars: _16, giftNum: _17) } } @@ -778,12 +734,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.SearchPostsFlood.searchPostsFlood(flags: _1!, totalDaily: _2!, remains: _3!, waitTill: _4, starsAmount: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.SearchPostsFlood.searchPostsFlood(flags: _1!, totalDaily: _2!, remains: _3!, waitTill: _4, starsAmount: _5!) } } @@ -826,12 +782,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.SearchResultsCalendarPeriod.searchResultsCalendarPeriod(date: _1!, minMsgId: _2!, maxMsgId: _3!, count: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.SearchResultsCalendarPeriod.searchResultsCalendarPeriod(date: _1!, minMsgId: _2!, maxMsgId: _3!, count: _4!) } } @@ -870,12 +825,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SearchResultsPosition.searchResultPosition(msgId: _1!, date: _2!, offset: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SearchResultsPosition.searchResultPosition(msgId: _1!, date: _2!, offset: _3!) } } @@ -914,12 +867,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureCredentialsEncrypted.secureCredentialsEncrypted(data: _1!, hash: _2!, secret: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureCredentialsEncrypted.secureCredentialsEncrypted(data: _1!, hash: _2!, secret: _3!) } } @@ -958,12 +909,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureData.secureData(data: _1!, dataHash: _2!, secret: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureData.secureData(data: _1!, dataHash: _2!, secret: _3!) } } @@ -1027,12 +976,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.SecureFile.secureFile(id: _1!, accessHash: _2!, size: _3!, dcId: _4!, date: _5!, fileHash: _6!, secret: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.SecureFile.secureFile(id: _1!, accessHash: _2!, size: _3!, dcId: _4!, date: _5!, fileHash: _6!, secret: _7!) } public static func parse_secureFileEmpty(_ reader: BufferReader) -> SecureFile? { return Api.SecureFile.secureFileEmpty @@ -1084,23 +1035,15 @@ public extension Api { var _1: Buffer? _1 = parseBytes(reader) let _c1 = _1 != nil - if _c1 { - return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: _1!) } public static func parse_securePasswordKdfAlgoSHA512(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { var _1: Buffer? _1 = parseBytes(reader) let _c1 = _1 != nil - if _c1 { - return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoSHA512(salt: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoSHA512(salt: _1!) } public static func parse_securePasswordKdfAlgoUnknown(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoUnknown diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index 22a2ae99..77fe9cc5 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -33,23 +33,15 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.SecurePlainData.securePlainEmail(email: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SecurePlainData.securePlainEmail(email: _1!) } public static func parse_securePlainPhone(_ reader: BufferReader) -> SecurePlainData? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.SecurePlainData.securePlainPhone(phone: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SecurePlainData.securePlainPhone(phone: _1!) } } @@ -99,12 +91,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SecureRequiredType.secureRequiredType(flags: _1!, type: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.SecureRequiredType.secureRequiredType(flags: _1!, type: _2!) } public static func parse_secureRequiredTypeOneOf(_ reader: BufferReader) -> SecureRequiredType? { var _1: [Api.SecureRequiredType]? @@ -112,12 +101,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureRequiredType.self) } let _c1 = _1 != nil - if _c1 { - return Api.SecureRequiredType.secureRequiredTypeOneOf(types: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SecureRequiredType.secureRequiredTypeOneOf(types: _1!) } } @@ -158,12 +143,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureSecretSettings.secureSecretSettings(secureAlgo: _1!, secureSecret: _2!, secureSecretId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureSecretSettings.secureSecretSettings(secureAlgo: _1!, secureSecret: _2!, secureSecretId: _3!) } } @@ -254,12 +237,17 @@ public extension Api { let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil let _c10 = _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.SecureValue.secureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9, hash: _10!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + return Api.SecureValue.secureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9, hash: _10!) } } @@ -397,12 +385,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueError(type: _1!, hash: _2!, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureValueError.secureValueError(type: _1!, hash: _2!, text: _3!) } public static func parse_secureValueErrorData(_ reader: BufferReader) -> SecureValueError? { var _1: Api.SecureValueType? @@ -419,12 +405,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.SecureValueError.secureValueErrorData(type: _1!, dataHash: _2!, field: _3!, text: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.SecureValueError.secureValueErrorData(type: _1!, dataHash: _2!, field: _3!, text: _4!) } public static func parse_secureValueErrorFile(_ reader: BufferReader) -> SecureValueError? { var _1: Api.SecureValueType? @@ -438,12 +423,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorFile(type: _1!, fileHash: _2!, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureValueError.secureValueErrorFile(type: _1!, fileHash: _2!, text: _3!) } public static func parse_secureValueErrorFiles(_ reader: BufferReader) -> SecureValueError? { var _1: Api.SecureValueType? @@ -459,12 +442,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorFiles(type: _1!, fileHash: _2!, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureValueError.secureValueErrorFiles(type: _1!, fileHash: _2!, text: _3!) } public static func parse_secureValueErrorFrontSide(_ reader: BufferReader) -> SecureValueError? { var _1: Api.SecureValueType? @@ -478,12 +459,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorFrontSide(type: _1!, fileHash: _2!, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureValueError.secureValueErrorFrontSide(type: _1!, fileHash: _2!, text: _3!) } public static func parse_secureValueErrorReverseSide(_ reader: BufferReader) -> SecureValueError? { var _1: Api.SecureValueType? @@ -497,12 +476,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorReverseSide(type: _1!, fileHash: _2!, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureValueError.secureValueErrorReverseSide(type: _1!, fileHash: _2!, text: _3!) } public static func parse_secureValueErrorSelfie(_ reader: BufferReader) -> SecureValueError? { var _1: Api.SecureValueType? @@ -516,12 +493,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorSelfie(type: _1!, fileHash: _2!, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureValueError.secureValueErrorSelfie(type: _1!, fileHash: _2!, text: _3!) } public static func parse_secureValueErrorTranslationFile(_ reader: BufferReader) -> SecureValueError? { var _1: Api.SecureValueType? @@ -535,12 +510,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorTranslationFile(type: _1!, fileHash: _2!, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureValueError.secureValueErrorTranslationFile(type: _1!, fileHash: _2!, text: _3!) } public static func parse_secureValueErrorTranslationFiles(_ reader: BufferReader) -> SecureValueError? { var _1: Api.SecureValueType? @@ -556,12 +529,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorTranslationFiles(type: _1!, fileHash: _2!, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SecureValueError.secureValueErrorTranslationFiles(type: _1!, fileHash: _2!, text: _3!) } } @@ -598,12 +569,9 @@ public extension Api { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SecureValueHash.secureValueHash(type: _1!, hash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.SecureValueHash.secureValueHash(type: _1!, hash: _2!) } } @@ -812,12 +780,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SendAsPeer.sendAsPeer(flags: _1!, peer: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.SendAsPeer.sendAsPeer(flags: _1!, peer: _2!) } } @@ -1030,23 +995,17 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SendMessageAction.sendMessageEmojiInteraction(emoticon: _1!, msgId: _2!, interaction: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SendMessageAction.sendMessageEmojiInteraction(emoticon: _1!, msgId: _2!, interaction: _3!) } public static func parse_sendMessageEmojiInteractionSeen(_ reader: BufferReader) -> SendMessageAction? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageEmojiInteractionSeen(emoticon: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SendMessageAction.sendMessageEmojiInteractionSeen(emoticon: _1!) } public static func parse_sendMessageGamePlayAction(_ reader: BufferReader) -> SendMessageAction? { return Api.SendMessageAction.sendMessageGamePlayAction @@ -1058,12 +1017,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageHistoryImportAction(progress: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SendMessageAction.sendMessageHistoryImportAction(progress: _1!) } public static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { return Api.SendMessageAction.sendMessageRecordAudioAction @@ -1083,12 +1038,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SendMessageAction.sendMessageTextDraftAction(randomId: _1!, text: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.SendMessageAction.sendMessageTextDraftAction(randomId: _1!, text: _2!) } public static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { return Api.SendMessageAction.sendMessageTypingAction @@ -1097,56 +1049,36 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadAudioAction(progress: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SendMessageAction.sendMessageUploadAudioAction(progress: _1!) } public static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadDocumentAction(progress: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SendMessageAction.sendMessageUploadDocumentAction(progress: _1!) } public static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadPhotoAction(progress: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SendMessageAction.sendMessageUploadPhotoAction(progress: _1!) } public static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadRoundAction(progress: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SendMessageAction.sendMessageUploadRoundAction(progress: _1!) } public static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadVideoAction(progress: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.SendMessageAction.sendMessageUploadVideoAction(progress: _1!) } public static func parse_speakingInGroupCallAction(_ reader: BufferReader) -> SendMessageAction? { return Api.SendMessageAction.speakingInGroupCallAction diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index 5abc7a1d..08d8aafe 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -38,12 +38,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.ShippingOption.shippingOption(id: _1!, title: _2!, prices: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.ShippingOption.shippingOption(id: _1!, title: _2!, prices: _3!) } } @@ -82,12 +80,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SmsJob.smsJob(jobId: _1!, phoneNumber: _2!, text: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SmsJob.smsJob(jobId: _1!, phoneNumber: _2!, text: _3!) } } @@ -182,12 +178,21 @@ public extension Api { let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { - return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, url: _3!, title: _4!, message: _5!, entities: _6, photo: _7, media: _8, color: _9, buttonText: _10!, sponsorInfo: _11, additionalInfo: _12, minDisplayDuration: _13, maxDisplayDuration: _14) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, url: _3!, title: _4!, message: _5!, entities: _6, photo: _7, media: _8, color: _9, buttonText: _10!, sponsorInfo: _11, additionalInfo: _12, minDisplayDuration: _13, maxDisplayDuration: _14) } } @@ -222,12 +227,9 @@ public extension Api { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SponsoredMessageReportOption.sponsoredMessageReportOption(text: _1!, option: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.SponsoredMessageReportOption.sponsoredMessageReportOption(text: _1!, option: _2!) } } @@ -276,12 +278,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.SponsoredPeer.sponsoredPeer(flags: _1!, randomId: _2!, peer: _3!, sponsorInfo: _4, additionalInfo: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.SponsoredPeer.sponsoredPeer(flags: _1!, randomId: _2!, peer: _3!, sponsorInfo: _4, additionalInfo: _5) } } @@ -440,12 +442,29 @@ public extension Api { let _c20 = (Int(_1!) & Int(1 << 11) == 0) || _20 != nil let _c21 = (Int(_1!) & Int(1 << 12) == 0) || _21 != nil let _c22 = (Int(_1!) & Int(1 << 13) == 0) || _22 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { - return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14, perUserTotal: _15, perUserRemains: _16, lockedUntilDate: _17, auctionSlug: _18, giftsPerRound: _19, auctionStartDate: _20, upgradeVariants: _21, background: _22) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + if !_c20 { return nil } + if !_c21 { return nil } + if !_c22 { return nil } + return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14, perUserTotal: _15, perUserRemains: _16, lockedUntilDate: _17, auctionSlug: _18, giftsPerRound: _19, auctionStartDate: _20, upgradeVariants: _21, background: _22) } public static func parse_starGiftUnique(_ reader: BufferReader) -> StarGift? { var _1: Int32? @@ -528,12 +547,29 @@ public extension Api { let _c20 = (Int(_1!) & Int(1 << 11) == 0) || _20 != nil let _c21 = (Int(_1!) & Int(1 << 12) == 0) || _21 != nil let _c22 = (Int(_1!) & Int(1 << 13) == 0) || _22 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { - return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, valueUsdAmount: _18, themePeer: _19, peerColor: _20, hostId: _21, offerMinStars: _22) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + if !_c20 { return nil } + if !_c21 { return nil } + if !_c22 { return nil } + return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, valueUsdAmount: _18, themePeer: _19, peerColor: _20, hostId: _21, offerMinStars: _22) } } @@ -578,12 +614,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StarGiftActiveAuctionState.starGiftActiveAuctionState(gift: _1!, state: _2!, userState: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.StarGiftActiveAuctionState.starGiftActiveAuctionState(gift: _1!, state: _2!, userState: _3!) } } @@ -673,12 +707,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.StarGiftAttribute.starGiftAttributeBackdrop(name: _1!, backdropId: _2!, centerColor: _3!, edgeColor: _4!, patternColor: _5!, textColor: _6!, rarityPermille: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.StarGiftAttribute.starGiftAttributeBackdrop(name: _1!, backdropId: _2!, centerColor: _3!, edgeColor: _4!, patternColor: _5!, textColor: _6!, rarityPermille: _7!) } public static func parse_starGiftAttributeModel(_ reader: BufferReader) -> StarGiftAttribute? { var _1: String? @@ -692,12 +728,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StarGiftAttribute.starGiftAttributeModel(name: _1!, document: _2!, rarityPermille: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.StarGiftAttribute.starGiftAttributeModel(name: _1!, document: _2!, rarityPermille: _3!) } public static func parse_starGiftAttributeOriginalDetails(_ reader: BufferReader) -> StarGiftAttribute? { var _1: Int32? @@ -721,12 +755,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StarGiftAttribute.starGiftAttributeOriginalDetails(flags: _1!, senderId: _2, recipientId: _3!, date: _4!, message: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.StarGiftAttribute.starGiftAttributeOriginalDetails(flags: _1!, senderId: _2, recipientId: _3!, date: _4!, message: _5) } public static func parse_starGiftAttributePattern(_ reader: BufferReader) -> StarGiftAttribute? { var _1: String? @@ -740,12 +774,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StarGiftAttribute.starGiftAttributePattern(name: _1!, document: _2!, rarityPermille: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.StarGiftAttribute.starGiftAttributePattern(name: _1!, document: _2!, rarityPermille: _3!) } } @@ -782,12 +814,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StarGiftAttributeCounter.starGiftAttributeCounter(attribute: _1!, count: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StarGiftAttributeCounter.starGiftAttributeCounter(attribute: _1!, count: _2!) } } @@ -836,34 +865,22 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.StarGiftAttributeId.starGiftAttributeIdBackdrop(backdropId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StarGiftAttributeId.starGiftAttributeIdBackdrop(backdropId: _1!) } public static func parse_starGiftAttributeIdModel(_ reader: BufferReader) -> StarGiftAttributeId? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.StarGiftAttributeId.starGiftAttributeIdModel(documentId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StarGiftAttributeId.starGiftAttributeIdModel(documentId: _1!) } public static func parse_starGiftAttributeIdPattern(_ reader: BufferReader) -> StarGiftAttributeId? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.StarGiftAttributeId.starGiftAttributeIdPattern(documentId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StarGiftAttributeId.starGiftAttributeIdPattern(documentId: _1!) } } @@ -926,12 +943,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.StarGiftAuctionAcquiredGift.starGiftAuctionAcquiredGift(flags: _1!, peer: _2!, date: _3!, bidAmount: _4!, round: _5!, pos: _6!, message: _7, giftNum: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.StarGiftAuctionAcquiredGift.starGiftAuctionAcquiredGift(flags: _1!, peer: _2!, date: _3!, bidAmount: _4!, round: _5!, pos: _6!, message: _7, giftNum: _8) } } @@ -978,12 +998,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StarGiftAuctionRound.starGiftAuctionRound(num: _1!, duration: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StarGiftAuctionRound.starGiftAuctionRound(num: _1!, duration: _2!) } public static func parse_starGiftAuctionRoundExtendable(_ reader: BufferReader) -> StarGiftAuctionRound? { var _1: Int32? @@ -998,12 +1015,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StarGiftAuctionRound.starGiftAuctionRoundExtendable(num: _1!, duration: _2!, extendTop: _3!, extendWindow: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.StarGiftAuctionRound.starGiftAuctionRoundExtendable(num: _1!, duration: _2!, extendTop: _3!, extendWindow: _4!) } } @@ -1120,12 +1136,19 @@ public extension Api { let _c10 = _10 != nil let _c11 = _11 != nil let _c12 = _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.StarGiftAuctionState.starGiftAuctionState(version: _1!, startDate: _2!, endDate: _3!, minBidAmount: _4!, bidLevels: _5!, topBidders: _6!, nextRoundAt: _7!, lastGiftNum: _8!, giftsLeft: _9!, currentRound: _10!, totalRounds: _11!, rounds: _12!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + return Api.StarGiftAuctionState.starGiftAuctionState(version: _1!, startDate: _2!, endDate: _3!, minBidAmount: _4!, bidLevels: _5!, topBidders: _6!, nextRoundAt: _7!, lastGiftNum: _8!, giftsLeft: _9!, currentRound: _10!, totalRounds: _11!, rounds: _12!) } public static func parse_starGiftAuctionStateFinished(_ reader: BufferReader) -> StarGiftAuctionState? { var _1: Int32? @@ -1149,12 +1172,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.StarGiftAuctionState.starGiftAuctionStateFinished(flags: _1!, startDate: _2!, endDate: _3!, averagePrice: _4!, listedCount: _5, fragmentListedCount: _6, fragmentListedUrl: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.StarGiftAuctionState.starGiftAuctionStateFinished(flags: _1!, startDate: _2!, endDate: _3!, averagePrice: _4!, listedCount: _5, fragmentListedCount: _6, fragmentListedUrl: _7) } public static func parse_starGiftAuctionStateNotModified(_ reader: BufferReader) -> StarGiftAuctionState? { return Api.StarGiftAuctionState.starGiftAuctionStateNotModified @@ -1210,12 +1235,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StarGiftAuctionUserState.starGiftAuctionUserState(flags: _1!, bidAmount: _2, bidDate: _3, minBidAmount: _4, bidPeer: _5, acquiredCount: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.StarGiftAuctionUserState.starGiftAuctionUserState(flags: _1!, bidAmount: _2, bidDate: _3, minBidAmount: _4, bidPeer: _5, acquiredCount: _6!) } } @@ -1254,12 +1280,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StarGiftBackground.starGiftBackground(centerColor: _1!, edgeColor: _2!, textColor: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.StarGiftBackground.starGiftBackground(centerColor: _1!, edgeColor: _2!, textColor: _3!) } } @@ -1312,12 +1336,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StarGiftCollection.starGiftCollection(flags: _1!, collectionId: _2!, title: _3!, icon: _4, giftsCount: _5!, hash: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.StarGiftCollection.starGiftCollection(flags: _1!, collectionId: _2!, title: _3!, icon: _4, giftsCount: _5!, hash: _6!) } } @@ -1352,12 +1377,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StarGiftUpgradePrice.starGiftUpgradePrice(date: _1!, upgradeStars: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StarGiftUpgradePrice.starGiftUpgradePrice(date: _1!, upgradeStars: _2!) } } @@ -1410,12 +1432,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StarRefProgram.starRefProgram(flags: _1!, botId: _2!, commissionPermille: _3!, durationMonths: _4, endDate: _5, dailyRevenuePerUser: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.StarRefProgram.starRefProgram(flags: _1!, botId: _2!, commissionPermille: _3!, durationMonths: _4, endDate: _5, dailyRevenuePerUser: _6) } } @@ -1459,23 +1482,16 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StarsAmount.starsAmount(amount: _1!, nanos: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StarsAmount.starsAmount(amount: _1!, nanos: _2!) } public static func parse_starsTonAmount(_ reader: BufferReader) -> StarsAmount? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.StarsAmount.starsTonAmount(amount: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StarsAmount.starsTonAmount(amount: _1!) } } @@ -1522,12 +1538,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StarsGiftOption.starsGiftOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.StarsGiftOption.starsGiftOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!) } } @@ -1588,12 +1604,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.StarsGiveawayOption.starsGiveawayOption(flags: _1!, stars: _2!, yearlyBoosts: _3!, storeProduct: _4, currency: _5!, amount: _6!, winners: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.StarsGiveawayOption.starsGiveawayOption(flags: _1!, stars: _2!, yearlyBoosts: _3!, storeProduct: _4, currency: _5!, amount: _6!, winners: _7!) } } @@ -1632,12 +1650,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StarsGiveawayWinnersOption.starsGiveawayWinnersOption(flags: _1!, users: _2!, perUserStars: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.StarsGiveawayWinnersOption.starsGiveawayWinnersOption(flags: _1!, users: _2!, perUserStars: _3!) } } diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index fd08d27e..4a5c08c4 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -40,12 +40,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StarsRating.starsRating(flags: _1!, level: _2!, currentLevelStars: _3!, stars: _4!, nextLevelStars: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.StarsRating.starsRating(flags: _1!, level: _2!, currentLevelStars: _3!, stars: _4!, nextLevelStars: _5) } } @@ -98,12 +98,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StarsRevenueStatus.starsRevenueStatus(flags: _1!, currentBalance: _2!, availableBalance: _3!, overallRevenue: _4!, nextWithdrawalAt: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.StarsRevenueStatus.starsRevenueStatus(flags: _1!, currentBalance: _2!, availableBalance: _3!, overallRevenue: _4!, nextWithdrawalAt: _5) } } @@ -172,12 +172,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.StarsSubscription.starsSubscription(flags: _1!, id: _2!, peer: _3!, untilDate: _4!, pricing: _5!, chatInviteHash: _6, title: _7, photo: _8, invoiceSlug: _9) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.StarsSubscription.starsSubscription(flags: _1!, id: _2!, peer: _3!, untilDate: _4!, pricing: _5!, chatInviteHash: _6, title: _7, photo: _8, invoiceSlug: _9) } } @@ -212,12 +216,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StarsSubscriptionPricing.starsSubscriptionPricing(period: _1!, amount: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StarsSubscriptionPricing.starsSubscriptionPricing(period: _1!, amount: _2!) } } @@ -264,12 +265,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StarsTopupOption.starsTopupOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.StarsTopupOption.starsTopupOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!) } } @@ -410,12 +411,31 @@ public extension Api { let _c22 = (Int(_1!) & Int(1 << 20) == 0) || _22 != nil let _c23 = (Int(_1!) & Int(1 << 23) == 0) || _23 != nil let _c24 = (Int(_1!) & Int(1 << 23) == 0) || _24 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 { - return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, amount: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipNumber: _17, starrefCommissionPermille: _18, starrefPeer: _19, starrefAmount: _20, paidMessages: _21, premiumGiftMonths: _22, adsProceedsFromDate: _23, adsProceedsToDate: _24) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + if !_c20 { return nil } + if !_c21 { return nil } + if !_c22 { return nil } + if !_c23 { return nil } + if !_c24 { return nil } + return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, amount: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipNumber: _17, starrefCommissionPermille: _18, starrefPeer: _19, starrefAmount: _20, paidMessages: _21, premiumGiftMonths: _22, adsProceedsFromDate: _23, adsProceedsToDate: _24) } } @@ -511,12 +531,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Peer } let _c1 = _1 != nil - if _c1 { - return Api.StarsTransactionPeer.starsTransactionPeer(peer: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StarsTransactionPeer.starsTransactionPeer(peer: _1!) } public static func parse_starsTransactionPeerAPI(_ reader: BufferReader) -> StarsTransactionPeer? { return Api.StarsTransactionPeer.starsTransactionPeerAPI @@ -572,12 +588,9 @@ public extension Api { _2 = reader.readDouble() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StatsAbsValueAndPrev.statsAbsValueAndPrev(current: _1!, previous: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StatsAbsValueAndPrev.statsAbsValueAndPrev(current: _1!, previous: _2!) } } @@ -612,12 +625,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StatsDateRangeDays.statsDateRangeDays(minDate: _1!, maxDate: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StatsDateRangeDays.statsDateRangeDays(minDate: _1!, maxDate: _2!) } } @@ -676,34 +686,24 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.StatsGraph.statsGraph(flags: _1!, json: _2!, zoomToken: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.StatsGraph.statsGraph(flags: _1!, json: _2!, zoomToken: _3) } public static func parse_statsGraphAsync(_ reader: BufferReader) -> StatsGraph? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.StatsGraph.statsGraphAsync(token: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StatsGraph.statsGraphAsync(token: _1!) } public static func parse_statsGraphError(_ reader: BufferReader) -> StatsGraph? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.StatsGraph.statsGraphError(error: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StatsGraph.statsGraphError(error: _1!) } } @@ -746,12 +746,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StatsGroupTopAdmin.statsGroupTopAdmin(userId: _1!, deleted: _2!, kicked: _3!, banned: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.StatsGroupTopAdmin.statsGroupTopAdmin(userId: _1!, deleted: _2!, kicked: _3!, banned: _4!) } } @@ -786,12 +785,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StatsGroupTopInviter.statsGroupTopInviter(userId: _1!, invitations: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StatsGroupTopInviter.statsGroupTopInviter(userId: _1!, invitations: _2!) } } @@ -830,12 +826,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StatsGroupTopPoster.statsGroupTopPoster(userId: _1!, messages: _2!, avgChars: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.StatsGroupTopPoster.statsGroupTopPoster(userId: _1!, messages: _2!, avgChars: _3!) } } @@ -870,12 +864,9 @@ public extension Api { _2 = reader.readDouble() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StatsPercentValue.statsPercentValue(part: _1!, total: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StatsPercentValue.statsPercentValue(part: _1!, total: _2!) } } @@ -906,12 +897,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.StatsURL.statsURL(url: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StatsURL.statsURL(url: _1!) } } @@ -952,12 +939,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerKeyword.stickerKeyword(documentId: _1!, keyword: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StickerKeyword.stickerKeyword(documentId: _1!, keyword: _2!) } } @@ -998,12 +982,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerPack.stickerPack(emoticon: _1!, documents: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StickerPack.stickerPack(emoticon: _1!, documents: _2!) } } @@ -1084,12 +1065,19 @@ public extension Api { let _c10 = (Int(_1!) & Int(1 << 8) == 0) || _10 != nil let _c11 = _11 != nil let _c12 = _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.StickerSet.stickerSet(flags: _1!, installedDate: _2, id: _3!, accessHash: _4!, title: _5!, shortName: _6!, thumbs: _7, thumbDcId: _8, thumbVersion: _9, thumbDocumentId: _10, count: _11!, hash: _12!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + return Api.StickerSet.stickerSet(flags: _1!, installedDate: _2, id: _3!, accessHash: _4!, title: _5!, shortName: _6!, thumbs: _7, thumbDcId: _8, thumbVersion: _9, thumbDocumentId: _10, count: _11!, hash: _12!) } } @@ -1175,12 +1163,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerSetCovered.stickerSetCovered(set: _1!, cover: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StickerSetCovered.stickerSetCovered(set: _1!, cover: _2!) } public static func parse_stickerSetFullCovered(_ reader: BufferReader) -> StickerSetCovered? { var _1: Api.StickerSet? @@ -1203,12 +1188,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StickerSetCovered.stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.StickerSetCovered.stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!) } public static func parse_stickerSetMultiCovered(_ reader: BufferReader) -> StickerSetCovered? { var _1: Api.StickerSet? @@ -1221,12 +1205,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerSetCovered.stickerSetMultiCovered(set: _1!, covers: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StickerSetCovered.stickerSetMultiCovered(set: _1!, covers: _2!) } public static func parse_stickerSetNoCovered(_ reader: BufferReader) -> StickerSetCovered? { var _1: Api.StickerSet? @@ -1234,12 +1215,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.StickerSet } let _c1 = _1 != nil - if _c1 { - return Api.StickerSetCovered.stickerSetNoCovered(set: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StickerSetCovered.stickerSetNoCovered(set: _1!) } } @@ -1278,12 +1255,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoriesStealthMode.storiesStealthMode(flags: _1!, activeUntilDate: _2, cooldownUntilDate: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.StoriesStealthMode.storiesStealthMode(flags: _1!, activeUntilDate: _2, cooldownUntilDate: _3) } } diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 075ddc82..12cbc688 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -44,12 +44,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StoryAlbum.storyAlbum(flags: _1!, albumId: _2!, title: _3!, iconPhoto: _4, iconVideo: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.StoryAlbum.storyAlbum(flags: _1!, albumId: _2!, title: _3!, iconPhoto: _4, iconVideo: _5) } } @@ -94,12 +94,11 @@ public extension Api { let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StoryFwdHeader.storyFwdHeader(flags: _1!, from: _2, fromName: _3, storyId: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.StoryFwdHeader.storyFwdHeader(flags: _1!, from: _2, fromName: _3, storyId: _4) } } @@ -237,23 +236,28 @@ public extension Api { let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil let _c14 = (Int(_1!) & Int(1 << 19) == 0) || _14 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { - return Api.StoryItem.storyItem(flags: _1!, id: _2!, date: _3!, fromId: _4, fwdFrom: _5, expireDate: _6!, caption: _7, entities: _8, media: _9!, mediaAreas: _10, privacy: _11, views: _12, sentReaction: _13, albums: _14) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + return Api.StoryItem.storyItem(flags: _1!, id: _2!, date: _3!, fromId: _4, fwdFrom: _5, expireDate: _6!, caption: _7, entities: _8, media: _9!, mediaAreas: _10, privacy: _11, views: _12, sentReaction: _13, albums: _14) } public static func parse_storyItemDeleted(_ reader: BufferReader) -> StoryItem? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.StoryItem.storyItemDeleted(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StoryItem.storyItemDeleted(id: _1!) } public static func parse_storyItemSkipped(_ reader: BufferReader) -> StoryItem? { var _1: Int32? @@ -268,12 +272,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StoryItem.storyItemSkipped(flags: _1!, id: _2!, date: _3!, expireDate: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.StoryItem.storyItemSkipped(flags: _1!, id: _2!, date: _3!, expireDate: _4!) } } @@ -335,12 +338,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoryReaction.storyReaction(peerId: _1!, date: _2!, reaction: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.StoryReaction.storyReaction(peerId: _1!, date: _2!, reaction: _3!) } public static func parse_storyReactionPublicForward(_ reader: BufferReader) -> StoryReaction? { var _1: Api.Message? @@ -348,12 +349,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Message } let _c1 = _1 != nil - if _c1 { - return Api.StoryReaction.storyReactionPublicForward(message: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.StoryReaction.storyReactionPublicForward(message: _1!) } public static func parse_storyReactionPublicRepost(_ reader: BufferReader) -> StoryReaction? { var _1: Api.Peer? @@ -366,12 +363,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StoryReaction.storyReactionPublicRepost(peerId: _1!, story: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StoryReaction.storyReactionPublicRepost(peerId: _1!, story: _2!) } } @@ -437,12 +431,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StoryView.storyView(flags: _1!, userId: _2!, date: _3!, reaction: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.StoryView.storyView(flags: _1!, userId: _2!, date: _3!, reaction: _4) } public static func parse_storyViewPublicForward(_ reader: BufferReader) -> StoryView? { var _1: Int32? @@ -453,12 +446,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StoryView.storyViewPublicForward(flags: _1!, message: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.StoryView.storyViewPublicForward(flags: _1!, message: _2!) } public static func parse_storyViewPublicRepost(_ reader: BufferReader) -> StoryView? { var _1: Int32? @@ -474,12 +464,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoryView.storyViewPublicRepost(flags: _1!, peerId: _2!, story: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.StoryView.storyViewPublicRepost(flags: _1!, peerId: _2!, story: _3!) } } @@ -542,12 +530,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StoryViews.storyViews(flags: _1!, viewsCount: _2!, forwardsCount: _3, reactions: _4, reactionsCount: _5, recentViewers: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.StoryViews.storyViews(flags: _1!, viewsCount: _2!, forwardsCount: _3, reactions: _4, reactionsCount: _5, recentViewers: _6) } } @@ -588,12 +577,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.SuggestedPost.suggestedPost(flags: _1!, price: _2, scheduleDate: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.SuggestedPost.suggestedPost(flags: _1!, price: _2, scheduleDate: _3) } } @@ -634,12 +621,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.TextWithEntities.textWithEntities(text: _1!, entities: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.TextWithEntities.textWithEntities(text: _1!, entities: _2!) } } @@ -710,12 +694,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, emoticon: _8, installsCount: _9) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, emoticon: _8, installsCount: _9) } } @@ -776,12 +764,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.ThemeSettings.themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.ThemeSettings.themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6) } } @@ -820,12 +809,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Timezone.timezone(id: _1!, name: _2!, utcOffset: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Timezone.timezone(id: _1!, name: _2!, utcOffset: _3!) } } @@ -866,12 +853,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.TodoCompletion.todoCompletion(id: _1!, completedBy: _2!, date: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.TodoCompletion.todoCompletion(id: _1!, completedBy: _2!, date: _3!) } } @@ -908,12 +893,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.TodoItem.todoItem(id: _1!, title: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.TodoItem.todoItem(id: _1!, title: _2!) } } @@ -960,12 +942,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.TodoList.todoList(flags: _1!, title: _2!, list: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.TodoList.todoList(flags: _1!, title: _2!, list: _3!) } } @@ -1002,12 +982,9 @@ public extension Api { _2 = reader.readDouble() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.TopPeer.topPeer(peer: _1!, rating: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.TopPeer.topPeer(peer: _1!, rating: _2!) } } @@ -1178,12 +1155,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.TopPeerCategoryPeers.topPeerCategoryPeers(category: _1!, count: _2!, peers: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.TopPeerCategoryPeers.topPeerCategoryPeers(category: _1!, count: _2!, peers: _3!) } } @@ -1247,6 +1222,7 @@ public extension Api { case updateDraftMessage(flags: Int32, peer: Api.Peer, topMsgId: Int32?, savedPeerId: Api.Peer?, draft: Api.DraftMessage) case updateEditChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32) case updateEditMessage(message: Api.Message, pts: Int32, ptsCount: Int32) + case updateEmojiGameInfo(info: Api.messages.EmojiGameInfo) case updateEncryptedChatTyping(chatId: Int32) case updateEncryptedMessagesRead(chatId: Int32, maxDate: Int32, date: Int32) case updateEncryption(chat: Api.EncryptedChat, date: Int32) @@ -1887,6 +1863,12 @@ public extension Api { serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(ptsCount, buffer: buffer, boxed: false) break + case .updateEmojiGameInfo(let info): + if boxed { + buffer.appendInt32(-73640838) + } + info.serialize(buffer, true) + break case .updateEncryptedChatTyping(let chatId): if boxed { buffer.appendInt32(386986326) @@ -2774,6 +2756,8 @@ public extension Api { return ("updateEditChannelMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) case .updateEditMessage(let message, let pts, let ptsCount): return ("updateEditMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) + case .updateEmojiGameInfo(let info): + return ("updateEmojiGameInfo", [("info", info as Any)]) case .updateEncryptedChatTyping(let chatId): return ("updateEncryptedChatTyping", [("chatId", chatId as Any)]) case .updateEncryptedMessagesRead(let chatId, let maxDate, let date): @@ -2978,12 +2962,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateBotBusinessConnect(connection: _1!, qts: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateBotBusinessConnect(connection: _1!, qts: _2!) } public static func parse_updateBotCallbackQuery(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3012,12 +2993,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Update.updateBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, peer: _4!, msgId: _5!, chatInstance: _6!, data: _7, gameShortName: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.Update.updateBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, peer: _4!, msgId: _5!, chatInstance: _6!, data: _7, gameShortName: _8) } public static func parse_updateBotChatBoost(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -3033,12 +3017,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotChatBoost(peer: _1!, boost: _2!, qts: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateBotChatBoost(peer: _1!, boost: _2!, qts: _3!) } public static func parse_updateBotChatInviteRequester(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -3063,12 +3045,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateBotChatInviteRequester(peer: _1!, date: _2!, userId: _3!, about: _4!, invite: _5!, qts: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.Update.updateBotChatInviteRequester(peer: _1!, date: _2!, userId: _3!, about: _4!, invite: _5!, qts: _6!) } public static func parse_updateBotCommands(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -3084,12 +3067,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotCommands(peer: _1!, botId: _2!, commands: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateBotCommands(peer: _1!, botId: _2!, commands: _3!) } public static func parse_updateBotDeleteBusinessMessage(_ reader: BufferReader) -> Update? { var _1: String? @@ -3108,12 +3089,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateBotDeleteBusinessMessage(connectionId: _1!, peer: _2!, messages: _3!, qts: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateBotDeleteBusinessMessage(connectionId: _1!, peer: _2!, messages: _3!, qts: _4!) } public static func parse_updateBotEditBusinessMessage(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3135,12 +3115,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateBotEditBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updateBotEditBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!) } public static func parse_updateBotInlineQuery(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3168,12 +3148,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateBotInlineQuery(flags: _1!, queryId: _2!, userId: _3!, query: _4!, geo: _5, peerType: _6, offset: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.Update.updateBotInlineQuery(flags: _1!, queryId: _2!, userId: _3!, query: _4!, geo: _5, peerType: _6, offset: _7!) } public static func parse_updateBotInlineSend(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3198,12 +3180,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateBotInlineSend(flags: _1!, userId: _2!, query: _3!, geo: _4, id: _5!, msgId: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.Update.updateBotInlineSend(flags: _1!, userId: _2!, query: _3!, geo: _4, id: _5!, msgId: _6) } public static func parse_updateBotMenuButton(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3214,12 +3197,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateBotMenuButton(botId: _1!, button: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateBotMenuButton(botId: _1!, button: _2!) } public static func parse_updateBotMessageReaction(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -3251,12 +3231,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateBotMessageReaction(peer: _1!, msgId: _2!, date: _3!, actor: _4!, oldReactions: _5!, newReactions: _6!, qts: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.Update.updateBotMessageReaction(peer: _1!, msgId: _2!, date: _3!, actor: _4!, oldReactions: _5!, newReactions: _6!, qts: _7!) } public static func parse_updateBotMessageReactions(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -3278,12 +3260,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateBotMessageReactions(peer: _1!, msgId: _2!, date: _3!, reactions: _4!, qts: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updateBotMessageReactions(peer: _1!, msgId: _2!, date: _3!, reactions: _4!, qts: _5!) } public static func parse_updateBotNewBusinessMessage(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3305,12 +3287,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateBotNewBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updateBotNewBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!) } public static func parse_updateBotPrecheckoutQuery(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3339,12 +3321,15 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Update.updateBotPrecheckoutQuery(flags: _1!, queryId: _2!, userId: _3!, payload: _4!, info: _5, shippingOptionId: _6, currency: _7!, totalAmount: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.Update.updateBotPrecheckoutQuery(flags: _1!, queryId: _2!, userId: _3!, payload: _4!, info: _5, shippingOptionId: _6, currency: _7!, totalAmount: _8!) } public static func parse_updateBotPurchasedPaidMedia(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3356,12 +3341,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotPurchasedPaidMedia(userId: _1!, payload: _2!, qts: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateBotPurchasedPaidMedia(userId: _1!, payload: _2!, qts: _3!) } public static func parse_updateBotShippingQuery(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3378,12 +3361,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateBotShippingQuery(queryId: _1!, userId: _2!, payload: _3!, shippingAddress: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateBotShippingQuery(queryId: _1!, userId: _2!, payload: _3!, shippingAddress: _4!) } public static func parse_updateBotStopped(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3400,12 +3382,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateBotStopped(userId: _1!, date: _2!, stopped: _3!, qts: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateBotStopped(userId: _1!, date: _2!, stopped: _3!, qts: _4!) } public static func parse_updateBotWebhookJSON(_ reader: BufferReader) -> Update? { var _1: Api.DataJSON? @@ -3413,12 +3394,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.DataJSON } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateBotWebhookJSON(data: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateBotWebhookJSON(data: _1!) } public static func parse_updateBotWebhookJSONQuery(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3432,12 +3409,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotWebhookJSONQuery(queryId: _1!, data: _2!, timeout: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateBotWebhookJSONQuery(queryId: _1!, data: _2!, timeout: _3!) } public static func parse_updateBusinessBotCallbackQuery(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3468,23 +3443,22 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil let _c7 = _7 != nil let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Update.updateBusinessBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, connectionId: _4!, message: _5!, replyToMessage: _6, chatInstance: _7!, data: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.Update.updateBusinessBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, connectionId: _4!, message: _5!, replyToMessage: _6, chatInstance: _7!, data: _8) } public static func parse_updateChannel(_ reader: BufferReader) -> Update? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Update.updateChannel(channelId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateChannel(channelId: _1!) } public static func parse_updateChannelAvailableMessages(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3493,12 +3467,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateChannelAvailableMessages(channelId: _1!, availableMinId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateChannelAvailableMessages(channelId: _1!, availableMinId: _2!) } public static func parse_updateChannelMessageForwards(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3510,12 +3481,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelMessageForwards(channelId: _1!, id: _2!, forwards: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateChannelMessageForwards(channelId: _1!, id: _2!, forwards: _3!) } public static func parse_updateChannelMessageViews(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3527,12 +3496,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelMessageViews(channelId: _1!, id: _2!, views: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateChannelMessageViews(channelId: _1!, id: _2!, views: _3!) } public static func parse_updateChannelParticipant(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3568,12 +3535,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Update.updateChannelParticipant(flags: _1!, channelId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.Update.updateChannelParticipant(flags: _1!, channelId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!) } public static func parse_updateChannelReadMessagesContents(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3595,12 +3566,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateChannelReadMessagesContents(flags: _1!, channelId: _2!, topMsgId: _3, savedPeerId: _4, messages: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updateChannelReadMessagesContents(flags: _1!, channelId: _2!, topMsgId: _3, savedPeerId: _4, messages: _5!) } public static func parse_updateChannelTooLong(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3612,12 +3583,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelTooLong(flags: _1!, channelId: _2!, pts: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateChannelTooLong(flags: _1!, channelId: _2!, pts: _3) } public static func parse_updateChannelUserTyping(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3639,12 +3608,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateChannelUserTyping(flags: _1!, channelId: _2!, topMsgId: _3, fromId: _4!, action: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updateChannelUserTyping(flags: _1!, channelId: _2!, topMsgId: _3, fromId: _4!, action: _5!) } public static func parse_updateChannelViewForumAsMessages(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3655,12 +3624,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateChannelViewForumAsMessages(channelId: _1!, enabled: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateChannelViewForumAsMessages(channelId: _1!, enabled: _2!) } public static func parse_updateChannelWebPage(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3677,23 +3643,18 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateChannelWebPage(channelId: _1!, webpage: _2!, pts: _3!, ptsCount: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateChannelWebPage(channelId: _1!, webpage: _2!, pts: _3!, ptsCount: _4!) } public static func parse_updateChat(_ reader: BufferReader) -> Update? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Update.updateChat(chatId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateChat(chatId: _1!) } public static func parse_updateChatDefaultBannedRights(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -3709,12 +3670,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChatDefaultBannedRights(peer: _1!, defaultBannedRights: _2!, version: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateChatDefaultBannedRights(peer: _1!, defaultBannedRights: _2!, version: _3!) } public static func parse_updateChatParticipant(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3750,12 +3709,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Update.updateChatParticipant(flags: _1!, chatId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.Update.updateChatParticipant(flags: _1!, chatId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!) } public static func parse_updateChatParticipantAdd(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3773,12 +3736,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateChatParticipantAdd(chatId: _1!, userId: _2!, inviterId: _3!, date: _4!, version: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updateChatParticipantAdd(chatId: _1!, userId: _2!, inviterId: _3!, date: _4!, version: _5!) } public static func parse_updateChatParticipantAdmin(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3795,12 +3758,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateChatParticipantAdmin(chatId: _1!, userId: _2!, isAdmin: _3!, version: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateChatParticipantAdmin(chatId: _1!, userId: _2!, isAdmin: _3!, version: _4!) } public static func parse_updateChatParticipantDelete(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3812,12 +3774,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChatParticipantDelete(chatId: _1!, userId: _2!, version: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateChatParticipantDelete(chatId: _1!, userId: _2!, version: _3!) } public static func parse_updateChatParticipants(_ reader: BufferReader) -> Update? { var _1: Api.ChatParticipants? @@ -3825,12 +3785,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.ChatParticipants } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateChatParticipants(participants: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateChatParticipants(participants: _1!) } public static func parse_updateChatUserTyping(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3846,12 +3802,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChatUserTyping(chatId: _1!, fromId: _2!, action: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateChatUserTyping(chatId: _1!, fromId: _2!, action: _3!) } public static func parse_updateConfig(_ reader: BufferReader) -> Update? { return Api.Update.updateConfig @@ -3865,12 +3819,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DcOption.self) } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateDcOptions(dcOptions: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateDcOptions(dcOptions: _1!) } public static func parse_updateDeleteChannelMessages(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -3887,12 +3837,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateDeleteChannelMessages(channelId: _1!, messages: _2!, pts: _3!, ptsCount: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateDeleteChannelMessages(channelId: _1!, messages: _2!, pts: _3!, ptsCount: _4!) } public static func parse_updateDeleteGroupCallMessages(_ reader: BufferReader) -> Update? { var _1: Api.InputGroupCall? @@ -3905,12 +3854,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateDeleteGroupCallMessages(call: _1!, messages: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateDeleteGroupCallMessages(call: _1!, messages: _2!) } public static func parse_updateDeleteMessages(_ reader: BufferReader) -> Update? { var _1: [Int32]? @@ -3924,23 +3870,17 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDeleteMessages(messages: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateDeleteMessages(messages: _1!, pts: _2!, ptsCount: _3!) } public static func parse_updateDeleteQuickReply(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.Update.updateDeleteQuickReply(shortcutId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateDeleteQuickReply(shortcutId: _1!) } public static func parse_updateDeleteQuickReplyMessages(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3951,12 +3891,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateDeleteQuickReplyMessages(shortcutId: _1!, messages: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateDeleteQuickReplyMessages(shortcutId: _1!, messages: _2!) } public static func parse_updateDeleteScheduledMessages(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3977,12 +3914,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateDeleteScheduledMessages(flags: _1!, peer: _2!, messages: _3!, sentMessages: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateDeleteScheduledMessages(flags: _1!, peer: _2!, messages: _3!, sentMessages: _4) } public static func parse_updateDialogFilter(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -3996,12 +3932,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDialogFilter(flags: _1!, id: _2!, filter: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateDialogFilter(flags: _1!, id: _2!, filter: _3) } public static func parse_updateDialogFilterOrder(_ reader: BufferReader) -> Update? { var _1: [Int32]? @@ -4009,12 +3943,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateDialogFilterOrder(order: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateDialogFilterOrder(order: _1!) } public static func parse_updateDialogFilters(_ reader: BufferReader) -> Update? { return Api.Update.updateDialogFilters @@ -4031,12 +3961,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDialogPinned(flags: _1!, folderId: _2, peer: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateDialogPinned(flags: _1!, folderId: _2, peer: _3!) } public static func parse_updateDialogUnreadMark(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4052,12 +3980,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDialogUnreadMark(flags: _1!, peer: _2!, savedPeerId: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateDialogUnreadMark(flags: _1!, peer: _2!, savedPeerId: _3) } public static func parse_updateDraftMessage(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4081,12 +4007,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateDraftMessage(flags: _1!, peer: _2!, topMsgId: _3, savedPeerId: _4, draft: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updateDraftMessage(flags: _1!, peer: _2!, topMsgId: _3, savedPeerId: _4, draft: _5!) } public static func parse_updateEditChannelMessage(_ reader: BufferReader) -> Update? { var _1: Api.Message? @@ -4100,12 +4026,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateEditChannelMessage(message: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateEditChannelMessage(message: _1!, pts: _2!, ptsCount: _3!) } public static func parse_updateEditMessage(_ reader: BufferReader) -> Update? { var _1: Api.Message? @@ -4119,23 +4043,26 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateEditMessage(message: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateEditMessage(message: _1!, pts: _2!, ptsCount: _3!) + } + public static func parse_updateEmojiGameInfo(_ reader: BufferReader) -> Update? { + var _1: Api.messages.EmojiGameInfo? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameInfo } + let _c1 = _1 != nil + if !_c1 { return nil } + return Api.Update.updateEmojiGameInfo(info: _1!) } public static func parse_updateEncryptedChatTyping(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.Update.updateEncryptedChatTyping(chatId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateEncryptedChatTyping(chatId: _1!) } public static func parse_updateEncryptedMessagesRead(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4147,12 +4074,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateEncryptedMessagesRead(chatId: _1!, maxDate: _2!, date: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateEncryptedMessagesRead(chatId: _1!, maxDate: _2!, date: _3!) } public static func parse_updateEncryption(_ reader: BufferReader) -> Update? { var _1: Api.EncryptedChat? @@ -4163,12 +4088,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateEncryption(chat: _1!, date: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateEncryption(chat: _1!, date: _2!) } public static func parse_updateFavedStickers(_ reader: BufferReader) -> Update? { return Api.Update.updateFavedStickers @@ -4185,12 +4107,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateFolderPeers(folderPeers: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateFolderPeers(folderPeers: _1!, pts: _2!, ptsCount: _3!) } public static func parse_updateGeoLiveViewed(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -4201,12 +4121,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateGeoLiveViewed(peer: _1!, msgId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateGeoLiveViewed(peer: _1!, msgId: _2!) } public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4222,12 +4139,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateGroupCall(flags: _1!, peer: _2, call: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateGroupCall(flags: _1!, peer: _2, call: _3!) } public static func parse_updateGroupCallChainBlocks(_ reader: BufferReader) -> Update? { var _1: Api.InputGroupCall? @@ -4246,12 +4161,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateGroupCallChainBlocks(call: _1!, subChainId: _2!, blocks: _3!, nextOffset: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateGroupCallChainBlocks(call: _1!, subChainId: _2!, blocks: _3!, nextOffset: _4!) } public static func parse_updateGroupCallConnection(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4262,12 +4176,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateGroupCallConnection(flags: _1!, params: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateGroupCallConnection(flags: _1!, params: _2!) } public static func parse_updateGroupCallEncryptedMessage(_ reader: BufferReader) -> Update? { var _1: Api.InputGroupCall? @@ -4283,12 +4194,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateGroupCallEncryptedMessage(call: _1!, fromId: _2!, encryptedMessage: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateGroupCallEncryptedMessage(call: _1!, fromId: _2!, encryptedMessage: _3!) } public static func parse_updateGroupCallMessage(_ reader: BufferReader) -> Update? { var _1: Api.InputGroupCall? @@ -4301,12 +4210,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateGroupCallMessage(call: _1!, message: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateGroupCallMessage(call: _1!, message: _2!) } public static func parse_updateGroupCallParticipants(_ reader: BufferReader) -> Update? { var _1: Api.InputGroupCall? @@ -4322,12 +4228,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateGroupCallParticipants(call: _1!, participants: _2!, version: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateGroupCallParticipants(call: _1!, participants: _2!, version: _3!) } public static func parse_updateInlineBotCallbackQuery(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4353,12 +4257,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateInlineBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, msgId: _4!, chatInstance: _5!, data: _6, gameShortName: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.Update.updateInlineBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, msgId: _4!, chatInstance: _5!, data: _6, gameShortName: _7) } public static func parse_updateLangPack(_ reader: BufferReader) -> Update? { var _1: Api.LangPackDifference? @@ -4366,23 +4272,15 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.LangPackDifference } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateLangPack(difference: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateLangPack(difference: _1!) } public static func parse_updateLangPackTooLong(_ reader: BufferReader) -> Update? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.Update.updateLangPackTooLong(langCode: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateLangPackTooLong(langCode: _1!) } public static func parse_updateLoginToken(_ reader: BufferReader) -> Update? { return Api.Update.updateLoginToken @@ -4401,12 +4299,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateMessageExtendedMedia(peer: _1!, msgId: _2!, extendedMedia: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateMessageExtendedMedia(peer: _1!, msgId: _2!, extendedMedia: _3!) } public static func parse_updateMessageID(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4415,12 +4311,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateMessageID(id: _1!, randomId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateMessageID(id: _1!, randomId: _2!) } public static func parse_updateMessagePoll(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4439,12 +4332,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateMessagePoll(flags: _1!, pollId: _2!, poll: _3, results: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateMessagePoll(flags: _1!, pollId: _2!, poll: _3, results: _4!) } public static func parse_updateMessagePollVote(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -4463,12 +4355,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateMessagePollVote(pollId: _1!, peer: _2!, options: _3!, qts: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateMessagePollVote(pollId: _1!, peer: _2!, options: _3!, qts: _4!) } public static func parse_updateMessageReactions(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4495,12 +4386,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateMessageReactions(flags: _1!, peer: _2!, msgId: _3!, topMsgId: _4, savedPeerId: _5, reactions: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.Update.updateMessageReactions(flags: _1!, peer: _2!, msgId: _3!, topMsgId: _4, savedPeerId: _5, reactions: _6!) } public static func parse_updateMonoForumNoPaidException(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4514,12 +4406,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateMonoForumNoPaidException(flags: _1!, channelId: _2!, savedPeerId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateMonoForumNoPaidException(flags: _1!, channelId: _2!, savedPeerId: _3!) } public static func parse_updateMoveStickerSetToTop(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4528,12 +4418,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateMoveStickerSetToTop(flags: _1!, stickerset: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateMoveStickerSetToTop(flags: _1!, stickerset: _2!) } public static func parse_updateNewAuthorization(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4551,12 +4438,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateNewAuthorization(flags: _1!, hash: _2!, date: _3, device: _4, location: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updateNewAuthorization(flags: _1!, hash: _2!, date: _3, device: _4, location: _5) } public static func parse_updateNewChannelMessage(_ reader: BufferReader) -> Update? { var _1: Api.Message? @@ -4570,12 +4457,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateNewChannelMessage(message: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateNewChannelMessage(message: _1!, pts: _2!, ptsCount: _3!) } public static func parse_updateNewEncryptedMessage(_ reader: BufferReader) -> Update? { var _1: Api.EncryptedMessage? @@ -4586,12 +4471,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateNewEncryptedMessage(message: _1!, qts: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateNewEncryptedMessage(message: _1!, qts: _2!) } public static func parse_updateNewMessage(_ reader: BufferReader) -> Update? { var _1: Api.Message? @@ -4605,12 +4487,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateNewMessage(message: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateNewMessage(message: _1!, pts: _2!, ptsCount: _3!) } public static func parse_updateNewQuickReply(_ reader: BufferReader) -> Update? { var _1: Api.QuickReply? @@ -4618,12 +4498,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.QuickReply } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateNewQuickReply(quickReply: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateNewQuickReply(quickReply: _1!) } public static func parse_updateNewScheduledMessage(_ reader: BufferReader) -> Update? { var _1: Api.Message? @@ -4631,12 +4507,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Message } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateNewScheduledMessage(message: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateNewScheduledMessage(message: _1!) } public static func parse_updateNewStickerSet(_ reader: BufferReader) -> Update? { var _1: Api.messages.StickerSet? @@ -4644,12 +4516,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.messages.StickerSet } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateNewStickerSet(stickerset: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateNewStickerSet(stickerset: _1!) } public static func parse_updateNewStoryReaction(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4665,12 +4533,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateNewStoryReaction(storyId: _1!, peer: _2!, reaction: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateNewStoryReaction(storyId: _1!, peer: _2!, reaction: _3!) } public static func parse_updateNotifySettings(_ reader: BufferReader) -> Update? { var _1: Api.NotifyPeer? @@ -4683,12 +4549,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateNotifySettings(peer: _1!, notifySettings: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateNotifySettings(peer: _1!, notifySettings: _2!) } public static func parse_updatePaidReactionPrivacy(_ reader: BufferReader) -> Update? { var _1: Api.PaidReactionPrivacy? @@ -4696,12 +4559,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.PaidReactionPrivacy } let _c1 = _1 != nil - if _c1 { - return Api.Update.updatePaidReactionPrivacy(private: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updatePaidReactionPrivacy(private: _1!) } public static func parse_updatePeerBlocked(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4712,12 +4571,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePeerBlocked(flags: _1!, peerId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updatePeerBlocked(flags: _1!, peerId: _2!) } public static func parse_updatePeerHistoryTTL(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4731,12 +4587,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePeerHistoryTTL(flags: _1!, peer: _2!, ttlPeriod: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updatePeerHistoryTTL(flags: _1!, peer: _2!, ttlPeriod: _3) } public static func parse_updatePeerLocated(_ reader: BufferReader) -> Update? { var _1: [Api.PeerLocated]? @@ -4744,12 +4598,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerLocated.self) } let _c1 = _1 != nil - if _c1 { - return Api.Update.updatePeerLocated(peers: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updatePeerLocated(peers: _1!) } public static func parse_updatePeerSettings(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -4762,12 +4612,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePeerSettings(peer: _1!, settings: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updatePeerSettings(peer: _1!, settings: _2!) } public static func parse_updatePeerWallpaper(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4783,12 +4630,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePeerWallpaper(flags: _1!, peer: _2!, wallpaper: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updatePeerWallpaper(flags: _1!, peer: _2!, wallpaper: _3) } public static func parse_updatePendingJoinRequests(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -4804,12 +4649,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePendingJoinRequests(peer: _1!, requestsPending: _2!, recentRequesters: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updatePendingJoinRequests(peer: _1!, requestsPending: _2!, recentRequesters: _3!) } public static func parse_updatePhoneCall(_ reader: BufferReader) -> Update? { var _1: Api.PhoneCall? @@ -4817,12 +4660,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall } let _c1 = _1 != nil - if _c1 { - return Api.Update.updatePhoneCall(phoneCall: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updatePhoneCall(phoneCall: _1!) } public static func parse_updatePhoneCallSignalingData(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -4831,12 +4670,9 @@ public extension Api { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePhoneCallSignalingData(phoneCallId: _1!, data: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updatePhoneCallSignalingData(phoneCallId: _1!, data: _2!) } public static func parse_updatePinnedChannelMessages(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4856,12 +4692,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updatePinnedChannelMessages(flags: _1!, channelId: _2!, messages: _3!, pts: _4!, ptsCount: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updatePinnedChannelMessages(flags: _1!, channelId: _2!, messages: _3!, pts: _4!, ptsCount: _5!) } public static func parse_updatePinnedDialogs(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4875,12 +4711,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePinnedDialogs(flags: _1!, folderId: _2, order: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updatePinnedDialogs(flags: _1!, folderId: _2, order: _3) } public static func parse_updatePinnedForumTopic(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4894,12 +4728,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePinnedForumTopic(flags: _1!, peer: _2!, topicId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updatePinnedForumTopic(flags: _1!, peer: _2!, topicId: _3!) } public static func parse_updatePinnedForumTopics(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4915,12 +4747,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePinnedForumTopics(flags: _1!, peer: _2!, order: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updatePinnedForumTopics(flags: _1!, peer: _2!, order: _3) } public static func parse_updatePinnedMessages(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4942,12 +4772,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updatePinnedMessages(flags: _1!, peer: _2!, messages: _3!, pts: _4!, ptsCount: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updatePinnedMessages(flags: _1!, peer: _2!, messages: _3!, pts: _4!, ptsCount: _5!) } public static func parse_updatePinnedSavedDialogs(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -4958,12 +4788,9 @@ public extension Api { } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.Update.updatePinnedSavedDialogs(flags: _1!, order: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updatePinnedSavedDialogs(flags: _1!, order: _2) } public static func parse_updatePrivacy(_ reader: BufferReader) -> Update? { var _1: Api.PrivacyKey? @@ -4976,12 +4803,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePrivacy(key: _1!, rules: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updatePrivacy(key: _1!, rules: _2!) } public static func parse_updatePtsChanged(_ reader: BufferReader) -> Update? { return Api.Update.updatePtsChanged @@ -4992,12 +4816,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateQuickReplies(quickReplies: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateQuickReplies(quickReplies: _1!) } public static func parse_updateQuickReplyMessage(_ reader: BufferReader) -> Update? { var _1: Api.Message? @@ -5005,12 +4825,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Message } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateQuickReplyMessage(message: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateQuickReplyMessage(message: _1!) } public static func parse_updateReadChannelDiscussionInbox(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -5031,12 +4847,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateReadChannelDiscussionInbox(flags: _1!, channelId: _2!, topMsgId: _3!, readMaxId: _4!, broadcastId: _5, broadcastPost: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.Update.updateReadChannelDiscussionInbox(flags: _1!, channelId: _2!, topMsgId: _3!, readMaxId: _4!, broadcastId: _5, broadcastPost: _6) } public static func parse_updateReadChannelDiscussionOutbox(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -5048,12 +4865,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateReadChannelDiscussionOutbox(channelId: _1!, topMsgId: _2!, readMaxId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateReadChannelDiscussionOutbox(channelId: _1!, topMsgId: _2!, readMaxId: _3!) } public static func parse_updateReadChannelInbox(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -5074,12 +4889,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateReadChannelInbox(flags: _1!, folderId: _2, channelId: _3!, maxId: _4!, stillUnreadCount: _5!, pts: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.Update.updateReadChannelInbox(flags: _1!, folderId: _2, channelId: _3!, maxId: _4!, stillUnreadCount: _5!, pts: _6!) } public static func parse_updateReadChannelOutbox(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -5088,12 +4904,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateReadChannelOutbox(channelId: _1!, maxId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateReadChannelOutbox(channelId: _1!, maxId: _2!) } public static func parse_updateReadFeaturedEmojiStickers(_ reader: BufferReader) -> Update? { return Api.Update.updateReadFeaturedEmojiStickers @@ -5128,12 +4941,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Update.updateReadHistoryInbox(flags: _1!, folderId: _2, peer: _3!, topMsgId: _4, maxId: _5!, stillUnreadCount: _6!, pts: _7!, ptsCount: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.Update.updateReadHistoryInbox(flags: _1!, folderId: _2, peer: _3!, topMsgId: _4, maxId: _5!, stillUnreadCount: _6!, pts: _7!, ptsCount: _8!) } public static func parse_updateReadHistoryOutbox(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -5150,12 +4966,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateReadHistoryOutbox(peer: _1!, maxId: _2!, pts: _3!, ptsCount: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateReadHistoryOutbox(peer: _1!, maxId: _2!, pts: _3!, ptsCount: _4!) } public static func parse_updateReadMessagesContents(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -5175,12 +4990,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateReadMessagesContents(flags: _1!, messages: _2!, pts: _3!, ptsCount: _4!, date: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updateReadMessagesContents(flags: _1!, messages: _2!, pts: _3!, ptsCount: _4!, date: _5) } public static func parse_updateReadMonoForumInbox(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -5194,12 +5009,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateReadMonoForumInbox(channelId: _1!, savedPeerId: _2!, readMaxId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateReadMonoForumInbox(channelId: _1!, savedPeerId: _2!, readMaxId: _3!) } public static func parse_updateReadMonoForumOutbox(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -5213,12 +5026,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateReadMonoForumOutbox(channelId: _1!, savedPeerId: _2!, readMaxId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateReadMonoForumOutbox(channelId: _1!, savedPeerId: _2!, readMaxId: _3!) } public static func parse_updateReadStories(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -5229,12 +5040,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateReadStories(peer: _1!, maxId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateReadStories(peer: _1!, maxId: _2!) } public static func parse_updateRecentEmojiStatuses(_ reader: BufferReader) -> Update? { return Api.Update.updateRecentEmojiStatuses @@ -5254,12 +5062,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateSavedDialogPinned(flags: _1!, peer: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateSavedDialogPinned(flags: _1!, peer: _2!) } public static func parse_updateSavedGifs(_ reader: BufferReader) -> Update? { return Api.Update.updateSavedGifs @@ -5276,12 +5081,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.auth.SentCode } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateSentPhoneCode(sentCode: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateSentPhoneCode(sentCode: _1!) } public static func parse_updateSentStoryReaction(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -5297,12 +5098,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateSentStoryReaction(peer: _1!, storyId: _2!, reaction: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateSentStoryReaction(peer: _1!, storyId: _2!, reaction: _3!) } public static func parse_updateServiceNotification(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -5327,23 +5126,20 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateServiceNotification(flags: _1!, inboxDate: _2, type: _3!, message: _4!, media: _5!, entities: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.Update.updateServiceNotification(flags: _1!, inboxDate: _2, type: _3!, message: _4!, media: _5!, entities: _6!) } public static func parse_updateSmsJob(_ reader: BufferReader) -> Update? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.Update.updateSmsJob(jobId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateSmsJob(jobId: _1!) } public static func parse_updateStarGiftAuctionState(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -5354,12 +5150,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStarGiftAuctionState(giftId: _1!, state: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateStarGiftAuctionState(giftId: _1!, state: _2!) } public static func parse_updateStarGiftAuctionUserState(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -5370,12 +5163,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStarGiftAuctionUserState(giftId: _1!, userState: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateStarGiftAuctionUserState(giftId: _1!, userState: _2!) } public static func parse_updateStarsBalance(_ reader: BufferReader) -> Update? { var _1: Api.StarsAmount? @@ -5383,12 +5173,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.StarsAmount } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateStarsBalance(balance: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateStarsBalance(balance: _1!) } public static func parse_updateStarsRevenueStatus(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -5401,23 +5187,16 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStarsRevenueStatus(peer: _1!, status: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateStarsRevenueStatus(peer: _1!, status: _2!) } public static func parse_updateStickerSets(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.Update.updateStickerSets(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateStickerSets(flags: _1!) } public static func parse_updateStickerSetsOrder(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -5428,12 +5207,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStickerSetsOrder(flags: _1!, order: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateStickerSetsOrder(flags: _1!, order: _2!) } public static func parse_updateStoriesStealthMode(_ reader: BufferReader) -> Update? { var _1: Api.StoriesStealthMode? @@ -5441,12 +5217,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateStoriesStealthMode(stealthMode: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateStoriesStealthMode(stealthMode: _1!) } public static func parse_updateStory(_ reader: BufferReader) -> Update? { var _1: Api.Peer? @@ -5459,12 +5231,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStory(peer: _1!, story: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateStory(peer: _1!, story: _2!) } public static func parse_updateStoryID(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -5473,12 +5242,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStoryID(id: _1!, randomId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateStoryID(id: _1!, randomId: _2!) } public static func parse_updateTheme(_ reader: BufferReader) -> Update? { var _1: Api.Theme? @@ -5486,12 +5252,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Theme } let _c1 = _1 != nil - if _c1 { - return Api.Update.updateTheme(theme: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateTheme(theme: _1!) } public static func parse_updateTranscribedAudio(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -5511,23 +5273,19 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateTranscribedAudio(flags: _1!, peer: _2!, msgId: _3!, transcriptionId: _4!, text: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Update.updateTranscribedAudio(flags: _1!, peer: _2!, msgId: _3!, transcriptionId: _4!, text: _5!) } public static func parse_updateUser(_ reader: BufferReader) -> Update? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Update.updateUser(userId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateUser(userId: _1!) } public static func parse_updateUserEmojiStatus(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -5538,12 +5296,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateUserEmojiStatus(userId: _1!, emojiStatus: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateUserEmojiStatus(userId: _1!, emojiStatus: _2!) } public static func parse_updateUserName(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -5560,12 +5315,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateUserName(userId: _1!, firstName: _2!, lastName: _3!, usernames: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateUserName(userId: _1!, firstName: _2!, lastName: _3!, usernames: _4!) } public static func parse_updateUserPhone(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -5574,12 +5328,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateUserPhone(userId: _1!, phone: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateUserPhone(userId: _1!, phone: _2!) } public static func parse_updateUserStatus(_ reader: BufferReader) -> Update? { var _1: Int64? @@ -5590,12 +5341,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateUserStatus(userId: _1!, status: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Update.updateUserStatus(userId: _1!, status: _2!) } public static func parse_updateUserTyping(_ reader: BufferReader) -> Update? { var _1: Int32? @@ -5612,12 +5360,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateUserTyping(flags: _1!, userId: _2!, topMsgId: _3, action: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Update.updateUserTyping(flags: _1!, userId: _2!, topMsgId: _3, action: _4!) } public static func parse_updateWebPage(_ reader: BufferReader) -> Update? { var _1: Api.WebPage? @@ -5631,23 +5378,17 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateWebPage(webpage: _1!, pts: _2!, ptsCount: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.Update.updateWebPage(webpage: _1!, pts: _2!, ptsCount: _3!) } public static func parse_updateWebViewResultSent(_ reader: BufferReader) -> Update? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Update.updateWebViewResultSent(queryId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Update.updateWebViewResultSent(queryId: _1!) } } diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index 69ae42de..247694f3 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -159,12 +159,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Updates.updateShort(update: _1!, date: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Updates.updateShort(update: _1!, date: _2!) } public static func parse_updateShortChatMessage(_ reader: BufferReader) -> Updates? { var _1: Int32? @@ -212,12 +209,20 @@ public extension Api { let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 25) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.Updates.updateShortChatMessage(flags: _1!, id: _2!, fromId: _3!, chatId: _4!, message: _5!, pts: _6!, ptsCount: _7!, date: _8!, fwdFrom: _9, viaBotId: _10, replyTo: _11, entities: _12, ttlPeriod: _13) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + return Api.Updates.updateShortChatMessage(flags: _1!, id: _2!, fromId: _3!, chatId: _4!, message: _5!, pts: _6!, ptsCount: _7!, date: _8!, fwdFrom: _9, viaBotId: _10, replyTo: _11, entities: _12, ttlPeriod: _13) } public static func parse_updateShortMessage(_ reader: BufferReader) -> Updates? { var _1: Int32? @@ -262,12 +267,19 @@ public extension Api { let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 25) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.Updates.updateShortMessage(flags: _1!, id: _2!, userId: _3!, message: _4!, pts: _5!, ptsCount: _6!, date: _7!, fwdFrom: _8, viaBotId: _9, replyTo: _10, entities: _11, ttlPeriod: _12) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + return Api.Updates.updateShortMessage(flags: _1!, id: _2!, userId: _3!, message: _4!, pts: _5!, ptsCount: _6!, date: _7!, fwdFrom: _8, viaBotId: _9, replyTo: _10, entities: _11, ttlPeriod: _12) } public static func parse_updateShortSentMessage(_ reader: BufferReader) -> Updates? { var _1: Int32? @@ -298,12 +310,15 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 9) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 25) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Updates.updateShortSentMessage(flags: _1!, id: _2!, pts: _3!, ptsCount: _4!, date: _5!, media: _6, entities: _7, ttlPeriod: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.Updates.updateShortSentMessage(flags: _1!, id: _2!, pts: _3!, ptsCount: _4!, date: _5!, media: _6, entities: _7, ttlPeriod: _8) } public static func parse_updates(_ reader: BufferReader) -> Updates? { var _1: [Api.Update]? @@ -327,12 +342,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Updates.updates(updates: _1!, users: _2!, chats: _3!, date: _4!, seq: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Updates.updates(updates: _1!, users: _2!, chats: _3!, date: _4!, seq: _5!) } public static func parse_updatesCombined(_ reader: BufferReader) -> Updates? { var _1: [Api.Update]? @@ -359,12 +374,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Updates.updatesCombined(updates: _1!, users: _2!, chats: _3!, date: _4!, seqStart: _5!, seq: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.Updates.updatesCombined(updates: _1!, users: _2!, chats: _3!, date: _4!, seqStart: _5!, seq: _6!) } public static func parse_updatesTooLong(_ reader: BufferReader) -> Updates? { return Api.Updates.updatesTooLong @@ -418,12 +434,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.UrlAuthResult.urlAuthResultAccepted(url: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.UrlAuthResult.urlAuthResultAccepted(url: _1!) } public static func parse_urlAuthResultDefault(_ reader: BufferReader) -> UrlAuthResult? { return Api.UrlAuthResult.urlAuthResultDefault @@ -440,12 +452,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.UrlAuthResult.urlAuthResultRequest(flags: _1!, bot: _2!, domain: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.UrlAuthResult.urlAuthResultRequest(flags: _1!, bot: _2!, domain: _3!) } } @@ -593,23 +603,36 @@ public extension Api { let _c20 = (Int(_2!) & Int(1 << 12) == 0) || _20 != nil let _c21 = (Int(_2!) & Int(1 << 14) == 0) || _21 != nil let _c22 = (Int(_2!) & Int(1 << 15) == 0) || _22 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { - return Api.User.user(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, firstName: _5, lastName: _6, username: _7, phone: _8, photo: _9, status: _10, botInfoVersion: _11, restrictionReason: _12, botInlinePlaceholder: _13, langCode: _14, emojiStatus: _15, usernames: _16, storiesMaxId: _17, color: _18, profileColor: _19, botActiveUsers: _20, botVerificationIcon: _21, sendPaidMessagesStars: _22) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + if !_c20 { return nil } + if !_c21 { return nil } + if !_c22 { return nil } + return Api.User.user(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, firstName: _5, lastName: _6, username: _7, phone: _8, photo: _9, status: _10, botInfoVersion: _11, restrictionReason: _12, botInlinePlaceholder: _13, langCode: _14, emojiStatus: _15, usernames: _16, storiesMaxId: _17, color: _18, profileColor: _19, botActiveUsers: _20, botVerificationIcon: _21, sendPaidMessagesStars: _22) } public static func parse_userEmpty(_ reader: BufferReader) -> User? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.User.userEmpty(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.User.userEmpty(id: _1!) } } @@ -842,12 +865,46 @@ public extension Api { let _c37 = (Int(_2!) & Int(1 << 20) == 0) || _37 != nil let _c38 = (Int(_2!) & Int(1 << 21) == 0) || _38 != nil let _c39 = (Int(_2!) & Int(1 << 22) == 0) || _39 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 { - return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38, note: _39) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + if !_c20 { return nil } + if !_c21 { return nil } + if !_c22 { return nil } + if !_c23 { return nil } + if !_c24 { return nil } + if !_c25 { return nil } + if !_c26 { return nil } + if !_c27 { return nil } + if !_c28 { return nil } + if !_c29 { return nil } + if !_c30 { return nil } + if !_c31 { return nil } + if !_c32 { return nil } + if !_c33 { return nil } + if !_c34 { return nil } + if !_c35 { return nil } + if !_c36 { return nil } + if !_c37 { return nil } + if !_c38 { return nil } + if !_c39 { return nil } + return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38, note: _39) } } @@ -899,12 +956,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.UserProfilePhoto.userProfilePhoto(flags: _1!, photoId: _2!, strippedThumb: _3, dcId: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.UserProfilePhoto.userProfilePhoto(flags: _1!, photoId: _2!, strippedThumb: _3, dcId: _4!) } public static func parse_userProfilePhotoEmpty(_ reader: BufferReader) -> UserProfilePhoto? { return Api.UserProfilePhoto.userProfilePhotoEmpty @@ -986,56 +1042,36 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusLastMonth(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.UserStatus.userStatusLastMonth(flags: _1!) } public static func parse_userStatusLastWeek(_ reader: BufferReader) -> UserStatus? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusLastWeek(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.UserStatus.userStatusLastWeek(flags: _1!) } public static func parse_userStatusOffline(_ reader: BufferReader) -> UserStatus? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusOffline(wasOnline: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.UserStatus.userStatusOffline(wasOnline: _1!) } public static func parse_userStatusOnline(_ reader: BufferReader) -> UserStatus? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusOnline(expires: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.UserStatus.userStatusOnline(expires: _1!) } public static func parse_userStatusRecently(_ reader: BufferReader) -> UserStatus? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusRecently(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.UserStatus.userStatusRecently(flags: _1!) } } @@ -1070,12 +1106,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Username.username(flags: _1!, username: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Username.username(flags: _1!, username: _2!) } } @@ -1155,12 +1188,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.VideoSize.videoSize(flags: _1!, type: _2!, w: _3!, h: _4!, size: _5!, videoStartTs: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.VideoSize.videoSize(flags: _1!, type: _2!, w: _3!, h: _4!, size: _5!, videoStartTs: _6) } public static func parse_videoSizeEmojiMarkup(_ reader: BufferReader) -> VideoSize? { var _1: Int64? @@ -1171,12 +1205,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.VideoSize.videoSizeEmojiMarkup(emojiId: _1!, backgroundColors: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.VideoSize.videoSizeEmojiMarkup(emojiId: _1!, backgroundColors: _2!) } public static func parse_videoSizeStickerMarkup(_ reader: BufferReader) -> VideoSize? { var _1: Api.InputStickerSet? @@ -1192,12 +1223,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.VideoSize.videoSizeStickerMarkup(stickerset: _1!, stickerId: _2!, backgroundColors: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.VideoSize.videoSizeStickerMarkup(stickerset: _1!, stickerId: _2!, backgroundColors: _3!) } } @@ -1263,12 +1292,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_2!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.WallPaper.wallPaper(id: _1!, flags: _2!, accessHash: _3!, slug: _4!, document: _5!, settings: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.WallPaper.wallPaper(id: _1!, flags: _2!, accessHash: _3!, slug: _4!, document: _5!, settings: _6) } public static func parse_wallPaperNoFile(_ reader: BufferReader) -> WallPaper? { var _1: Int64? @@ -1282,12 +1312,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_2!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.WallPaper.wallPaperNoFile(id: _1!, flags: _2!, settings: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.WallPaper.wallPaperNoFile(id: _1!, flags: _2!, settings: _3) } } @@ -1346,12 +1374,15 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.WallPaperSettings.wallPaperSettings(flags: _1!, backgroundColor: _2, secondBackgroundColor: _3, thirdBackgroundColor: _4, fourthBackgroundColor: _5, intensity: _6, rotation: _7, emoticon: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.WallPaperSettings.wallPaperSettings(flags: _1!, backgroundColor: _2, secondBackgroundColor: _3, thirdBackgroundColor: _4, fourthBackgroundColor: _5, intensity: _6, rotation: _7, emoticon: _8) } } @@ -1414,12 +1445,16 @@ public extension Api { let _c7 = _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.WebAuthorization.webAuthorization(hash: _1!, botId: _2!, domain: _3!, browser: _4!, platform: _5!, dateCreated: _6!, dateActive: _7!, ip: _8!, region: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.WebAuthorization.webAuthorization(hash: _1!, botId: _2!, domain: _3!, browser: _4!, platform: _5!, dateCreated: _6!, dateActive: _7!, ip: _8!, region: _9!) } } @@ -1488,12 +1523,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.WebDocument.webDocument(url: _1!, accessHash: _2!, size: _3!, mimeType: _4!, attributes: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.WebDocument.webDocument(url: _1!, accessHash: _2!, size: _3!, mimeType: _4!, attributes: _5!) } public static func parse_webDocumentNoProxy(_ reader: BufferReader) -> WebDocument? { var _1: String? @@ -1510,12 +1545,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.WebDocument.webDocumentNoProxy(url: _1!, size: _2!, mimeType: _3!, attributes: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.WebDocument.webDocumentNoProxy(url: _1!, size: _2!, mimeType: _3!, attributes: _4!) } } @@ -1663,12 +1697,26 @@ public extension Api { let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil let _c19 = (Int(_1!) & Int(1 << 12) == 0) || _19 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { - return Api.WebPage.webPage(flags: _1!, id: _2!, url: _3!, displayUrl: _4!, hash: _5!, type: _6, siteName: _7, title: _8, description: _9, photo: _10, embedUrl: _11, embedType: _12, embedWidth: _13, embedHeight: _14, duration: _15, author: _16, document: _17, cachedPage: _18, attributes: _19) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + return Api.WebPage.webPage(flags: _1!, id: _2!, url: _3!, displayUrl: _4!, hash: _5!, type: _6, siteName: _7, title: _8, description: _9, photo: _10, embedUrl: _11, embedType: _12, embedWidth: _13, embedHeight: _14, duration: _15, author: _16, document: _17, cachedPage: _18, attributes: _19) } public static func parse_webPageEmpty(_ reader: BufferReader) -> WebPage? { var _1: Int32? @@ -1680,12 +1728,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.WebPage.webPageEmpty(flags: _1!, id: _2!, url: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.WebPage.webPageEmpty(flags: _1!, id: _2!, url: _3) } public static func parse_webPageNotModified(_ reader: BufferReader) -> WebPage? { var _1: Int32? @@ -1694,12 +1740,9 @@ public extension Api { if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.WebPage.webPageNotModified(flags: _1!, cachedPageViews: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.WebPage.webPageNotModified(flags: _1!, cachedPageViews: _2) } public static func parse_webPagePending(_ reader: BufferReader) -> WebPage? { var _1: Int32? @@ -1714,12 +1757,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.WebPage.webPagePending(flags: _1!, id: _2!, url: _3, date: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.WebPage.webPagePending(flags: _1!, id: _2!, url: _3, date: _4!) } } diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 17bc8c5a..7814d89e 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -93,12 +93,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.WebPageAttribute.webPageAttributeStarGiftAuction(gift: _1!, endDate: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.WebPageAttribute.webPageAttributeStarGiftAuction(gift: _1!, endDate: _2!) } public static func parse_webPageAttributeStarGiftCollection(_ reader: BufferReader) -> WebPageAttribute? { var _1: [Api.Document]? @@ -106,12 +103,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil - if _c1 { - return Api.WebPageAttribute.webPageAttributeStarGiftCollection(icons: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.WebPageAttribute.webPageAttributeStarGiftCollection(icons: _1!) } public static func parse_webPageAttributeStickerSet(_ reader: BufferReader) -> WebPageAttribute? { var _1: Int32? @@ -122,12 +115,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.WebPageAttribute.webPageAttributeStickerSet(flags: _1!, stickers: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.WebPageAttribute.webPageAttributeStickerSet(flags: _1!, stickers: _2!) } public static func parse_webPageAttributeStory(_ reader: BufferReader) -> WebPageAttribute? { var _1: Int32? @@ -146,12 +136,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.WebPageAttribute.webPageAttributeStory(flags: _1!, peer: _2!, id: _3!, story: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.WebPageAttribute.webPageAttributeStory(flags: _1!, peer: _2!, id: _3!, story: _4) } public static func parse_webPageAttributeTheme(_ reader: BufferReader) -> WebPageAttribute? { var _1: Int32? @@ -167,12 +156,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.WebPageAttribute.webPageAttributeTheme(flags: _1!, documents: _2, settings: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.WebPageAttribute.webPageAttributeTheme(flags: _1!, documents: _2, settings: _3) } public static func parse_webPageAttributeUniqueStarGift(_ reader: BufferReader) -> WebPageAttribute? { var _1: Api.StarGift? @@ -180,12 +167,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.StarGift } let _c1 = _1 != nil - if _c1 { - return Api.WebPageAttribute.webPageAttributeUniqueStarGift(gift: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.WebPageAttribute.webPageAttributeUniqueStarGift(gift: _1!) } } @@ -222,12 +205,9 @@ public extension Api { } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.WebViewMessageSent.webViewMessageSent(flags: _1!, msgId: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.WebViewMessageSent.webViewMessageSent(flags: _1!, msgId: _2) } } @@ -266,12 +246,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.WebViewResult.webViewResultUrl(flags: _1!, queryId: _2, url: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.WebViewResult.webViewResultUrl(flags: _1!, queryId: _2, url: _3!) } } @@ -346,12 +324,13 @@ public extension Api.account { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.AuthorizationForm.authorizationForm(flags: _1!, requiredTypes: _2!, values: _3!, errors: _4!, users: _5!, privacyPolicyUrl: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.account.AuthorizationForm.authorizationForm(flags: _1!, requiredTypes: _2!, values: _3!, errors: _4!, users: _5!, privacyPolicyUrl: _6) } } @@ -392,12 +371,9 @@ public extension Api.account { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.Authorizations.authorizations(authorizationTtlDays: _1!, authorizations: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.account.Authorizations.authorizations(authorizationTtlDays: _1!, authorizations: _2!) } } @@ -442,12 +418,10 @@ public extension Api.account { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.AutoDownloadSettings.autoDownloadSettings(low: _1!, medium: _2!, high: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.account.AutoDownloadSettings.autoDownloadSettings(low: _1!, medium: _2!, high: _3!) } } @@ -522,12 +496,13 @@ public extension Api.account { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.AutoSaveSettings.autoSaveSettings(usersSettings: _1!, chatsSettings: _2!, broadcastsSettings: _3!, exceptions: _4!, chats: _5!, users: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.account.AutoSaveSettings.autoSaveSettings(usersSettings: _1!, chatsSettings: _2!, broadcastsSettings: _3!, exceptions: _4!, chats: _5!, users: _6!) } } @@ -584,12 +559,10 @@ public extension Api.account { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.BusinessChatLinks.businessChatLinks(links: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.account.BusinessChatLinks.businessChatLinks(links: _1!, chats: _2!, users: _3!) } } @@ -667,12 +640,13 @@ public extension Api.account { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.ChatThemes.chatThemes(flags: _1!, hash: _2!, themes: _3!, chats: _4!, users: _5!, nextOffset: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.account.ChatThemes.chatThemes(flags: _1!, hash: _2!, themes: _3!, chats: _4!, users: _5!, nextOffset: _6) } public static func parse_chatThemesNotModified(_ reader: BufferReader) -> ChatThemes? { return Api.account.ChatThemes.chatThemesNotModified @@ -722,12 +696,9 @@ public extension Api.account { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.ConnectedBots.connectedBots(connectedBots: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.account.ConnectedBots.connectedBots(connectedBots: _1!, users: _2!) } } @@ -758,12 +729,8 @@ public extension Api.account { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.account.ContentSettings.contentSettings(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.account.ContentSettings.contentSettings(flags: _1!) } } @@ -804,12 +771,8 @@ public extension Api.account { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.account.EmailVerified.emailVerified(email: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.account.EmailVerified.emailVerified(email: _1!) } public static func parse_emailVerifiedLogin(_ reader: BufferReader) -> EmailVerified? { var _1: String? @@ -820,12 +783,9 @@ public extension Api.account { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.EmailVerified.emailVerifiedLogin(email: _1!, sentCode: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.account.EmailVerified.emailVerifiedLogin(email: _1!, sentCode: _2!) } } @@ -875,12 +835,9 @@ public extension Api.account { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.EmojiStatuses.emojiStatuses(hash: _1!, statuses: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.account.EmojiStatuses.emojiStatuses(hash: _1!, statuses: _2!) } public static func parse_emojiStatusesNotModified(_ reader: BufferReader) -> EmojiStatuses? { return Api.account.EmojiStatuses.emojiStatusesNotModified @@ -914,12 +871,8 @@ public extension Api.account { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.account.PaidMessagesRevenue.paidMessagesRevenue(starsAmount: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.account.PaidMessagesRevenue.paidMessagesRevenue(starsAmount: _1!) } } @@ -952,12 +905,8 @@ public extension Api.account { _1 = Api.parse(reader, signature: signature) as? Api.DataJSON } let _c1 = _1 != nil - if _c1 { - return Api.account.PasskeyRegistrationOptions.passkeyRegistrationOptions(options: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.account.PasskeyRegistrationOptions.passkeyRegistrationOptions(options: _1!) } } @@ -994,12 +943,8 @@ public extension Api.account { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Passkey.self) } let _c1 = _1 != nil - if _c1 { - return Api.account.Passkeys.passkeys(passkeys: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.account.Passkeys.passkeys(passkeys: _1!) } } @@ -1076,12 +1021,18 @@ public extension Api.account { let _c9 = _9 != nil let _c10 = (Int(_1!) & Int(1 << 5) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 6) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.account.Password.password(flags: _1!, currentAlgo: _2, srpB: _3, srpId: _4, hint: _5, emailUnconfirmedPattern: _6, newAlgo: _7!, newSecureAlgo: _8!, secureRandom: _9!, pendingResetDate: _10, loginEmailPattern: _11) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + return Api.account.Password.password(flags: _1!, currentAlgo: _2, srpB: _3, srpId: _4, hint: _5, emailUnconfirmedPattern: _6, newAlgo: _7!, newSecureAlgo: _8!, secureRandom: _9!, pendingResetDate: _10, loginEmailPattern: _11) } } @@ -1136,12 +1087,13 @@ public extension Api.account { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.PasswordInputSettings.passwordInputSettings(flags: _1!, newAlgo: _2, newPasswordHash: _3, hint: _4, email: _5, newSecureSettings: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.account.PasswordInputSettings.passwordInputSettings(flags: _1!, newAlgo: _2, newPasswordHash: _3, hint: _4, email: _5, newSecureSettings: _6) } } @@ -1182,12 +1134,10 @@ public extension Api.account { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.PasswordSettings.passwordSettings(flags: _1!, email: _2, secureSettings: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.account.PasswordSettings.passwordSettings(flags: _1!, email: _2, secureSettings: _3) } } @@ -1244,12 +1194,10 @@ public extension Api.account { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.PrivacyRules.privacyRules(rules: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.account.PrivacyRules.privacyRules(rules: _1!, chats: _2!, users: _3!) } } @@ -1298,12 +1246,8 @@ public extension Api.account { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.account.ResetPasswordResult.resetPasswordFailedWait(retryDate: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.account.ResetPasswordResult.resetPasswordFailedWait(retryDate: _1!) } public static func parse_resetPasswordOk(_ reader: BufferReader) -> ResetPasswordResult? { return Api.account.ResetPasswordResult.resetPasswordOk @@ -1312,12 +1256,8 @@ public extension Api.account { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.account.ResetPasswordResult.resetPasswordRequestedWait(untilDate: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.account.ResetPasswordResult.resetPasswordRequestedWait(untilDate: _1!) } } @@ -1388,12 +1328,13 @@ public extension Api.account { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.ResolvedBusinessChatLinks.resolvedBusinessChatLinks(flags: _1!, peer: _2!, message: _3!, entities: _4, chats: _5!, users: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.account.ResolvedBusinessChatLinks.resolvedBusinessChatLinks(flags: _1!, peer: _2!, message: _3!, entities: _4, chats: _5!, users: _6!) } } diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 49988fc9..b71ce826 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -34,12 +34,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.BusinessGreetingMessage.businessGreetingMessage(shortcutId: _1!, recipients: _2!, noActivityDays: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.BusinessGreetingMessage.businessGreetingMessage(shortcutId: _1!, recipients: _2!, noActivityDays: _3!) } } @@ -84,12 +82,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.BusinessIntro.businessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.BusinessIntro.businessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4) } } @@ -130,12 +127,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.BusinessLocation.businessLocation(flags: _1!, geoPoint: _2, address: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.BusinessLocation.businessLocation(flags: _1!, geoPoint: _2, address: _3!) } } @@ -176,12 +171,9 @@ public extension Api { } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil - if _c1 && _c2 { - return Api.BusinessRecipients.businessRecipients(flags: _1!, users: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.BusinessRecipients.businessRecipients(flags: _1!, users: _2) } } @@ -216,12 +208,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.BusinessWeeklyOpen.businessWeeklyOpen(startMinute: _1!, endMinute: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.BusinessWeeklyOpen.businessWeeklyOpen(startMinute: _1!, endMinute: _2!) } } @@ -266,12 +255,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.BusinessWorkHours.businessWorkHours(flags: _1!, timezoneId: _2!, weeklyOpen: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.BusinessWorkHours.businessWorkHours(flags: _1!, timezoneId: _2!, weeklyOpen: _3!) } } @@ -308,12 +295,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.CdnPublicKey.self) } let _c1 = _1 != nil - if _c1 { - return Api.CdnConfig.cdnConfig(publicKeys: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.CdnConfig.cdnConfig(publicKeys: _1!) } } @@ -348,12 +331,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.CdnPublicKey.cdnPublicKey(dcId: _1!, publicKey: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.CdnPublicKey.cdnPublicKey(dcId: _1!, publicKey: _2!) } } @@ -398,12 +378,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.ChannelAdminLogEvent.channelAdminLogEvent(id: _1!, date: _2!, userId: _3!, action: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.ChannelAdminLogEvent.channelAdminLogEvent(id: _1!, date: _2!, userId: _3!, action: _4!) } } @@ -922,12 +901,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeAbout(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeAbout(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangeAvailableReactions(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ChatReactions? @@ -940,12 +916,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeAvailableReactions(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeAvailableReactions(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangeEmojiStatus(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.EmojiStatus? @@ -958,12 +931,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeEmojiStatus(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeEmojiStatus(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangeEmojiStickerSet(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.InputStickerSet? @@ -976,12 +946,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeEmojiStickerSet(prevStickerset: _1!, newStickerset: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeEmojiStickerSet(prevStickerset: _1!, newStickerset: _2!) } public static func parse_channelAdminLogEventActionChangeHistoryTTL(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Int32? @@ -990,12 +957,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeHistoryTTL(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeHistoryTTL(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangeLinkedChat(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Int64? @@ -1004,12 +968,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeLinkedChat(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeLinkedChat(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangeLocation(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ChannelLocation? @@ -1022,12 +983,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeLocation(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeLocation(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangePeerColor(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.PeerColor? @@ -1040,12 +998,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangePeerColor(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangePeerColor(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangePhoto(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Photo? @@ -1058,12 +1013,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangePhoto(prevPhoto: _1!, newPhoto: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangePhoto(prevPhoto: _1!, newPhoto: _2!) } public static func parse_channelAdminLogEventActionChangeProfilePeerColor(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.PeerColor? @@ -1076,12 +1028,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeProfilePeerColor(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeProfilePeerColor(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangeStickerSet(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.InputStickerSet? @@ -1094,12 +1043,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeStickerSet(prevStickerset: _1!, newStickerset: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeStickerSet(prevStickerset: _1!, newStickerset: _2!) } public static func parse_channelAdminLogEventActionChangeTitle(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: String? @@ -1108,12 +1054,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeTitle(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeTitle(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangeUsername(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: String? @@ -1122,12 +1065,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeUsername(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeUsername(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangeUsernames(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: [String]? @@ -1140,12 +1080,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeUsernames(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeUsernames(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionChangeWallpaper(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.WallPaper? @@ -1158,12 +1095,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeWallpaper(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeWallpaper(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionCreateTopic(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ForumTopic? @@ -1171,12 +1105,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.ForumTopic } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionCreateTopic(topic: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionCreateTopic(topic: _1!) } public static func parse_channelAdminLogEventActionDefaultBannedRights(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ChatBannedRights? @@ -1189,12 +1119,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionDefaultBannedRights(prevBannedRights: _1!, newBannedRights: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionDefaultBannedRights(prevBannedRights: _1!, newBannedRights: _2!) } public static func parse_channelAdminLogEventActionDeleteMessage(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Message? @@ -1202,12 +1129,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Message } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionDeleteMessage(message: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionDeleteMessage(message: _1!) } public static func parse_channelAdminLogEventActionDeleteTopic(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ForumTopic? @@ -1215,12 +1138,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.ForumTopic } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionDeleteTopic(topic: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionDeleteTopic(topic: _1!) } public static func parse_channelAdminLogEventActionDiscardGroupCall(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.InputGroupCall? @@ -1228,12 +1147,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionDiscardGroupCall(call: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionDiscardGroupCall(call: _1!) } public static func parse_channelAdminLogEventActionEditMessage(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Message? @@ -1246,12 +1161,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionEditMessage(prevMessage: _1!, newMessage: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionEditMessage(prevMessage: _1!, newMessage: _2!) } public static func parse_channelAdminLogEventActionEditTopic(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ForumTopic? @@ -1264,12 +1176,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionEditTopic(prevTopic: _1!, newTopic: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionEditTopic(prevTopic: _1!, newTopic: _2!) } public static func parse_channelAdminLogEventActionExportedInviteDelete(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ExportedChatInvite? @@ -1277,12 +1186,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionExportedInviteDelete(invite: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionExportedInviteDelete(invite: _1!) } public static func parse_channelAdminLogEventActionExportedInviteEdit(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ExportedChatInvite? @@ -1295,12 +1200,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionExportedInviteEdit(prevInvite: _1!, newInvite: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionExportedInviteEdit(prevInvite: _1!, newInvite: _2!) } public static func parse_channelAdminLogEventActionExportedInviteRevoke(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ExportedChatInvite? @@ -1308,12 +1210,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionExportedInviteRevoke(invite: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionExportedInviteRevoke(invite: _1!) } public static func parse_channelAdminLogEventActionParticipantInvite(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ChannelParticipant? @@ -1321,12 +1219,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantInvite(participant: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantInvite(participant: _1!) } public static func parse_channelAdminLogEventActionParticipantJoin(_ reader: BufferReader) -> ChannelAdminLogEventAction? { return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoin @@ -1340,12 +1234,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoinByInvite(flags: _1!, invite: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoinByInvite(flags: _1!, invite: _2!) } public static func parse_channelAdminLogEventActionParticipantJoinByRequest(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ExportedChatInvite? @@ -1356,12 +1247,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoinByRequest(invite: _1!, approvedBy: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoinByRequest(invite: _1!, approvedBy: _2!) } public static func parse_channelAdminLogEventActionParticipantLeave(_ reader: BufferReader) -> ChannelAdminLogEventAction? { return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantLeave @@ -1372,12 +1260,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipant } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantMute(participant: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantMute(participant: _1!) } public static func parse_channelAdminLogEventActionParticipantSubExtend(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ChannelParticipant? @@ -1390,12 +1274,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantSubExtend(prevParticipant: _1!, newParticipant: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantSubExtend(prevParticipant: _1!, newParticipant: _2!) } public static func parse_channelAdminLogEventActionParticipantToggleAdmin(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ChannelParticipant? @@ -1408,12 +1289,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantToggleAdmin(prevParticipant: _1!, newParticipant: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantToggleAdmin(prevParticipant: _1!, newParticipant: _2!) } public static func parse_channelAdminLogEventActionParticipantToggleBan(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ChannelParticipant? @@ -1426,12 +1304,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantToggleBan(prevParticipant: _1!, newParticipant: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantToggleBan(prevParticipant: _1!, newParticipant: _2!) } public static func parse_channelAdminLogEventActionParticipantUnmute(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.GroupCallParticipant? @@ -1439,12 +1314,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipant } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantUnmute(participant: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantUnmute(participant: _1!) } public static func parse_channelAdminLogEventActionParticipantVolume(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.GroupCallParticipant? @@ -1452,12 +1323,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipant } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantVolume(participant: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantVolume(participant: _1!) } public static func parse_channelAdminLogEventActionPinTopic(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Int32? @@ -1473,12 +1340,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionPinTopic(flags: _1!, prevTopic: _2, newTopic: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionPinTopic(flags: _1!, prevTopic: _2, newTopic: _3) } public static func parse_channelAdminLogEventActionSendMessage(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Message? @@ -1486,12 +1351,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Message } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionSendMessage(message: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionSendMessage(message: _1!) } public static func parse_channelAdminLogEventActionStartGroupCall(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.InputGroupCall? @@ -1499,12 +1360,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionStartGroupCall(call: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionStartGroupCall(call: _1!) } public static func parse_channelAdminLogEventActionStopPoll(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Message? @@ -1512,12 +1369,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Message } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionStopPoll(message: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionStopPoll(message: _1!) } public static func parse_channelAdminLogEventActionToggleAntiSpam(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Bool? @@ -1525,12 +1378,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Bool } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleAntiSpam(newValue: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleAntiSpam(newValue: _1!) } public static func parse_channelAdminLogEventActionToggleAutotranslation(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Bool? @@ -1538,12 +1387,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Bool } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleAutotranslation(newValue: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleAutotranslation(newValue: _1!) } public static func parse_channelAdminLogEventActionToggleForum(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Bool? @@ -1551,12 +1396,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Bool } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleForum(newValue: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleForum(newValue: _1!) } public static func parse_channelAdminLogEventActionToggleGroupCallSetting(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Bool? @@ -1564,12 +1405,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Bool } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleGroupCallSetting(joinMuted: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleGroupCallSetting(joinMuted: _1!) } public static func parse_channelAdminLogEventActionToggleInvites(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Bool? @@ -1577,12 +1414,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Bool } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleInvites(newValue: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleInvites(newValue: _1!) } public static func parse_channelAdminLogEventActionToggleNoForwards(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Bool? @@ -1590,12 +1423,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Bool } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleNoForwards(newValue: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleNoForwards(newValue: _1!) } public static func parse_channelAdminLogEventActionTogglePreHistoryHidden(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Bool? @@ -1603,12 +1432,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Bool } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionTogglePreHistoryHidden(newValue: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionTogglePreHistoryHidden(newValue: _1!) } public static func parse_channelAdminLogEventActionToggleSignatureProfiles(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Bool? @@ -1616,12 +1441,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Bool } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleSignatureProfiles(newValue: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleSignatureProfiles(newValue: _1!) } public static func parse_channelAdminLogEventActionToggleSignatures(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Bool? @@ -1629,12 +1450,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Bool } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleSignatures(newValue: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleSignatures(newValue: _1!) } public static func parse_channelAdminLogEventActionToggleSlowMode(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Int32? @@ -1643,12 +1460,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleSlowMode(prevValue: _1!, newValue: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleSlowMode(prevValue: _1!, newValue: _2!) } public static func parse_channelAdminLogEventActionUpdatePinned(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Message? @@ -1656,12 +1470,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Message } let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionUpdatePinned(message: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionUpdatePinned(message: _1!) } } diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index 0d25527d..b10d58f7 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -39,12 +39,8 @@ public extension Api.account { _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil - if _c1 { - return Api.account.SavedMusicIds.savedMusicIds(ids: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.account.SavedMusicIds.savedMusicIds(ids: _1!) } public static func parse_savedMusicIdsNotModified(_ reader: BufferReader) -> SavedMusicIds? { return Api.account.SavedMusicIds.savedMusicIdsNotModified @@ -92,12 +88,8 @@ public extension Api.account { _1 = Api.parse(reader, signature: signature) as? Api.Document } let _c1 = _1 != nil - if _c1 { - return Api.account.SavedRingtone.savedRingtoneConverted(document: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.account.SavedRingtone.savedRingtoneConverted(document: _1!) } } @@ -147,12 +139,9 @@ public extension Api.account { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.SavedRingtones.savedRingtones(hash: _1!, ringtones: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.account.SavedRingtones.savedRingtones(hash: _1!, ringtones: _2!) } public static func parse_savedRingtonesNotModified(_ reader: BufferReader) -> SavedRingtones? { return Api.account.SavedRingtones.savedRingtonesNotModified @@ -190,12 +179,9 @@ public extension Api.account { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.SentEmailCode.sentEmailCode(emailPattern: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.account.SentEmailCode.sentEmailCode(emailPattern: _1!, length: _2!) } } @@ -226,12 +212,8 @@ public extension Api.account { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.account.Takeout.takeout(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.account.Takeout.takeout(id: _1!) } } @@ -281,12 +263,9 @@ public extension Api.account { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.Themes.themes(hash: _1!, themes: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.account.Themes.themes(hash: _1!, themes: _2!) } public static func parse_themesNotModified(_ reader: BufferReader) -> Themes? { return Api.account.Themes.themesNotModified @@ -324,12 +303,9 @@ public extension Api.account { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.TmpPassword.tmpPassword(tmpPassword: _1!, validUntil: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.account.TmpPassword.tmpPassword(tmpPassword: _1!, validUntil: _2!) } } @@ -379,12 +355,9 @@ public extension Api.account { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.WallPapers.wallPapers(hash: _1!, wallpapers: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.account.WallPapers.wallPapers(hash: _1!, wallpapers: _2!) } public static func parse_wallPapersNotModified(_ reader: BufferReader) -> WallPapers? { return Api.account.WallPapers.wallPapersNotModified @@ -434,12 +407,9 @@ public extension Api.account { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.WebAuthorizations.webAuthorizations(authorizations: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.account.WebAuthorizations.webAuthorizations(authorizations: _1!, users: _2!) } } @@ -498,12 +468,12 @@ public extension Api.auth { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.Authorization.authorization(flags: _1!, otherwiseReloginDays: _2, tmpSessions: _3, futureAuthToken: _4, user: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.auth.Authorization.authorization(flags: _1!, otherwiseReloginDays: _2, tmpSessions: _3, futureAuthToken: _4, user: _5!) } public static func parse_authorizationSignUpRequired(_ reader: BufferReader) -> Authorization? { var _1: Int32? @@ -514,12 +484,9 @@ public extension Api.auth { } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.Authorization.authorizationSignUpRequired(flags: _1!, termsOfService: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.auth.Authorization.authorizationSignUpRequired(flags: _1!, termsOfService: _2) } } @@ -630,12 +597,9 @@ public extension Api.auth { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.ExportedAuthorization.exportedAuthorization(id: _1!, bytes: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.auth.ExportedAuthorization.exportedAuthorization(id: _1!, bytes: _2!) } } @@ -670,12 +634,9 @@ public extension Api.auth { if Int(_1!) & Int(1 << 0) != 0 {_2 = parseBytes(reader) } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.LoggedOut.loggedOut(flags: _1!, futureAuthToken: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.auth.LoggedOut.loggedOut(flags: _1!, futureAuthToken: _2) } } @@ -729,12 +690,9 @@ public extension Api.auth { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.LoginToken.loginToken(expires: _1!, token: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.auth.LoginToken.loginToken(expires: _1!, token: _2!) } public static func parse_loginTokenMigrateTo(_ reader: BufferReader) -> LoginToken? { var _1: Int32? @@ -743,12 +701,9 @@ public extension Api.auth { _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.LoginToken.loginTokenMigrateTo(dcId: _1!, token: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.auth.LoginToken.loginTokenMigrateTo(dcId: _1!, token: _2!) } public static func parse_loginTokenSuccess(_ reader: BufferReader) -> LoginToken? { var _1: Api.auth.Authorization? @@ -756,12 +711,8 @@ public extension Api.auth { _1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization } let _c1 = _1 != nil - if _c1 { - return Api.auth.LoginToken.loginTokenSuccess(authorization: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.auth.LoginToken.loginTokenSuccess(authorization: _1!) } } @@ -794,12 +745,8 @@ public extension Api.auth { _1 = Api.parse(reader, signature: signature) as? Api.DataJSON } let _c1 = _1 != nil - if _c1 { - return Api.auth.PasskeyLoginOptions.passkeyLoginOptions(options: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.auth.PasskeyLoginOptions.passkeyLoginOptions(options: _1!) } } @@ -830,12 +777,8 @@ public extension Api.auth { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.auth.PasswordRecovery.passwordRecovery(emailPattern: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.auth.PasswordRecovery.passwordRecovery(emailPattern: _1!) } } @@ -909,12 +852,12 @@ public extension Api.auth { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.SentCode.sentCode(flags: _1!, type: _2!, phoneCodeHash: _3!, nextType: _4, timeout: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.auth.SentCode.sentCode(flags: _1!, type: _2!, phoneCodeHash: _3!, nextType: _4, timeout: _5) } public static func parse_sentCodePaymentRequired(_ reader: BufferReader) -> SentCode? { var _1: String? @@ -935,12 +878,13 @@ public extension Api.auth { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.auth.SentCode.sentCodePaymentRequired(storeProduct: _1!, phoneCodeHash: _2!, supportEmailAddress: _3!, supportEmailSubject: _4!, currency: _5!, amount: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.auth.SentCode.sentCodePaymentRequired(storeProduct: _1!, phoneCodeHash: _2!, supportEmailAddress: _3!, supportEmailSubject: _4!, currency: _5!, amount: _6!) } public static func parse_sentCodeSuccess(_ reader: BufferReader) -> SentCode? { var _1: Api.auth.Authorization? @@ -948,12 +892,8 @@ public extension Api.auth { _1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization } let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCode.sentCodeSuccess(authorization: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.auth.SentCode.sentCodeSuccess(authorization: _1!) } } @@ -1088,23 +1028,15 @@ public extension Api.auth { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeApp(length: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.auth.SentCodeType.sentCodeTypeApp(length: _1!) } public static func parse_sentCodeTypeCall(_ reader: BufferReader) -> SentCodeType? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeCall(length: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.auth.SentCodeType.sentCodeTypeCall(length: _1!) } public static func parse_sentCodeTypeEmailCode(_ reader: BufferReader) -> SentCodeType? { var _1: Int32? @@ -1122,12 +1054,12 @@ public extension Api.auth { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.SentCodeType.sentCodeTypeEmailCode(flags: _1!, emailPattern: _2!, length: _3!, resetAvailablePeriod: _4, resetPendingDate: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.auth.SentCodeType.sentCodeTypeEmailCode(flags: _1!, emailPattern: _2!, length: _3!, resetAvailablePeriod: _4, resetPendingDate: _5) } public static func parse_sentCodeTypeFirebaseSms(_ reader: BufferReader) -> SentCodeType? { var _1: Int32? @@ -1151,23 +1083,21 @@ public extension Api.auth { let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, playIntegrityProjectId: _3, playIntegrityNonce: _4, receipt: _5, pushTimeout: _6, length: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, playIntegrityProjectId: _3, playIntegrityNonce: _4, receipt: _5, pushTimeout: _6, length: _7!) } public static func parse_sentCodeTypeFlashCall(_ reader: BufferReader) -> SentCodeType? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeFlashCall(pattern: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.auth.SentCodeType.sentCodeTypeFlashCall(pattern: _1!) } public static func parse_sentCodeTypeFragmentSms(_ reader: BufferReader) -> SentCodeType? { var _1: String? @@ -1176,12 +1106,9 @@ public extension Api.auth { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeFragmentSms(url: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.auth.SentCodeType.sentCodeTypeFragmentSms(url: _1!, length: _2!) } public static func parse_sentCodeTypeMissedCall(_ reader: BufferReader) -> SentCodeType? { var _1: String? @@ -1190,34 +1117,23 @@ public extension Api.auth { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeMissedCall(prefix: _1!, length: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.auth.SentCodeType.sentCodeTypeMissedCall(prefix: _1!, length: _2!) } public static func parse_sentCodeTypeSetUpEmailRequired(_ reader: BufferReader) -> SentCodeType? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeSetUpEmailRequired(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.auth.SentCodeType.sentCodeTypeSetUpEmailRequired(flags: _1!) } public static func parse_sentCodeTypeSms(_ reader: BufferReader) -> SentCodeType? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeSms(length: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.auth.SentCodeType.sentCodeTypeSms(length: _1!) } public static func parse_sentCodeTypeSmsPhrase(_ reader: BufferReader) -> SentCodeType? { var _1: Int32? @@ -1226,12 +1142,9 @@ public extension Api.auth { if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeSmsPhrase(flags: _1!, beginning: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.auth.SentCodeType.sentCodeTypeSmsPhrase(flags: _1!, beginning: _2) } public static func parse_sentCodeTypeSmsWord(_ reader: BufferReader) -> SentCodeType? { var _1: Int32? @@ -1240,12 +1153,9 @@ public extension Api.auth { if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeSmsWord(flags: _1!, beginning: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.auth.SentCodeType.sentCodeTypeSmsWord(flags: _1!, beginning: _2) } } diff --git a/submodules/TelegramApi/Sources/Api31.swift b/submodules/TelegramApi/Sources/Api31.swift index b35e3904..d713950b 100644 --- a/submodules/TelegramApi/Sources/Api31.swift +++ b/submodules/TelegramApi/Sources/Api31.swift @@ -32,12 +32,10 @@ public extension Api.bots { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.bots.BotInfo.botInfo(name: _1!, about: _2!, description: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.bots.BotInfo.botInfo(name: _1!, about: _2!, description: _3!) } } @@ -82,12 +80,10 @@ public extension Api.bots { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.bots.PopularAppBots.popularAppBots(flags: _1!, nextOffset: _2, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.bots.PopularAppBots.popularAppBots(flags: _1!, nextOffset: _2, users: _3!) } } @@ -134,12 +130,9 @@ public extension Api.bots { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.bots.PreviewInfo.previewInfo(media: _1!, langCodes: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.bots.PreviewInfo.previewInfo(media: _1!, langCodes: _2!) } } @@ -196,12 +189,10 @@ public extension Api.channels { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.channels.AdminLogResults.adminLogResults(events: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.channels.AdminLogResults.adminLogResults(events: _1!, chats: _2!, users: _3!) } } @@ -254,12 +245,10 @@ public extension Api.channels { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.channels.ChannelParticipant.channelParticipant(participant: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.channels.ChannelParticipant.channelParticipant(participant: _1!, chats: _2!, users: _3!) } } @@ -329,12 +318,11 @@ public extension Api.channels { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.channels.ChannelParticipants.channelParticipants(count: _1!, participants: _2!, chats: _3!, users: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.channels.ChannelParticipants.channelParticipants(count: _1!, participants: _2!, chats: _3!, users: _4!) } public static func parse_channelParticipantsNotModified(_ reader: BufferReader) -> ChannelParticipants? { return Api.channels.ChannelParticipants.channelParticipantsNotModified @@ -394,12 +382,10 @@ public extension Api.channels { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.channels.SendAsPeers.sendAsPeers(peers: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.channels.SendAsPeers.sendAsPeers(peers: _1!, chats: _2!, users: _3!) } } @@ -461,12 +447,9 @@ public extension Api.channels { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultChooseOption(title: _1!, options: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultChooseOption(title: _1!, options: _2!) } public static func parse_sponsoredMessageReportResultReported(_ reader: BufferReader) -> SponsoredMessageReportResult? { return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultReported @@ -569,12 +552,13 @@ public extension Api.chatlists { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.chatlists.ChatlistInvite.chatlistInvite(flags: _1!, title: _2!, emoticon: _3, peers: _4!, chats: _5!, users: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.chatlists.ChatlistInvite.chatlistInvite(flags: _1!, title: _2!, emoticon: _3, peers: _4!, chats: _5!, users: _6!) } public static func parse_chatlistInviteAlready(_ reader: BufferReader) -> ChatlistInvite? { var _1: Int32? @@ -600,12 +584,12 @@ public extension Api.chatlists { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.chatlists.ChatlistInvite.chatlistInviteAlready(filterId: _1!, missingPeers: _2!, alreadyPeers: _3!, chats: _4!, users: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.chatlists.ChatlistInvite.chatlistInviteAlready(filterId: _1!, missingPeers: _2!, alreadyPeers: _3!, chats: _4!, users: _5!) } } @@ -662,12 +646,10 @@ public extension Api.chatlists { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.chatlists.ChatlistUpdates.chatlistUpdates(missingPeers: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.chatlists.ChatlistUpdates.chatlistUpdates(missingPeers: _1!, chats: _2!, users: _3!) } } @@ -706,12 +688,9 @@ public extension Api.chatlists { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.chatlists.ExportedChatlistInvite.exportedChatlistInvite(filter: _1!, invite: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.chatlists.ExportedChatlistInvite.exportedChatlistInvite(filter: _1!, invite: _2!) } } @@ -768,12 +747,10 @@ public extension Api.chatlists { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.chatlists.ExportedInvites.exportedInvites(invites: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.chatlists.ExportedInvites.exportedInvites(invites: _1!, chats: _2!, users: _3!) } } @@ -854,12 +831,10 @@ public extension Api.contacts { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.Blocked.blocked(blocked: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.contacts.Blocked.blocked(blocked: _1!, chats: _2!, users: _3!) } public static func parse_blockedSlice(_ reader: BufferReader) -> Blocked? { var _1: Int32? @@ -880,12 +855,11 @@ public extension Api.contacts { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.contacts.Blocked.blockedSlice(count: _1!, blocked: _2!, chats: _3!, users: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.contacts.Blocked.blockedSlice(count: _1!, blocked: _2!, chats: _3!, users: _4!) } } @@ -932,12 +906,9 @@ public extension Api.contacts { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.contacts.ContactBirthdays.contactBirthdays(contacts: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.contacts.ContactBirthdays.contactBirthdays(contacts: _1!, users: _2!) } } @@ -997,12 +968,10 @@ public extension Api.contacts { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.Contacts.contacts(contacts: _1!, savedCount: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.contacts.Contacts.contacts(contacts: _1!, savedCount: _2!, users: _3!) } public static func parse_contactsNotModified(_ reader: BufferReader) -> Contacts? { return Api.contacts.Contacts.contactsNotModified @@ -1072,12 +1041,11 @@ public extension Api.contacts { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.contacts.Found.found(myResults: _1!, results: _2!, chats: _3!, users: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.contacts.Found.found(myResults: _1!, results: _2!, chats: _3!, users: _4!) } } @@ -1144,12 +1112,11 @@ public extension Api.contacts { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.contacts.ImportedContacts.importedContacts(imported: _1!, popularInvites: _2!, retryContacts: _3!, users: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.contacts.ImportedContacts.importedContacts(imported: _1!, popularInvites: _2!, retryContacts: _3!, users: _4!) } } @@ -1202,12 +1169,10 @@ public extension Api.contacts { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.ResolvedPeer.resolvedPeer(peer: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.contacts.ResolvedPeer.resolvedPeer(peer: _1!, chats: _2!, users: _3!) } } @@ -1273,12 +1238,10 @@ public extension Api.contacts { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.SponsoredPeers.sponsoredPeers(peers: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.contacts.SponsoredPeers.sponsoredPeers(peers: _1!, chats: _2!, users: _3!) } public static func parse_sponsoredPeersEmpty(_ reader: BufferReader) -> SponsoredPeers? { return Api.contacts.SponsoredPeers.sponsoredPeersEmpty @@ -1356,12 +1319,10 @@ public extension Api.contacts { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.TopPeers.topPeers(categories: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.contacts.TopPeers.topPeers(categories: _1!, chats: _2!, users: _3!) } public static func parse_topPeersDisabled(_ reader: BufferReader) -> TopPeers? { return Api.contacts.TopPeers.topPeersDisabled @@ -1418,12 +1379,13 @@ public extension Api.fragment { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.fragment.CollectibleInfo.collectibleInfo(purchaseDate: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, url: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.fragment.CollectibleInfo.collectibleInfo(purchaseDate: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, url: _6!) } } @@ -1469,12 +1431,9 @@ public extension Api.help { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.AppConfig.appConfig(hash: _1!, config: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.help.AppConfig.appConfig(hash: _1!, config: _2!) } public static func parse_appConfigNotModified(_ reader: BufferReader) -> AppConfig? { return Api.help.AppConfig.appConfigNotModified diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index a9067b18..5649eb9f 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -71,12 +71,15 @@ public extension Api.help { let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.help.AppUpdate.appUpdate(flags: _1!, id: _2!, version: _3!, text: _4!, entities: _5!, document: _6, url: _7, sticker: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.help.AppUpdate.appUpdate(flags: _1!, id: _2!, version: _3!, text: _4!, entities: _5!, document: _6, url: _7, sticker: _8) } public static func parse_noAppUpdate(_ reader: BufferReader) -> AppUpdate? { return Api.help.AppUpdate.noAppUpdate @@ -129,12 +132,9 @@ public extension Api.help { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.CountriesList.countriesList(countries: _1!, hash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.help.CountriesList.countriesList(countries: _1!, hash: _2!) } public static func parse_countriesListNotModified(_ reader: BufferReader) -> CountriesList? { return Api.help.CountriesList.countriesListNotModified @@ -190,12 +190,12 @@ public extension Api.help { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.help.Country.country(flags: _1!, iso2: _2!, defaultName: _3!, name: _4, countryCodes: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.help.Country.country(flags: _1!, iso2: _2!, defaultName: _3!, name: _4, countryCodes: _5!) } } @@ -250,12 +250,11 @@ public extension Api.help { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.help.CountryCode.countryCode(flags: _1!, countryCode: _2!, prefixes: _3, patterns: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.help.CountryCode.countryCode(flags: _1!, countryCode: _2!, prefixes: _3, patterns: _4) } } @@ -309,12 +308,10 @@ public extension Api.help { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.help.DeepLinkInfo.deepLinkInfo(flags: _1!, message: _2!, entities: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.help.DeepLinkInfo.deepLinkInfo(flags: _1!, message: _2!, entities: _3) } public static func parse_deepLinkInfoEmpty(_ reader: BufferReader) -> DeepLinkInfo? { return Api.help.DeepLinkInfo.deepLinkInfoEmpty @@ -348,12 +345,8 @@ public extension Api.help { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.help.InviteText.inviteText(message: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.help.InviteText.inviteText(message: _1!) } } @@ -399,12 +392,9 @@ public extension Api.help { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.PassportConfig.passportConfig(hash: _1!, countriesLangs: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.help.PassportConfig.passportConfig(hash: _1!, countriesLangs: _2!) } public static func parse_passportConfigNotModified(_ reader: BufferReader) -> PassportConfig? { return Api.help.PassportConfig.passportConfigNotModified @@ -462,12 +452,13 @@ public extension Api.help { let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.help.PeerColorOption.peerColorOption(flags: _1!, colorId: _2!, colors: _3, darkColors: _4, channelMinLevel: _5, groupMinLevel: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.help.PeerColorOption.peerColorOption(flags: _1!, colorId: _2!, colors: _3, darkColors: _4, channelMinLevel: _5, groupMinLevel: _6) } } @@ -537,12 +528,10 @@ public extension Api.help { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.help.PeerColorSet.peerColorProfileSet(paletteColors: _1!, bgColors: _2!, storyColors: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.help.PeerColorSet.peerColorProfileSet(paletteColors: _1!, bgColors: _2!, storyColors: _3!) } public static func parse_peerColorSet(_ reader: BufferReader) -> PeerColorSet? { var _1: [Int32]? @@ -550,12 +539,8 @@ public extension Api.help { _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil - if _c1 { - return Api.help.PeerColorSet.peerColorSet(colors: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.help.PeerColorSet.peerColorSet(colors: _1!) } } @@ -605,12 +590,9 @@ public extension Api.help { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.PeerColors.peerColors(hash: _1!, colors: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.help.PeerColors.peerColors(hash: _1!, colors: _2!) } public static func parse_peerColorsNotModified(_ reader: BufferReader) -> PeerColors? { return Api.help.PeerColors.peerColorsNotModified @@ -694,12 +676,13 @@ public extension Api.help { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!) } } @@ -803,23 +786,24 @@ public extension Api.help { let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil let _c9 = _9 != nil let _c10 = _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.help.PromoData.promoData(flags: _1!, expires: _2!, peer: _3, psaType: _4, psaMessage: _5, pendingSuggestions: _6!, dismissedSuggestions: _7!, customPendingSuggestion: _8, chats: _9!, users: _10!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + return Api.help.PromoData.promoData(flags: _1!, expires: _2!, peer: _3, psaType: _4, psaMessage: _5, pendingSuggestions: _6!, dismissedSuggestions: _7!, customPendingSuggestion: _8, chats: _9!, users: _10!) } public static func parse_promoDataEmpty(_ reader: BufferReader) -> PromoData? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.help.PromoData.promoDataEmpty(expires: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.help.PromoData.promoDataEmpty(expires: _1!) } } @@ -876,12 +860,10 @@ public extension Api.help { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.help.RecentMeUrls.recentMeUrls(urls: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.help.RecentMeUrls.recentMeUrls(urls: _1!, chats: _2!, users: _3!) } } @@ -918,12 +900,9 @@ public extension Api.help { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.Support.support(phoneNumber: _1!, user: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.help.Support.support(phoneNumber: _1!, user: _2!) } } @@ -954,12 +933,8 @@ public extension Api.help { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.help.SupportName.supportName(name: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.help.SupportName.supportName(name: _1!) } } @@ -1014,12 +989,12 @@ public extension Api.help { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.help.TermsOfService.termsOfService(flags: _1!, id: _2!, text: _3!, entities: _4!, minAgeConfirm: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.help.TermsOfService.termsOfService(flags: _1!, id: _2!, text: _3!, entities: _4!, minAgeConfirm: _5) } } @@ -1065,23 +1040,16 @@ public extension Api.help { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.TermsOfServiceUpdate.termsOfServiceUpdate(expires: _1!, termsOfService: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.help.TermsOfServiceUpdate.termsOfServiceUpdate(expires: _1!, termsOfService: _2!) } public static func parse_termsOfServiceUpdateEmpty(_ reader: BufferReader) -> TermsOfServiceUpdate? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.help.TermsOfServiceUpdate.termsOfServiceUpdateEmpty(expires: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.help.TermsOfServiceUpdate.termsOfServiceUpdateEmpty(expires: _1!) } } @@ -1131,12 +1099,9 @@ public extension Api.help { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.TimezonesList.timezonesList(timezones: _1!, hash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.help.TimezonesList.timezonesList(timezones: _1!, hash: _2!) } public static func parse_timezonesListNotModified(_ reader: BufferReader) -> TimezonesList? { return Api.help.TimezonesList.timezonesListNotModified @@ -1197,12 +1162,11 @@ public extension Api.help { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.help.UserInfo.userInfo(message: _1!, entities: _2!, author: _3!, date: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.help.UserInfo.userInfo(message: _1!, entities: _2!, author: _3!, date: _4!) } public static func parse_userInfoEmpty(_ reader: BufferReader) -> UserInfo? { return Api.help.UserInfo.userInfoEmpty @@ -1254,12 +1218,11 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.AffectedFoundMessages.affectedFoundMessages(pts: _1!, ptsCount: _2!, offset: _3!, messages: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.messages.AffectedFoundMessages.affectedFoundMessages(pts: _1!, ptsCount: _2!, offset: _3!, messages: _4!) } } @@ -1298,12 +1261,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.AffectedHistory.affectedHistory(pts: _1!, ptsCount: _2!, offset: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.AffectedHistory.affectedHistory(pts: _1!, ptsCount: _2!, offset: _3!) } } @@ -1338,12 +1299,9 @@ public extension Api.messages { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.AffectedMessages.affectedMessages(pts: _1!, ptsCount: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.AffectedMessages.affectedMessages(pts: _1!, ptsCount: _2!) } } diff --git a/submodules/TelegramApi/Sources/Api33.swift b/submodules/TelegramApi/Sources/Api33.swift index 1341629f..98c550df 100644 --- a/submodules/TelegramApi/Sources/Api33.swift +++ b/submodules/TelegramApi/Sources/Api33.swift @@ -43,12 +43,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.AllStickers.allStickers(hash: _1!, sets: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.AllStickers.allStickers(hash: _1!, sets: _2!) } public static func parse_allStickersNotModified(_ reader: BufferReader) -> AllStickers? { return Api.messages.AllStickers.allStickersNotModified @@ -92,12 +89,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.ArchivedStickers.archivedStickers(count: _1!, sets: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.ArchivedStickers.archivedStickers(count: _1!, sets: _2!) } } @@ -157,12 +151,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.AvailableEffects.availableEffects(hash: _1!, effects: _2!, documents: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.AvailableEffects.availableEffects(hash: _1!, effects: _2!, documents: _3!) } public static func parse_availableEffectsNotModified(_ reader: BufferReader) -> AvailableEffects? { return Api.messages.AvailableEffects.availableEffectsNotModified @@ -215,12 +207,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.AvailableReactions.availableReactions(hash: _1!, reactions: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.AvailableReactions.availableReactions(hash: _1!, reactions: _2!) } public static func parse_availableReactionsNotModified(_ reader: BufferReader) -> AvailableReactions? { return Api.messages.AvailableReactions.availableReactionsNotModified @@ -260,12 +249,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.BotApp.botApp(flags: _1!, app: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.BotApp.botApp(flags: _1!, app: _2!) } } @@ -308,12 +294,11 @@ public extension Api.messages { let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.BotCallbackAnswer.botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.messages.BotCallbackAnswer.botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!) } } @@ -348,12 +333,9 @@ public extension Api.messages { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.BotPreparedInlineMessage.botPreparedInlineMessage(id: _1!, expireDate: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.BotPreparedInlineMessage.botPreparedInlineMessage(id: _1!, expireDate: _2!) } } @@ -428,12 +410,15 @@ public extension Api.messages { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.messages.BotResults.botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, switchWebview: _5, results: _6!, cacheTime: _7!, users: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.messages.BotResults.botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, switchWebview: _5, results: _6!, cacheTime: _7!, users: _8!) } } @@ -480,12 +465,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.ChatAdminsWithInvites.chatAdminsWithInvites(admins: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.ChatAdminsWithInvites.chatAdminsWithInvites(admins: _1!, users: _2!) } } @@ -538,12 +520,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ChatFull.chatFull(fullChat: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.ChatFull.chatFull(fullChat: _1!, chats: _2!, users: _3!) } } @@ -594,12 +574,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ChatInviteImporters.chatInviteImporters(count: _1!, importers: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.ChatInviteImporters.chatInviteImporters(count: _1!, importers: _2!, users: _3!) } } @@ -650,12 +628,8 @@ public extension Api.messages { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } let _c1 = _1 != nil - if _c1 { - return Api.messages.Chats.chats(chats: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.Chats.chats(chats: _1!) } public static func parse_chatsSlice(_ reader: BufferReader) -> Chats? { var _1: Int32? @@ -666,12 +640,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.Chats.chatsSlice(count: _1!, chats: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.Chats.chatsSlice(count: _1!, chats: _2!) } } @@ -702,12 +673,8 @@ public extension Api.messages { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.messages.CheckedHistoryImportPeer.checkedHistoryImportPeer(confirmText: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.CheckedHistoryImportPeer.checkedHistoryImportPeer(confirmText: _1!) } } @@ -759,23 +726,18 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.DhConfig.dhConfig(g: _1!, p: _2!, version: _3!, random: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.messages.DhConfig.dhConfig(g: _1!, p: _2!, version: _3!, random: _4!) } public static func parse_dhConfigNotModified(_ reader: BufferReader) -> DhConfig? { var _1: Buffer? _1 = parseBytes(reader) let _c1 = _1 != nil - if _c1 { - return Api.messages.DhConfig.dhConfigNotModified(random: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.DhConfig.dhConfigNotModified(random: _1!) } } @@ -816,12 +778,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.DialogFilters.dialogFilters(flags: _1!, filters: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.DialogFilters.dialogFilters(flags: _1!, filters: _2!) } } @@ -926,23 +885,18 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.Dialogs.dialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.messages.Dialogs.dialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!) } public static func parse_dialogsNotModified(_ reader: BufferReader) -> Dialogs? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.messages.Dialogs.dialogsNotModified(count: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.Dialogs.dialogsNotModified(count: _1!) } public static func parse_dialogsSlice(_ reader: BufferReader) -> Dialogs? { var _1: Int32? @@ -968,12 +922,12 @@ public extension Api.messages { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.Dialogs.dialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.messages.Dialogs.dialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!) } } @@ -1050,12 +1004,132 @@ public extension Api.messages { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.messages.DiscussionMessage.discussionMessage(flags: _1!, messages: _2!, maxId: _3, readInboxMaxId: _4, readOutboxMaxId: _5, unreadCount: _6!, chats: _7!, users: _8!) - } - else { - return nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.messages.DiscussionMessage.discussionMessage(flags: _1!, messages: _2!, maxId: _3, readInboxMaxId: _4, readOutboxMaxId: _5, unreadCount: _6!, chats: _7!, users: _8!) + } + + } +} +public extension Api.messages { + enum EmojiGameInfo: TypeConstructorDescription { + case emojiGameDiceInfo(flags: Int32, gameHash: String, prevStake: Int64, currentStreak: Int32, params: [Int32], playsLeft: Int32?) + case emojiGameUnavailable + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .emojiGameDiceInfo(let flags, let gameHash, let prevStake, let currentStreak, let params, let playsLeft): + if boxed { + buffer.appendInt32(1155883043) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(gameHash, buffer: buffer, boxed: false) + serializeInt64(prevStake, buffer: buffer, boxed: false) + serializeInt32(currentStreak, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(params.count)) + for item in params { + serializeInt32(item, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(playsLeft!, buffer: buffer, boxed: false)} + break + case .emojiGameUnavailable: + if boxed { + buffer.appendInt32(1508266805) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .emojiGameDiceInfo(let flags, let gameHash, let prevStake, let currentStreak, let params, let playsLeft): + return ("emojiGameDiceInfo", [("flags", flags as Any), ("gameHash", gameHash as Any), ("prevStake", prevStake as Any), ("currentStreak", currentStreak as Any), ("params", params as Any), ("playsLeft", playsLeft as Any)]) + case .emojiGameUnavailable: + return ("emojiGameUnavailable", []) + } + } + + public static func parse_emojiGameDiceInfo(_ reader: BufferReader) -> EmojiGameInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: [Int32]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } + var _6: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.messages.EmojiGameInfo.emojiGameDiceInfo(flags: _1!, gameHash: _2!, prevStake: _3!, currentStreak: _4!, params: _5!, playsLeft: _6) + } + public static func parse_emojiGameUnavailable(_ reader: BufferReader) -> EmojiGameInfo? { + return Api.messages.EmojiGameInfo.emojiGameUnavailable + } + + } +} +public extension Api.messages { + enum EmojiGameOutcome: TypeConstructorDescription { + case emojiGameOutcome(seed: Buffer, stakeTonAmount: Int64, tonAmount: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .emojiGameOutcome(let seed, let stakeTonAmount, let tonAmount): + if boxed { + buffer.appendInt32(-634726841) + } + serializeBytes(seed, buffer: buffer, boxed: false) + serializeInt64(stakeTonAmount, buffer: buffer, boxed: false) + serializeInt64(tonAmount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .emojiGameOutcome(let seed, let stakeTonAmount, let tonAmount): + return ("emojiGameOutcome", [("seed", seed as Any), ("stakeTonAmount", stakeTonAmount as Any), ("tonAmount", tonAmount as Any)]) + } + } + + public static func parse_emojiGameOutcome(_ reader: BufferReader) -> EmojiGameOutcome? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.EmojiGameOutcome.emojiGameOutcome(seed: _1!, stakeTonAmount: _2!, tonAmount: _3!) } } @@ -1105,12 +1179,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.EmojiGroups.emojiGroups(hash: _1!, groups: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.EmojiGroups.emojiGroups(hash: _1!, groups: _2!) } public static func parse_emojiGroupsNotModified(_ reader: BufferReader) -> EmojiGroups? { return Api.messages.EmojiGroups.emojiGroupsNotModified @@ -1171,12 +1242,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.ExportedChatInvite.exportedChatInvite(invite: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.ExportedChatInvite.exportedChatInvite(invite: _1!, users: _2!) } public static func parse_exportedChatInviteReplaced(_ reader: BufferReader) -> ExportedChatInvite? { var _1: Api.ExportedChatInvite? @@ -1194,12 +1262,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ExportedChatInvite.exportedChatInviteReplaced(invite: _1!, newInvite: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.ExportedChatInvite.exportedChatInviteReplaced(invite: _1!, newInvite: _2!, users: _3!) } } @@ -1250,164 +1316,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ExportedChatInvites.exportedChatInvites(count: _1!, invites: _2!, users: _3!) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum FavedStickers: TypeConstructorDescription { - case favedStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document]) - case favedStickersNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .favedStickers(let hash, let packs, let stickers): - if boxed { - buffer.appendInt32(750063767) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(packs.count)) - for item in packs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers.count)) - for item in stickers { - item.serialize(buffer, true) - } - break - case .favedStickersNotModified: - if boxed { - buffer.appendInt32(-1634752813) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .favedStickers(let hash, let packs, let stickers): - return ("favedStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any)]) - case .favedStickersNotModified: - return ("favedStickersNotModified", []) - } - } - - public static func parse_favedStickers(_ reader: BufferReader) -> FavedStickers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StickerPack]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) - } - var _3: [Api.Document]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.FavedStickers.favedStickers(hash: _1!, packs: _2!, stickers: _3!) - } - else { - return nil - } - } - public static func parse_favedStickersNotModified(_ reader: BufferReader) -> FavedStickers? { - return Api.messages.FavedStickers.favedStickersNotModified - } - - } -} -public extension Api.messages { - enum FeaturedStickers: TypeConstructorDescription { - case featuredStickers(flags: Int32, hash: Int64, count: Int32, sets: [Api.StickerSetCovered], unread: [Int64]) - case featuredStickersNotModified(count: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .featuredStickers(let flags, let hash, let count, let sets, let unread): - if boxed { - buffer.appendInt32(-1103615738) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sets.count)) - for item in sets { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(unread.count)) - for item in unread { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .featuredStickersNotModified(let count): - if boxed { - buffer.appendInt32(-958657434) - } - serializeInt32(count, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .featuredStickers(let flags, let hash, let count, let sets, let unread): - return ("featuredStickers", [("flags", flags as Any), ("hash", hash as Any), ("count", count as Any), ("sets", sets as Any), ("unread", unread as Any)]) - case .featuredStickersNotModified(let count): - return ("featuredStickersNotModified", [("count", count as Any)]) - } - } - - public static func parse_featuredStickers(_ reader: BufferReader) -> FeaturedStickers? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } - var _5: [Int64]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.FeaturedStickers.featuredStickers(flags: _1!, hash: _2!, count: _3!, sets: _4!, unread: _5!) - } - else { - return nil - } - } - public static func parse_featuredStickersNotModified(_ reader: BufferReader) -> FeaturedStickers? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.messages.FeaturedStickers.featuredStickersNotModified(count: _1!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.ExportedChatInvites.exportedChatInvites(count: _1!, invites: _2!, users: _3!) } } diff --git a/submodules/TelegramApi/Sources/Api34.swift b/submodules/TelegramApi/Sources/Api34.swift index a0c446bb..23900f23 100644 --- a/submodules/TelegramApi/Sources/Api34.swift +++ b/submodules/TelegramApi/Sources/Api34.swift @@ -1,3 +1,149 @@ +public extension Api.messages { + enum FavedStickers: TypeConstructorDescription { + case favedStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document]) + case favedStickersNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .favedStickers(let hash, let packs, let stickers): + if boxed { + buffer.appendInt32(750063767) + } + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(packs.count)) + for item in packs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers.count)) + for item in stickers { + item.serialize(buffer, true) + } + break + case .favedStickersNotModified: + if boxed { + buffer.appendInt32(-1634752813) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .favedStickers(let hash, let packs, let stickers): + return ("favedStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any)]) + case .favedStickersNotModified: + return ("favedStickersNotModified", []) + } + } + + public static func parse_favedStickers(_ reader: BufferReader) -> FavedStickers? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.StickerPack]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + } + var _3: [Api.Document]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.FavedStickers.favedStickers(hash: _1!, packs: _2!, stickers: _3!) + } + public static func parse_favedStickersNotModified(_ reader: BufferReader) -> FavedStickers? { + return Api.messages.FavedStickers.favedStickersNotModified + } + + } +} +public extension Api.messages { + enum FeaturedStickers: TypeConstructorDescription { + case featuredStickers(flags: Int32, hash: Int64, count: Int32, sets: [Api.StickerSetCovered], unread: [Int64]) + case featuredStickersNotModified(count: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .featuredStickers(let flags, let hash, let count, let sets, let unread): + if boxed { + buffer.appendInt32(-1103615738) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sets.count)) + for item in sets { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(unread.count)) + for item in unread { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .featuredStickersNotModified(let count): + if boxed { + buffer.appendInt32(-958657434) + } + serializeInt32(count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .featuredStickers(let flags, let hash, let count, let sets, let unread): + return ("featuredStickers", [("flags", flags as Any), ("hash", hash as Any), ("count", count as Any), ("sets", sets as Any), ("unread", unread as Any)]) + case .featuredStickersNotModified(let count): + return ("featuredStickersNotModified", [("count", count as Any)]) + } + } + + public static func parse_featuredStickers(_ reader: BufferReader) -> FeaturedStickers? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } + var _5: [Int64]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.messages.FeaturedStickers.featuredStickers(flags: _1!, hash: _2!, count: _3!, sets: _4!, unread: _5!) + } + public static func parse_featuredStickersNotModified(_ reader: BufferReader) -> FeaturedStickers? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if !_c1 { return nil } + return Api.messages.FeaturedStickers.featuredStickersNotModified(count: _1!) + } + + } +} public extension Api.messages { enum ForumTopics: TypeConstructorDescription { case forumTopics(flags: Int32, count: Int32, topics: [Api.ForumTopic], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], pts: Int32) @@ -72,12 +218,14 @@ public extension Api.messages { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.messages.ForumTopics.forumTopics(flags: _1!, count: _2!, topics: _3!, messages: _4!, chats: _5!, users: _6!, pts: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.messages.ForumTopics.forumTopics(flags: _1!, count: _2!, topics: _3!, messages: _4!, chats: _5!, users: _6!, pts: _7!) } } @@ -127,12 +275,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.FoundStickerSets.foundStickerSets(hash: _1!, sets: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.FoundStickerSets.foundStickerSets(hash: _1!, sets: _2!) } public static func parse_foundStickerSetsNotModified(_ reader: BufferReader) -> FoundStickerSets? { return Api.messages.FoundStickerSets.foundStickerSetsNotModified @@ -194,12 +339,11 @@ public extension Api.messages { let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.FoundStickers.foundStickers(flags: _1!, nextOffset: _2, hash: _3!, stickers: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.messages.FoundStickers.foundStickers(flags: _1!, nextOffset: _2, hash: _3!, stickers: _4!) } public static func parse_foundStickersNotModified(_ reader: BufferReader) -> FoundStickers? { var _1: Int32? @@ -208,12 +352,9 @@ public extension Api.messages { if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.messages.FoundStickers.foundStickersNotModified(flags: _1!, nextOffset: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.FoundStickers.foundStickersNotModified(flags: _1!, nextOffset: _2) } } @@ -260,12 +401,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.HighScores.highScores(scores: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.HighScores.highScores(scores: _1!, users: _2!) } } @@ -296,12 +434,8 @@ public extension Api.messages { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.messages.HistoryImport.historyImport(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.HistoryImport.historyImport(id: _1!) } } @@ -336,12 +470,9 @@ public extension Api.messages { if Int(_1!) & Int(1 << 2) != 0 {_2 = parseString(reader) } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil - if _c1 && _c2 { - return Api.messages.HistoryImportParsed.historyImportParsed(flags: _1!, title: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.HistoryImportParsed.historyImportParsed(flags: _1!, title: _2) } } @@ -398,12 +529,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.InactiveChats.inactiveChats(dates: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.InactiveChats.inactiveChats(dates: _1!, chats: _2!, users: _3!) } } @@ -446,12 +575,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.InvitedUsers.invitedUsers(updates: _1!, missingInvitees: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.InvitedUsers.invitedUsers(updates: _1!, missingInvitees: _2!) } } @@ -482,12 +608,8 @@ public extension Api.messages { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.messages.MessageEditData.messageEditData(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.MessageEditData.messageEditData(flags: _1!) } } @@ -556,12 +678,13 @@ public extension Api.messages { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.messages.MessageReactionsList.messageReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.messages.MessageReactionsList.messageReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6) } } @@ -618,12 +741,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.MessageViews.messageViews(views: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.MessageViews.messageViews(views: _1!, chats: _2!, users: _3!) } } @@ -776,12 +897,15 @@ public extension Api.messages { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.messages.Messages.channelMessages(flags: _1!, pts: _2!, count: _3!, offsetIdOffset: _4, messages: _5!, topics: _6!, chats: _7!, users: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.messages.Messages.channelMessages(flags: _1!, pts: _2!, count: _3!, offsetIdOffset: _4, messages: _5!, topics: _6!, chats: _7!, users: _8!) } public static func parse_messages(_ reader: BufferReader) -> Messages? { var _1: [Api.Message]? @@ -804,23 +928,18 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.Messages.messages(messages: _1!, topics: _2!, chats: _3!, users: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.messages.Messages.messages(messages: _1!, topics: _2!, chats: _3!, users: _4!) } public static func parse_messagesNotModified(_ reader: BufferReader) -> Messages? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.messages.Messages.messagesNotModified(count: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.Messages.messagesNotModified(count: _1!) } public static func parse_messagesSlice(_ reader: BufferReader) -> Messages? { var _1: Int32? @@ -860,12 +979,16 @@ public extension Api.messages { let _c7 = _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.messages.Messages.messagesSlice(flags: _1!, count: _2!, nextRate: _3, offsetIdOffset: _4, searchFlood: _5, messages: _6!, topics: _7!, chats: _8!, users: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.messages.Messages.messagesSlice(flags: _1!, count: _2!, nextRate: _3, offsetIdOffset: _4, searchFlood: _5, messages: _6!, topics: _7!, chats: _8!, users: _9!) } } @@ -906,12 +1029,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.MyStickers.myStickers(count: _1!, sets: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.MyStickers.myStickers(count: _1!, sets: _2!) } } @@ -984,12 +1104,12 @@ public extension Api.messages { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.PeerDialogs.peerDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!, state: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.messages.PeerDialogs.peerDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!, state: _5!) } } @@ -1042,12 +1162,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.PeerSettings.peerSettings(settings: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.PeerSettings.peerSettings(settings: _1!, chats: _2!, users: _3!) } } @@ -1108,12 +1226,12 @@ public extension Api.messages { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.PreparedInlineMessage.preparedInlineMessage(queryId: _1!, result: _2!, peerTypes: _3!, cacheTime: _4!, users: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.messages.PreparedInlineMessage.preparedInlineMessage(queryId: _1!, result: _2!, peerTypes: _3!, cacheTime: _4!, users: _5!) } } @@ -1189,12 +1307,11 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.QuickReplies.quickReplies(quickReplies: _1!, messages: _2!, chats: _3!, users: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.messages.QuickReplies.quickReplies(quickReplies: _1!, messages: _2!, chats: _3!, users: _4!) } public static func parse_quickRepliesNotModified(_ reader: BufferReader) -> QuickReplies? { return Api.messages.QuickReplies.quickRepliesNotModified @@ -1247,12 +1364,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.Reactions.reactions(hash: _1!, reactions: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.Reactions.reactions(hash: _1!, reactions: _2!) } public static func parse_reactionsNotModified(_ reader: BufferReader) -> Reactions? { return Api.messages.Reactions.reactionsNotModified @@ -1325,12 +1439,11 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.RecentStickers.recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.messages.RecentStickers.recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!) } public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? { return Api.messages.RecentStickers.recentStickersNotModified @@ -1438,23 +1551,18 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.SavedDialogs.savedDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.messages.SavedDialogs.savedDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!) } public static func parse_savedDialogsNotModified(_ reader: BufferReader) -> SavedDialogs? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.messages.SavedDialogs.savedDialogsNotModified(count: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.SavedDialogs.savedDialogsNotModified(count: _1!) } public static func parse_savedDialogsSlice(_ reader: BufferReader) -> SavedDialogs? { var _1: Int32? @@ -1480,70 +1588,12 @@ public extension Api.messages { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.SavedDialogs.savedDialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum SavedGifs: TypeConstructorDescription { - case savedGifs(hash: Int64, gifs: [Api.Document]) - case savedGifsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedGifs(let hash, let gifs): - if boxed { - buffer.appendInt32(-2069878259) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(gifs.count)) - for item in gifs { - item.serialize(buffer, true) - } - break - case .savedGifsNotModified: - if boxed { - buffer.appendInt32(-402498398) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .savedGifs(let hash, let gifs): - return ("savedGifs", [("hash", hash as Any), ("gifs", gifs as Any)]) - case .savedGifsNotModified: - return ("savedGifsNotModified", []) - } - } - - public static func parse_savedGifs(_ reader: BufferReader) -> SavedGifs? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Document]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.SavedGifs.savedGifs(hash: _1!, gifs: _2!) - } - else { - return nil - } - } - public static func parse_savedGifsNotModified(_ reader: BufferReader) -> SavedGifs? { - return Api.messages.SavedGifs.savedGifsNotModified + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.messages.SavedDialogs.savedDialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!) } } diff --git a/submodules/TelegramApi/Sources/Api35.swift b/submodules/TelegramApi/Sources/Api35.swift index 31b027ae..0898d641 100644 --- a/submodules/TelegramApi/Sources/Api35.swift +++ b/submodules/TelegramApi/Sources/Api35.swift @@ -1,3 +1,58 @@ +public extension Api.messages { + enum SavedGifs: TypeConstructorDescription { + case savedGifs(hash: Int64, gifs: [Api.Document]) + case savedGifsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedGifs(let hash, let gifs): + if boxed { + buffer.appendInt32(-2069878259) + } + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(gifs.count)) + for item in gifs { + item.serialize(buffer, true) + } + break + case .savedGifsNotModified: + if boxed { + buffer.appendInt32(-402498398) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .savedGifs(let hash, let gifs): + return ("savedGifs", [("hash", hash as Any), ("gifs", gifs as Any)]) + case .savedGifsNotModified: + return ("savedGifsNotModified", []) + } + } + + public static func parse_savedGifs(_ reader: BufferReader) -> SavedGifs? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Document]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.SavedGifs.savedGifs(hash: _1!, gifs: _2!) + } + public static func parse_savedGifsNotModified(_ reader: BufferReader) -> SavedGifs? { + return Api.messages.SavedGifs.savedGifsNotModified + } + + } +} public extension Api.messages { enum SavedReactionTags: TypeConstructorDescription { case savedReactionTags(tags: [Api.SavedReactionTag], hash: Int64) @@ -43,12 +98,9 @@ public extension Api.messages { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.SavedReactionTags.savedReactionTags(tags: _1!, hash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.SavedReactionTags.savedReactionTags(tags: _1!, hash: _2!) } public static func parse_savedReactionTagsNotModified(_ reader: BufferReader) -> SavedReactionTags? { return Api.messages.SavedReactionTags.savedReactionTagsNotModified @@ -92,12 +144,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.SearchCounter.searchCounter(flags: _1!, filter: _2!, count: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.SearchCounter.searchCounter(flags: _1!, filter: _2!, count: _3!) } } @@ -184,12 +234,16 @@ public extension Api.messages { let _c7 = _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.messages.SearchResultsCalendar.searchResultsCalendar(flags: _1!, count: _2!, minDate: _3!, minMsgId: _4!, offsetIdOffset: _5, periods: _6!, messages: _7!, chats: _8!, users: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.messages.SearchResultsCalendar.searchResultsCalendar(flags: _1!, count: _2!, minDate: _3!, minMsgId: _4!, offsetIdOffset: _5, periods: _6!, messages: _7!, chats: _8!, users: _9!) } } @@ -230,12 +284,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.SearchResultsPositions.searchResultsPositions(count: _1!, positions: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.SearchResultsPositions.searchResultsPositions(count: _1!, positions: _2!) } } @@ -281,23 +332,16 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.SentEncryptedMessage.sentEncryptedFile(date: _1!, file: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.SentEncryptedMessage.sentEncryptedFile(date: _1!, file: _2!) } public static func parse_sentEncryptedMessage(_ reader: BufferReader) -> SentEncryptedMessage? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.messages.SentEncryptedMessage.sentEncryptedMessage(date: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.SentEncryptedMessage.sentEncryptedMessage(date: _1!) } } @@ -379,12 +423,14 @@ public extension Api.messages { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.messages.SponsoredMessages.sponsoredMessages(flags: _1!, postsBetween: _2, startDelay: _3, betweenDelay: _4, messages: _5!, chats: _6!, users: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.messages.SponsoredMessages.sponsoredMessages(flags: _1!, postsBetween: _2, startDelay: _3, betweenDelay: _4, messages: _5!, chats: _6!, users: _7!) } public static func parse_sponsoredMessagesEmpty(_ reader: BufferReader) -> SponsoredMessages? { return Api.messages.SponsoredMessages.sponsoredMessagesEmpty @@ -459,12 +505,11 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.StickerSet.stickerSet(set: _1!, packs: _2!, keywords: _3!, documents: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.messages.StickerSet.stickerSet(set: _1!, packs: _2!, keywords: _3!, documents: _4!) } public static func parse_stickerSetNotModified(_ reader: BufferReader) -> StickerSet? { return Api.messages.StickerSet.stickerSetNotModified @@ -513,12 +558,8 @@ public extension Api.messages { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) } let _c1 = _1 != nil - if _c1 { - return Api.messages.StickerSetInstallResult.stickerSetInstallResultArchive(sets: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.StickerSetInstallResult.stickerSetInstallResultArchive(sets: _1!) } public static func parse_stickerSetInstallResultSuccess(_ reader: BufferReader) -> StickerSetInstallResult? { return Api.messages.StickerSetInstallResult.stickerSetInstallResultSuccess @@ -571,12 +612,9 @@ public extension Api.messages { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.Stickers.stickers(hash: _1!, stickers: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.messages.Stickers.stickers(hash: _1!, stickers: _2!) } public static func parse_stickersNotModified(_ reader: BufferReader) -> Stickers? { return Api.messages.Stickers.stickersNotModified @@ -626,12 +664,12 @@ public extension Api.messages { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.TranscribedAudio.transcribedAudio(flags: _1!, transcriptionId: _2!, text: _3!, trialRemainsNum: _4, trialRemainsUntilDate: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.messages.TranscribedAudio.transcribedAudio(flags: _1!, transcriptionId: _2!, text: _3!, trialRemainsNum: _4, trialRemainsUntilDate: _5) } } @@ -668,12 +706,8 @@ public extension Api.messages { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TextWithEntities.self) } let _c1 = _1 != nil - if _c1 { - return Api.messages.TranslatedText.translateResult(result: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.messages.TranslatedText.translateResult(result: _1!) } } @@ -742,12 +776,13 @@ public extension Api.messages { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.messages.VotesList.votesList(flags: _1!, count: _2!, votes: _3!, chats: _4!, users: _5!, nextOffset: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.messages.VotesList.votesList(flags: _1!, count: _2!, votes: _3!, chats: _4!, users: _5!, nextOffset: _6) } } @@ -800,12 +835,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.WebPage.webPage(webpage: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.WebPage.webPage(webpage: _1!, chats: _2!, users: _3!) } } @@ -858,12 +891,10 @@ public extension Api.messages { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.WebPagePreview.webPagePreview(media: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.messages.WebPagePreview.webPagePreview(media: _1!, chats: _2!, users: _3!) } } @@ -904,12 +935,9 @@ public extension Api.payments { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.payments.BankCardData.bankCardData(title: _1!, openUrls: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.payments.BankCardData.bankCardData(title: _1!, openUrls: _2!) } } @@ -951,12 +979,8 @@ public extension Api.payments { _1 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } let _c1 = _1 != nil - if _c1 { - return Api.payments.CheckCanSendGiftResult.checkCanSendGiftResultFail(reason: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.payments.CheckCanSendGiftResult.checkCanSendGiftResultFail(reason: _1!) } public static func parse_checkCanSendGiftResultOk(_ reader: BufferReader) -> CheckCanSendGiftResult? { return Api.payments.CheckCanSendGiftResult.checkCanSendGiftResultOk @@ -1036,12 +1060,16 @@ public extension Api.payments { let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.payments.CheckedGiftCode.checkedGiftCode(flags: _1!, fromId: _2, giveawayMsgId: _3, toId: _4, date: _5!, days: _6!, usedDate: _7, chats: _8!, users: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.payments.CheckedGiftCode.checkedGiftCode(flags: _1!, fromId: _2, giveawayMsgId: _3, toId: _4, date: _5!, days: _6!, usedDate: _7, chats: _8!, users: _9!) } } @@ -1092,12 +1120,10 @@ public extension Api.payments { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.ConnectedStarRefBots.connectedStarRefBots(count: _1!, connectedBots: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.payments.ConnectedStarRefBots.connectedStarRefBots(count: _1!, connectedBots: _2!, users: _3!) } } @@ -1128,12 +1154,8 @@ public extension Api.payments { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.payments.ExportedInvoice.exportedInvoice(url: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.payments.ExportedInvoice.exportedInvoice(url: _1!) } } @@ -1195,12 +1217,12 @@ public extension Api.payments { let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.payments.GiveawayInfo.giveawayInfo(flags: _1!, startDate: _2!, joinedTooEarlyDate: _3, adminDisallowedChatId: _4, disallowedCountry: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.payments.GiveawayInfo.giveawayInfo(flags: _1!, startDate: _2!, joinedTooEarlyDate: _3, adminDisallowedChatId: _4, disallowedCountry: _5) } public static func parse_giveawayInfoResults(_ reader: BufferReader) -> GiveawayInfo? { var _1: Int32? @@ -1224,12 +1246,14 @@ public extension Api.payments { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.payments.GiveawayInfo.giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, starsPrize: _4, finishDate: _5!, winnersCount: _6!, activatedCount: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.payments.GiveawayInfo.giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, starsPrize: _4, finishDate: _5!, winnersCount: _6!, activatedCount: _7) } } @@ -1372,12 +1396,22 @@ public extension Api.payments { let _c13 = (Int(_1!) & Int(1 << 0) == 0) || _13 != nil let _c14 = (Int(_1!) & Int(1 << 1) == 0) || _14 != nil let _c15 = _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.payments.PaymentForm.paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, additionalMethods: _12, savedInfo: _13, savedCredentials: _14, users: _15!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + return Api.payments.PaymentForm.paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, additionalMethods: _12, savedInfo: _13, savedCredentials: _14, users: _15!) } public static func parse_paymentFormStarGift(_ reader: BufferReader) -> PaymentForm? { var _1: Int64? @@ -1388,12 +1422,9 @@ public extension Api.payments { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.payments.PaymentForm.paymentFormStarGift(formId: _1!, invoice: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.payments.PaymentForm.paymentFormStarGift(formId: _1!, invoice: _2!) } public static func parse_paymentFormStars(_ reader: BufferReader) -> PaymentForm? { var _1: Int32? @@ -1426,188 +1457,15 @@ public extension Api.payments { let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.payments.PaymentForm.paymentFormStars(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, users: _8!) - } - else { - return nil - } - } - - } -} -public extension Api.payments { - enum PaymentReceipt: TypeConstructorDescription { - case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User]) - case paymentReceiptStars(flags: Int32, date: Int32, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, currency: String, totalAmount: Int64, transactionId: String, users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): - if boxed { - buffer.appendInt32(1891958275) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeInt64(providerId, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} - invoice.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {shipping!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)} - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(totalAmount, buffer: buffer, boxed: false) - serializeString(credentialsTitle, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .paymentReceiptStars(let flags, let date, let botId, let title, let description, let photo, let invoice, let currency, let totalAmount, let transactionId, let users): - if boxed { - buffer.appendInt32(-625215430) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} - invoice.serialize(buffer, true) - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(totalAmount, buffer: buffer, boxed: false) - serializeString(transactionId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): - return ("paymentReceipt", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("providerId", providerId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("info", info as Any), ("shipping", shipping as Any), ("tipAmount", tipAmount as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("credentialsTitle", credentialsTitle as Any), ("users", users as Any)]) - case .paymentReceiptStars(let flags, let date, let botId, let title, let description, let photo, let invoice, let currency, let totalAmount, let transactionId, let users): - return ("paymentReceiptStars", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("transactionId", transactionId as Any), ("users", users as Any)]) - } - } - - public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - var _7: Api.WebDocument? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.WebDocument - } } - var _8: Api.Invoice? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Invoice - } - var _9: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } } - var _10: Api.ShippingOption? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.ShippingOption - } } - var _11: Int64? - if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() } - var _12: String? - _12 = parseString(reader) - var _13: Int64? - _13 = reader.readInt64() - var _14: String? - _14 = parseString(reader) - var _15: [Api.User]? - if let _ = reader.readInt32() { - _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = _12 != nil - let _c13 = _13 != nil - let _c14 = _14 != nil - let _c15 = _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.payments.PaymentReceipt.paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!) - } - else { - return nil - } - } - public static func parse_paymentReceiptStars(_ reader: BufferReader) -> PaymentReceipt? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Api.WebDocument? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WebDocument - } } - var _7: Api.Invoice? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Invoice - } - var _8: String? - _8 = parseString(reader) - var _9: Int64? - _9 = reader.readInt64() - var _10: String? - _10 = parseString(reader) - var _11: [Api.User]? - if let _ = reader.readInt32() { - _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.payments.PaymentReceipt.paymentReceiptStars(flags: _1!, date: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, currency: _8!, totalAmount: _9!, transactionId: _10!, users: _11!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.payments.PaymentForm.paymentFormStars(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, users: _8!) } } diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index 22436f1e..1bad226d 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -1,3 +1,195 @@ +public extension Api.payments { + enum PaymentReceipt: TypeConstructorDescription { + case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User]) + case paymentReceiptStars(flags: Int32, date: Int32, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, currency: String, totalAmount: Int64, transactionId: String, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): + if boxed { + buffer.appendInt32(1891958275) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeInt64(providerId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} + invoice.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {shipping!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)} + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(totalAmount, buffer: buffer, boxed: false) + serializeString(credentialsTitle, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .paymentReceiptStars(let flags, let date, let botId, let title, let description, let photo, let invoice, let currency, let totalAmount, let transactionId, let users): + if boxed { + buffer.appendInt32(-625215430) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} + invoice.serialize(buffer, true) + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(totalAmount, buffer: buffer, boxed: false) + serializeString(transactionId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): + return ("paymentReceipt", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("providerId", providerId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("info", info as Any), ("shipping", shipping as Any), ("tipAmount", tipAmount as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("credentialsTitle", credentialsTitle as Any), ("users", users as Any)]) + case .paymentReceiptStars(let flags, let date, let botId, let title, let description, let photo, let invoice, let currency, let totalAmount, let transactionId, let users): + return ("paymentReceiptStars", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("transactionId", transactionId as Any), ("users", users as Any)]) + } + } + + public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + var _7: Api.WebDocument? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } + var _8: Api.Invoice? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _9: Api.PaymentRequestedInfo? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } } + var _10: Api.ShippingOption? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.ShippingOption + } } + var _11: Int64? + if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() } + var _12: String? + _12 = parseString(reader) + var _13: Int64? + _13 = reader.readInt64() + var _14: String? + _14 = parseString(reader) + var _15: [Api.User]? + if let _ = reader.readInt32() { + _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil + let _c12 = _12 != nil + let _c13 = _13 != nil + let _c14 = _14 != nil + let _c15 = _15 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + return Api.payments.PaymentReceipt.paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!) + } + public static func parse_paymentReceiptStars(_ reader: BufferReader) -> PaymentReceipt? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.WebDocument? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } + var _7: Api.Invoice? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _8: String? + _8 = parseString(reader) + var _9: Int64? + _9 = reader.readInt64() + var _10: String? + _10 = parseString(reader) + var _11: [Api.User]? + if let _ = reader.readInt32() { + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + return Api.payments.PaymentReceipt.paymentReceiptStars(flags: _1!, date: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, currency: _8!, totalAmount: _9!, transactionId: _10!, users: _11!) + } + + } +} public extension Api.payments { indirect enum PaymentResult: TypeConstructorDescription { case paymentResult(updates: Api.Updates) @@ -35,23 +227,15 @@ public extension Api.payments { _1 = Api.parse(reader, signature: signature) as? Api.Updates } let _c1 = _1 != nil - if _c1 { - return Api.payments.PaymentResult.paymentResult(updates: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.payments.PaymentResult.paymentResult(updates: _1!) } public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.payments.PaymentResult.paymentVerificationNeeded(url: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.payments.PaymentResult.paymentVerificationNeeded(url: _1!) } } @@ -144,12 +328,16 @@ public extension Api.payments { let _c7 = _7 != nil let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.payments.ResaleStarGifts.resaleStarGifts(flags: _1!, count: _2!, gifts: _3!, nextOffset: _4, attributes: _5, attributesHash: _6, chats: _7!, counters: _8, users: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.payments.ResaleStarGifts.resaleStarGifts(flags: _1!, count: _2!, gifts: _3!, nextOffset: _4, attributes: _5, attributesHash: _6, chats: _7!, counters: _8, users: _9!) } } @@ -186,12 +374,9 @@ public extension Api.payments { } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.payments.SavedInfo.savedInfo(flags: _1!, savedInfo: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.payments.SavedInfo.savedInfo(flags: _1!, savedInfo: _2) } } @@ -266,12 +451,14 @@ public extension Api.payments { let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, gifts: _4!, nextOffset: _5, chats: _6!, users: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, gifts: _4!, nextOffset: _5, chats: _6!, users: _7!) } } @@ -337,12 +524,10 @@ public extension Api.payments { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.StarGiftActiveAuctions.starGiftActiveAuctions(auctions: _1!, users: _2!, chats: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.payments.StarGiftActiveAuctions.starGiftActiveAuctions(auctions: _1!, users: _2!, chats: _3!) } public static func parse_starGiftActiveAuctionsNotModified(_ reader: BufferReader) -> StarGiftActiveAuctions? { return Api.payments.StarGiftActiveAuctions.starGiftActiveAuctionsNotModified @@ -402,12 +587,10 @@ public extension Api.payments { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.StarGiftAuctionAcquiredGifts.starGiftAuctionAcquiredGifts(gifts: _1!, users: _2!, chats: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.payments.StarGiftAuctionAcquiredGifts.starGiftAuctionAcquiredGifts(gifts: _1!, users: _2!, chats: _3!) } } @@ -476,12 +659,13 @@ public extension Api.payments { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.payments.StarGiftAuctionState.starGiftAuctionState(gift: _1!, state: _2!, userState: _3!, timeout: _4!, users: _5!, chats: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.payments.StarGiftAuctionState.starGiftAuctionState(gift: _1!, state: _2!, userState: _3!, timeout: _4!, users: _5!, chats: _6!) } } @@ -527,12 +711,8 @@ public extension Api.payments { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftCollection.self) } let _c1 = _1 != nil - if _c1 { - return Api.payments.StarGiftCollections.starGiftCollections(collections: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.payments.StarGiftCollections.starGiftCollections(collections: _1!) } public static func parse_starGiftCollectionsNotModified(_ reader: BufferReader) -> StarGiftCollections? { return Api.payments.StarGiftCollections.starGiftCollectionsNotModified @@ -572,12 +752,8 @@ public extension Api.payments { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) } let _c1 = _1 != nil - if _c1 { - return Api.payments.StarGiftUpgradeAttributes.starGiftUpgradeAttributes(attributes: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.payments.StarGiftUpgradeAttributes.starGiftUpgradeAttributes(attributes: _1!) } } @@ -634,12 +810,10 @@ public extension Api.payments { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.StarGiftUpgradePreview.starGiftUpgradePreview(sampleAttributes: _1!, prices: _2!, nextPrices: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.payments.StarGiftUpgradePreview.starGiftUpgradePreview(sampleAttributes: _1!, prices: _2!, nextPrices: _3!) } } @@ -670,12 +844,8 @@ public extension Api.payments { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.payments.StarGiftWithdrawalUrl.starGiftWithdrawalUrl(url: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.payments.StarGiftWithdrawalUrl.starGiftWithdrawalUrl(url: _1!) } } @@ -745,12 +915,11 @@ public extension Api.payments { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.payments.StarGifts.starGifts(hash: _1!, gifts: _2!, chats: _3!, users: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.payments.StarGifts.starGifts(hash: _1!, gifts: _2!, chats: _3!, users: _4!) } public static func parse_starGiftsNotModified(_ reader: BufferReader) -> StarGifts? { return Api.payments.StarGifts.starGiftsNotModified @@ -784,12 +953,8 @@ public extension Api.payments { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.payments.StarsRevenueAdsAccountUrl.starsRevenueAdsAccountUrl(url: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.payments.StarsRevenueAdsAccountUrl.starsRevenueAdsAccountUrl(url: _1!) } } @@ -842,12 +1007,12 @@ public extension Api.payments { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.payments.StarsRevenueStats.starsRevenueStats(flags: _1!, topHoursGraph: _2, revenueGraph: _3!, status: _4!, usdRate: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.payments.StarsRevenueStats.starsRevenueStats(flags: _1!, topHoursGraph: _2, revenueGraph: _3!, status: _4!, usdRate: _5!) } } @@ -878,12 +1043,8 @@ public extension Api.payments { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.payments.StarsRevenueWithdrawalUrl.starsRevenueWithdrawalUrl(url: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.payments.StarsRevenueWithdrawalUrl.starsRevenueWithdrawalUrl(url: _1!) } } @@ -972,12 +1133,16 @@ public extension Api.payments { let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.payments.StarsStatus.starsStatus(flags: _1!, balance: _2!, subscriptions: _3, subscriptionsNextOffset: _4, subscriptionsMissingBalance: _5, history: _6, nextOffset: _7, chats: _8!, users: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.payments.StarsStatus.starsStatus(flags: _1!, balance: _2!, subscriptions: _3, subscriptionsNextOffset: _4, subscriptionsMissingBalance: _5, history: _6, nextOffset: _7, chats: _8!, users: _9!) } } @@ -1036,12 +1201,12 @@ public extension Api.payments { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.payments.SuggestedStarRefBots.suggestedStarRefBots(flags: _1!, count: _2!, suggestedBots: _3!, users: _4!, nextOffset: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.payments.SuggestedStarRefBots.suggestedStarRefBots(flags: _1!, count: _2!, suggestedBots: _3!, users: _4!, nextOffset: _5) } } @@ -1094,12 +1259,10 @@ public extension Api.payments { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.UniqueStarGift.uniqueStarGift(gift: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.payments.UniqueStarGift.uniqueStarGift(gift: _1!, chats: _2!, users: _3!) } } @@ -1178,12 +1341,20 @@ public extension Api.payments { let _c11 = (Int(_1!) & Int(1 << 4) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 5) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.payments.UniqueStarGiftValueInfo.uniqueStarGiftValueInfo(flags: _1!, currency: _2!, value: _3!, initialSaleDate: _4!, initialSaleStars: _5!, initialSalePrice: _6!, lastSaleDate: _7, lastSalePrice: _8, floorPrice: _9, averagePrice: _10, listedCount: _11, fragmentListedCount: _12, fragmentListedUrl: _13) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + return Api.payments.UniqueStarGiftValueInfo.uniqueStarGiftValueInfo(flags: _1!, currency: _2!, value: _3!, initialSaleDate: _4!, initialSaleStars: _5!, initialSalePrice: _6!, lastSaleDate: _7, lastSalePrice: _8, floorPrice: _9, averagePrice: _10, listedCount: _11, fragmentListedCount: _12, fragmentListedUrl: _13) } } @@ -1228,12 +1399,10 @@ public extension Api.payments { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.ValidatedRequestedInfo.validatedRequestedInfo(flags: _1!, id: _2, shippingOptions: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.payments.ValidatedRequestedInfo.validatedRequestedInfo(flags: _1!, id: _2, shippingOptions: _3) } } @@ -1264,12 +1433,8 @@ public extension Api.phone { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(link: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(link: _1!) } } @@ -1336,12 +1501,12 @@ public extension Api.phone { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!) } } @@ -1402,12 +1567,11 @@ public extension Api.phone { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.phone.GroupCallStars.groupCallStars(totalStars: _1!, topDonors: _2!, chats: _3!, users: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.phone.GroupCallStars.groupCallStars(totalStars: _1!, topDonors: _2!, chats: _3!, users: _4!) } } @@ -1444,12 +1608,8 @@ public extension Api.phone { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallStreamChannel.self) } let _c1 = _1 != nil - if _c1 { - return Api.phone.GroupCallStreamChannels.groupCallStreamChannels(channels: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.phone.GroupCallStreamChannels.groupCallStreamChannels(channels: _1!) } } @@ -1484,12 +1644,9 @@ public extension Api.phone { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.phone.GroupCallStreamRtmpUrl.groupCallStreamRtmpUrl(url: _1!, key: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.phone.GroupCallStreamRtmpUrl.groupCallStreamRtmpUrl(url: _1!, key: _2!) } } @@ -1558,122 +1715,13 @@ public extension Api.phone { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.phone.GroupParticipants.groupParticipants(count: _1!, participants: _2!, nextOffset: _3!, chats: _4!, users: _5!, version: _6!) - } - else { - return nil - } - } - - } -} -public extension Api.phone { - enum JoinAsPeers: TypeConstructorDescription { - case joinAsPeers(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .joinAsPeers(let peers, let chats, let users): - if boxed { - buffer.appendInt32(-1343921601) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .joinAsPeers(let peers, let chats, let users): - return ("joinAsPeers", [("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_joinAsPeers(_ reader: BufferReader) -> JoinAsPeers? { - var _1: [Api.Peer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.phone.JoinAsPeers.joinAsPeers(peers: _1!, chats: _2!, users: _3!) - } - else { - return nil - } - } - - } -} -public extension Api.phone { - enum PhoneCall: TypeConstructorDescription { - case phoneCall(phoneCall: Api.PhoneCall, users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .phoneCall(let phoneCall, let users): - if boxed { - buffer.appendInt32(-326966976) - } - phoneCall.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .phoneCall(let phoneCall, let users): - return ("phoneCall", [("phoneCall", phoneCall as Any), ("users", users as Any)]) - } - } - - public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? { - var _1: Api.PhoneCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.phone.PhoneCall.phoneCall(phoneCall: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.phone.GroupParticipants.groupParticipants(count: _1!, participants: _2!, nextOffset: _3!, chats: _4!, users: _5!, version: _6!) } } diff --git a/submodules/TelegramApi/Sources/Api37.swift b/submodules/TelegramApi/Sources/Api37.swift index 3b5d2415..c9b4cfd1 100644 --- a/submodules/TelegramApi/Sources/Api37.swift +++ b/submodules/TelegramApi/Sources/Api37.swift @@ -1,3 +1,108 @@ +public extension Api.phone { + enum JoinAsPeers: TypeConstructorDescription { + case joinAsPeers(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .joinAsPeers(let peers, let chats, let users): + if boxed { + buffer.appendInt32(-1343921601) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .joinAsPeers(let peers, let chats, let users): + return ("joinAsPeers", [("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_joinAsPeers(_ reader: BufferReader) -> JoinAsPeers? { + var _1: [Api.Peer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.phone.JoinAsPeers.joinAsPeers(peers: _1!, chats: _2!, users: _3!) + } + + } +} +public extension Api.phone { + enum PhoneCall: TypeConstructorDescription { + case phoneCall(phoneCall: Api.PhoneCall, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .phoneCall(let phoneCall, let users): + if boxed { + buffer.appendInt32(-326966976) + } + phoneCall.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .phoneCall(let phoneCall, let users): + return ("phoneCall", [("phoneCall", phoneCall as Any), ("users", users as Any)]) + } + } + + public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? { + var _1: Api.PhoneCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if !_c1 { return nil } + if !_c2 { return nil } + return Api.phone.PhoneCall.phoneCall(phoneCall: _1!, users: _2!) + } + + } +} public extension Api.photos { enum Photo: TypeConstructorDescription { case photo(photo: Api.Photo, users: [Api.User]) @@ -36,12 +141,9 @@ public extension Api.photos { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.photos.Photo.photo(photo: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.photos.Photo.photo(photo: _1!, users: _2!) } } @@ -107,12 +209,9 @@ public extension Api.photos { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.photos.Photos.photos(photos: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.photos.Photos.photos(photos: _1!, users: _2!) } public static func parse_photosSlice(_ reader: BufferReader) -> Photos? { var _1: Int32? @@ -128,12 +227,10 @@ public extension Api.photos { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.photos.Photos.photosSlice(count: _1!, photos: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.photos.Photos.photosSlice(count: _1!, photos: _2!, users: _3!) } } @@ -192,12 +289,12 @@ public extension Api.premium { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.premium.BoostsList.boostsList(flags: _1!, count: _2!, boosts: _3!, nextOffset: _4, users: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.premium.BoostsList.boostsList(flags: _1!, count: _2!, boosts: _3!, nextOffset: _4, users: _5!) } } @@ -278,12 +375,17 @@ public extension Api.premium { let _c8 = _8 != nil let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.premium.BoostsStatus.boostsStatus(flags: _1!, level: _2!, currentLevelBoosts: _3!, boosts: _4!, giftBoosts: _5, nextLevelBoosts: _6, premiumAudience: _7, boostUrl: _8!, prepaidGiveaways: _9, myBoostSlots: _10) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + return Api.premium.BoostsStatus.boostsStatus(flags: _1!, level: _2!, currentLevelBoosts: _3!, boosts: _4!, giftBoosts: _5, nextLevelBoosts: _6, premiumAudience: _7, boostUrl: _8!, prepaidGiveaways: _9, myBoostSlots: _10) } } @@ -340,12 +442,10 @@ public extension Api.premium { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.premium.MyBoosts.myBoosts(myBoosts: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.premium.MyBoosts.myBoosts(myBoosts: _1!, chats: _2!, users: _3!) } } @@ -380,12 +480,9 @@ public extension Api.smsjobs { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.smsjobs.EligibilityToJoin.eligibleToJoin(termsUrl: _1!, monthlySentSms: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.smsjobs.EligibilityToJoin.eligibleToJoin(termsUrl: _1!, monthlySentSms: _2!) } } @@ -444,12 +541,15 @@ public extension Api.smsjobs { let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.smsjobs.Status.status(flags: _1!, recentSent: _2!, recentSince: _3!, recentRemains: _4!, totalSent: _5!, totalSince: _6!, lastGiftSlug: _7, termsUrl: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.smsjobs.Status.status(flags: _1!, recentSent: _2!, recentSince: _3!, recentRemains: _4!, totalSent: _5!, totalSince: _6!, lastGiftSlug: _7, termsUrl: _8!) } } @@ -612,12 +712,29 @@ public extension Api.stats { let _c20 = _20 != nil let _c21 = _21 != nil let _c22 = _22 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { - return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, reactionsPerPost: _5!, viewsPerStory: _6!, sharesPerStory: _7!, reactionsPerStory: _8!, enabledNotifications: _9!, growthGraph: _10!, followersGraph: _11!, muteGraph: _12!, topHoursGraph: _13!, interactionsGraph: _14!, ivInteractionsGraph: _15!, viewsBySourceGraph: _16!, newFollowersBySourceGraph: _17!, languagesGraph: _18!, reactionsByEmotionGraph: _19!, storyInteractionsGraph: _20!, storyReactionsByEmotionGraph: _21!, recentPostsInteractions: _22!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + if !_c20 { return nil } + if !_c21 { return nil } + if !_c22 { return nil } + return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, reactionsPerPost: _5!, viewsPerStory: _6!, sharesPerStory: _7!, reactionsPerStory: _8!, enabledNotifications: _9!, growthGraph: _10!, followersGraph: _11!, muteGraph: _12!, topHoursGraph: _13!, interactionsGraph: _14!, ivInteractionsGraph: _15!, viewsBySourceGraph: _16!, newFollowersBySourceGraph: _17!, languagesGraph: _18!, reactionsByEmotionGraph: _19!, storyInteractionsGraph: _20!, storyReactionsByEmotionGraph: _21!, recentPostsInteractions: _22!) } } @@ -762,12 +879,24 @@ public extension Api.stats { let _c15 = _15 != nil let _c16 = _16 != nil let _c17 = _17 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { - return Api.stats.MegagroupStats.megagroupStats(period: _1!, members: _2!, messages: _3!, viewers: _4!, posters: _5!, growthGraph: _6!, membersGraph: _7!, newMembersBySourceGraph: _8!, languagesGraph: _9!, messagesGraph: _10!, actionsGraph: _11!, topHoursGraph: _12!, weekdaysGraph: _13!, topPosters: _14!, topAdmins: _15!, topInviters: _16!, users: _17!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + return Api.stats.MegagroupStats.megagroupStats(period: _1!, members: _2!, messages: _3!, viewers: _4!, posters: _5!, growthGraph: _6!, membersGraph: _7!, newMembersBySourceGraph: _8!, languagesGraph: _9!, messagesGraph: _10!, actionsGraph: _11!, topHoursGraph: _12!, weekdaysGraph: _13!, topPosters: _14!, topAdmins: _15!, topInviters: _16!, users: _17!) } } @@ -806,12 +935,9 @@ public extension Api.stats { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stats.MessageStats.messageStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.stats.MessageStats.messageStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!) } } @@ -880,12 +1006,13 @@ public extension Api.stats { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stats.PublicForwards.publicForwards(flags: _1!, count: _2!, forwards: _3!, nextOffset: _4, chats: _5!, users: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.stats.PublicForwards.publicForwards(flags: _1!, count: _2!, forwards: _3!, nextOffset: _4, chats: _5!, users: _6!) } } @@ -924,12 +1051,9 @@ public extension Api.stats { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stats.StoryStats.storyStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.stats.StoryStats.storyStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!) } } @@ -960,12 +1084,8 @@ public extension Api.stickers { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.stickers.SuggestedShortName.suggestedShortName(shortName: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.stickers.SuggestedShortName.suggestedShortName(shortName: _1!) } } @@ -1151,12 +1271,9 @@ public extension Api.stories { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stories.Albums.albums(hash: _1!, albums: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.stories.Albums.albums(hash: _1!, albums: _2!) } public static func parse_albumsNotModified(_ reader: BufferReader) -> Albums? { return Api.stories.Albums.albumsNotModified @@ -1245,12 +1362,14 @@ public extension Api.stories { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.stories.AllStories.allStories(flags: _1!, count: _2!, state: _3!, peerStories: _4!, chats: _5!, users: _6!, stealthMode: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.stories.AllStories.allStories(flags: _1!, count: _2!, state: _3!, peerStories: _4!, chats: _5!, users: _6!, stealthMode: _7!) } public static func parse_allStoriesNotModified(_ reader: BufferReader) -> AllStories? { var _1: Int32? @@ -1264,12 +1383,10 @@ public extension Api.stories { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.stories.AllStories.allStoriesNotModified(flags: _1!, state: _2!, stealthMode: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.stories.AllStories.allStoriesNotModified(flags: _1!, state: _2!, stealthMode: _3!) } } @@ -1300,12 +1417,8 @@ public extension Api.stories { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.stories.CanSendStoryCount.canSendStoryCount(countRemains: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.stories.CanSendStoryCount.canSendStoryCount(countRemains: _1!) } } @@ -1374,150 +1487,13 @@ public extension Api.stories { let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stories.FoundStories.foundStories(flags: _1!, count: _2!, stories: _3!, nextOffset: _4, chats: _5!, users: _6!) - } - else { - return nil - } - } - - } -} -public extension Api.stories { - enum PeerStories: TypeConstructorDescription { - case peerStories(stories: Api.PeerStories, chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerStories(let stories, let chats, let users): - if boxed { - buffer.appendInt32(-890861720) - } - stories.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .peerStories(let stories, let chats, let users): - return ("peerStories", [("stories", stories as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? { - var _1: Api.PeerStories? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PeerStories - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.stories.PeerStories.peerStories(stories: _1!, chats: _2!, users: _3!) - } - else { - return nil - } - } - - } -} -public extension Api.stories { - enum Stories: TypeConstructorDescription { - case stories(flags: Int32, count: Int32, stories: [Api.StoryItem], pinnedToTop: [Int32]?, chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users): - if boxed { - buffer.appendInt32(1673780490) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stories.count)) - for item in stories { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(pinnedToTop!.count)) - for item in pinnedToTop! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users): - return ("stories", [("flags", flags as Any), ("count", count as Any), ("stories", stories as Any), ("pinnedToTop", pinnedToTop as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_stories(_ reader: BufferReader) -> Stories? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.StoryItem]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self) - } - var _4: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stories.Stories.stories(flags: _1!, count: _2!, stories: _3!, pinnedToTop: _4, chats: _5!, users: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.stories.FoundStories.foundStories(flags: _1!, count: _2!, stories: _3!, nextOffset: _4, chats: _5!, users: _6!) } } diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index 77f635e8..68d4e320 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -1,3 +1,140 @@ +public extension Api.stories { + enum PeerStories: TypeConstructorDescription { + case peerStories(stories: Api.PeerStories, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerStories(let stories, let chats, let users): + if boxed { + buffer.appendInt32(-890861720) + } + stories.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .peerStories(let stories, let chats, let users): + return ("peerStories", [("stories", stories as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? { + var _1: Api.PeerStories? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PeerStories + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.stories.PeerStories.peerStories(stories: _1!, chats: _2!, users: _3!) + } + + } +} +public extension Api.stories { + enum Stories: TypeConstructorDescription { + case stories(flags: Int32, count: Int32, stories: [Api.StoryItem], pinnedToTop: [Int32]?, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users): + if boxed { + buffer.appendInt32(1673780490) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stories.count)) + for item in stories { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(pinnedToTop!.count)) + for item in pinnedToTop! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users): + return ("stories", [("flags", flags as Any), ("count", count as Any), ("stories", stories as Any), ("pinnedToTop", pinnedToTop as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_stories(_ reader: BufferReader) -> Stories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.StoryItem]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self) + } + var _4: [Int32]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.stories.Stories.stories(flags: _1!, count: _2!, stories: _3!, pinnedToTop: _4, chats: _5!, users: _6!) + } + + } +} public extension Api.stories { enum StoryReactionsList: TypeConstructorDescription { case storyReactionsList(flags: Int32, count: Int32, reactions: [Api.StoryReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?) @@ -62,12 +199,13 @@ public extension Api.stories { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stories.StoryReactionsList.storyReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.stories.StoryReactionsList.storyReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6) } } @@ -114,12 +252,9 @@ public extension Api.stories { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stories.StoryViews.storyViews(views: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.stories.StoryViews.storyViews(views: _1!, users: _2!) } } @@ -200,12 +335,16 @@ public extension Api.stories { let _c7 = _7 != nil let _c8 = _8 != nil let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.stories.StoryViewsList.storyViewsList(flags: _1!, count: _2!, viewsCount: _3!, forwardsCount: _4!, reactionsCount: _5!, views: _6!, chats: _7!, users: _8!, nextOffset: _9) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.stories.StoryViewsList.storyViewsList(flags: _1!, count: _2!, viewsCount: _3!, forwardsCount: _4!, reactionsCount: _5!, views: _6!, chats: _7!, users: _8!, nextOffset: _9) } } @@ -321,12 +460,14 @@ public extension Api.updates { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.updates.ChannelDifference.channelDifference(flags: _1!, pts: _2!, timeout: _3, newMessages: _4!, otherUpdates: _5!, chats: _6!, users: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.updates.ChannelDifference.channelDifference(flags: _1!, pts: _2!, timeout: _3, newMessages: _4!, otherUpdates: _5!, chats: _6!, users: _7!) } public static func parse_channelDifferenceEmpty(_ reader: BufferReader) -> ChannelDifference? { var _1: Int32? @@ -338,12 +479,10 @@ public extension Api.updates { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.updates.ChannelDifference.channelDifferenceEmpty(flags: _1!, pts: _2!, timeout: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.updates.ChannelDifference.channelDifferenceEmpty(flags: _1!, pts: _2!, timeout: _3) } public static func parse_channelDifferenceTooLong(_ reader: BufferReader) -> ChannelDifference? { var _1: Int32? @@ -372,12 +511,13 @@ public extension Api.updates { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.updates.ChannelDifference.channelDifferenceTooLong(flags: _1!, timeout: _2, dialog: _3!, messages: _4!, chats: _5!, users: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.updates.ChannelDifference.channelDifferenceTooLong(flags: _1!, timeout: _2, dialog: _3!, messages: _4!, chats: _5!, users: _6!) } } @@ -513,12 +653,13 @@ public extension Api.updates { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.updates.Difference.difference(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, state: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.updates.Difference.difference(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, state: _6!) } public static func parse_differenceEmpty(_ reader: BufferReader) -> Difference? { var _1: Int32? @@ -527,12 +668,9 @@ public extension Api.updates { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.updates.Difference.differenceEmpty(date: _1!, seq: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.updates.Difference.differenceEmpty(date: _1!, seq: _2!) } public static func parse_differenceSlice(_ reader: BufferReader) -> Difference? { var _1: [Api.Message]? @@ -565,23 +703,20 @@ public extension Api.updates { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.updates.Difference.differenceSlice(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, intermediateState: _6!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.updates.Difference.differenceSlice(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, intermediateState: _6!) } public static func parse_differenceTooLong(_ reader: BufferReader) -> Difference? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.updates.Difference.differenceTooLong(pts: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.updates.Difference.differenceTooLong(pts: _1!) } } @@ -628,12 +763,12 @@ public extension Api.updates { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.updates.State.state(pts: _1!, qts: _2!, date: _3!, seq: _4!, unreadCount: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.updates.State.state(pts: _1!, qts: _2!, date: _3!, seq: _4!, unreadCount: _5!) } } @@ -673,23 +808,15 @@ public extension Api.upload { var _1: Buffer? _1 = parseBytes(reader) let _c1 = _1 != nil - if _c1 { - return Api.upload.CdnFile.cdnFile(bytes: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.upload.CdnFile.cdnFile(bytes: _1!) } public static func parse_cdnFileReuploadNeeded(_ reader: BufferReader) -> CdnFile? { var _1: Buffer? _1 = parseBytes(reader) let _c1 = _1 != nil - if _c1 { - return Api.upload.CdnFile.cdnFileReuploadNeeded(requestToken: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.upload.CdnFile.cdnFileReuploadNeeded(requestToken: _1!) } } @@ -747,12 +874,10 @@ public extension Api.upload { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.upload.File.file(type: _1!, mtime: _2!, bytes: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.upload.File.file(type: _1!, mtime: _2!, bytes: _3!) } public static func parse_fileCdnRedirect(_ reader: BufferReader) -> File? { var _1: Int32? @@ -772,12 +897,12 @@ public extension Api.upload { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.upload.File.fileCdnRedirect(dcId: _1!, fileToken: _2!, encryptionKey: _3!, encryptionIv: _4!, fileHashes: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.upload.File.fileCdnRedirect(dcId: _1!, fileToken: _2!, encryptionKey: _3!, encryptionIv: _4!, fileHashes: _5!) } } @@ -826,12 +951,12 @@ public extension Api.upload { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.upload.WebFile.webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.upload.WebFile.webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!) } } @@ -881,23 +1006,16 @@ public extension Api.users { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.users.SavedMusic.savedMusic(count: _1!, documents: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.users.SavedMusic.savedMusic(count: _1!, documents: _2!) } public static func parse_savedMusicNotModified(_ reader: BufferReader) -> SavedMusic? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.users.SavedMusic.savedMusicNotModified(count: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.users.SavedMusic.savedMusicNotModified(count: _1!) } } @@ -950,12 +1068,10 @@ public extension Api.users { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.users.UserFull.userFull(fullUser: _1!, chats: _2!, users: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.users.UserFull.userFull(fullUser: _1!, chats: _2!, users: _3!) } } @@ -1006,12 +1122,8 @@ public extension Api.users { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil - if _c1 { - return Api.users.Users.users(users: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.users.Users.users(users: _1!) } public static func parse_usersSlice(_ reader: BufferReader) -> Users? { var _1: Int32? @@ -1022,12 +1134,9 @@ public extension Api.users { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.users.Users.usersSlice(count: _1!, users: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.users.Users.usersSlice(count: _1!, users: _2!) } } diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index d421b4d6..c9e087f8 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -6245,6 +6245,21 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func getEmojiGameInfo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-75592537) + + return (FunctionDescription(name: "messages.getEmojiGameInfo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGameInfo? in + let reader = BufferReader(buffer) + var result: Api.messages.EmojiGameInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameInfo + } + return result + }) + } +} public extension Api.functions.messages { static func getEmojiGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -8991,6 +9006,24 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func summarizeText(flags: Int32, peer: Api.InputPeer, id: Int32, toLang: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1656683294) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(toLang!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.summarizeText", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id)), ("toLang", String(describing: toLang))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.TextWithEntities? in + let reader = BufferReader(buffer) + var result: Api.TextWithEntities? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + return result + }) + } +} public extension Api.functions.messages { static func toggleBotInAttachMenu(flags: Int32, bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index fad4416f..33c86e99 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -24,12 +24,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.ChannelAdminLogEventsFilter.channelAdminLogEventsFilter(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelAdminLogEventsFilter.channelAdminLogEventsFilter(flags: _1!) } } @@ -75,12 +71,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelLocation.channelLocation(geoPoint: _1!, address: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelLocation.channelLocation(geoPoint: _1!, address: _2!) } public static func parse_channelLocationEmpty(_ reader: BufferReader) -> ChannelLocation? { return Api.ChannelLocation.channelLocationEmpty @@ -133,12 +126,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelMessagesFilter.channelMessagesFilter(flags: _1!, ranges: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChannelMessagesFilter.channelMessagesFilter(flags: _1!, ranges: _2!) } public static func parse_channelMessagesFilterEmpty(_ reader: BufferReader) -> ChannelMessagesFilter? { return Api.ChannelMessagesFilter.channelMessagesFilterEmpty @@ -246,12 +236,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.ChannelParticipant.channelParticipant(flags: _1!, userId: _2!, date: _3!, subscriptionUntilDate: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.ChannelParticipant.channelParticipant(flags: _1!, userId: _2!, date: _3!, subscriptionUntilDate: _4) } public static func parse_channelParticipantAdmin(_ reader: BufferReader) -> ChannelParticipant? { var _1: Int32? @@ -277,12 +266,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.ChannelParticipant.channelParticipantAdmin(flags: _1!, userId: _2!, inviterId: _3, promotedBy: _4!, date: _5!, adminRights: _6!, rank: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.ChannelParticipant.channelParticipantAdmin(flags: _1!, userId: _2!, inviterId: _3, promotedBy: _4!, date: _5!, adminRights: _6!, rank: _7) } public static func parse_channelParticipantBanned(_ reader: BufferReader) -> ChannelParticipant? { var _1: Int32? @@ -304,12 +295,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.ChannelParticipant.channelParticipantBanned(flags: _1!, peer: _2!, kickedBy: _3!, date: _4!, bannedRights: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.ChannelParticipant.channelParticipantBanned(flags: _1!, peer: _2!, kickedBy: _3!, date: _4!, bannedRights: _5!) } public static func parse_channelParticipantCreator(_ reader: BufferReader) -> ChannelParticipant? { var _1: Int32? @@ -326,12 +317,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.ChannelParticipant.channelParticipantCreator(flags: _1!, userId: _2!, adminRights: _3!, rank: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.ChannelParticipant.channelParticipantCreator(flags: _1!, userId: _2!, adminRights: _3!, rank: _4) } public static func parse_channelParticipantLeft(_ reader: BufferReader) -> ChannelParticipant? { var _1: Api.Peer? @@ -339,12 +329,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Peer } let _c1 = _1 != nil - if _c1 { - return Api.ChannelParticipant.channelParticipantLeft(peer: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelParticipant.channelParticipantLeft(peer: _1!) } public static func parse_channelParticipantSelf(_ reader: BufferReader) -> ChannelParticipant? { var _1: Int32? @@ -362,12 +348,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.ChannelParticipant.channelParticipantSelf(flags: _1!, userId: _2!, inviterId: _3!, date: _4!, subscriptionUntilDate: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.ChannelParticipant.channelParticipantSelf(flags: _1!, userId: _2!, inviterId: _3!, date: _4!, subscriptionUntilDate: _5) } } @@ -466,12 +452,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.ChannelParticipantsFilter.channelParticipantsBanned(q: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelParticipantsFilter.channelParticipantsBanned(q: _1!) } public static func parse_channelParticipantsBots(_ reader: BufferReader) -> ChannelParticipantsFilter? { return Api.ChannelParticipantsFilter.channelParticipantsBots @@ -480,23 +462,15 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.ChannelParticipantsFilter.channelParticipantsContacts(q: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelParticipantsFilter.channelParticipantsContacts(q: _1!) } public static func parse_channelParticipantsKicked(_ reader: BufferReader) -> ChannelParticipantsFilter? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.ChannelParticipantsFilter.channelParticipantsKicked(q: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelParticipantsFilter.channelParticipantsKicked(q: _1!) } public static func parse_channelParticipantsMentions(_ reader: BufferReader) -> ChannelParticipantsFilter? { var _1: Int32? @@ -508,12 +482,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.ChannelParticipantsFilter.channelParticipantsMentions(flags: _1!, q: _2, topMsgId: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.ChannelParticipantsFilter.channelParticipantsMentions(flags: _1!, q: _2, topMsgId: _3) } public static func parse_channelParticipantsRecent(_ reader: BufferReader) -> ChannelParticipantsFilter? { return Api.ChannelParticipantsFilter.channelParticipantsRecent @@ -522,12 +494,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.ChannelParticipantsFilter.channelParticipantsSearch(q: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChannelParticipantsFilter.channelParticipantsSearch(q: _1!) } } @@ -724,12 +692,30 @@ public extension Api { let _c21 = (Int(_2!) & Int(1 << 13) == 0) || _21 != nil let _c22 = (Int(_2!) & Int(1 << 14) == 0) || _22 != nil let _c23 = (Int(_2!) & Int(1 << 18) == 0) || _23 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 { - return Api.Chat.channel(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, title: _5!, username: _6, photo: _7!, date: _8!, restrictionReason: _9, adminRights: _10, bannedRights: _11, defaultBannedRights: _12, participantsCount: _13, usernames: _14, storiesMaxId: _15, color: _16, profileColor: _17, emojiStatus: _18, level: _19, subscriptionUntilDate: _20, botVerificationIcon: _21, sendPaidMessagesStars: _22, linkedMonoforumId: _23) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + if !_c20 { return nil } + if !_c21 { return nil } + if !_c22 { return nil } + if !_c23 { return nil } + return Api.Chat.channel(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, title: _5!, username: _6, photo: _7!, date: _8!, restrictionReason: _9, adminRights: _10, bannedRights: _11, defaultBannedRights: _12, participantsCount: _13, usernames: _14, storiesMaxId: _15, color: _16, profileColor: _17, emojiStatus: _18, level: _19, subscriptionUntilDate: _20, botVerificationIcon: _21, sendPaidMessagesStars: _22, linkedMonoforumId: _23) } public static func parse_channelForbidden(_ reader: BufferReader) -> Chat? { var _1: Int32? @@ -747,12 +733,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 16) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Chat.channelForbidden(flags: _1!, id: _2!, accessHash: _3!, title: _4!, untilDate: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.Chat.channelForbidden(flags: _1!, id: _2!, accessHash: _3!, title: _4!, untilDate: _5) } public static func parse_chat(_ reader: BufferReader) -> Chat? { var _1: Int32? @@ -793,23 +779,24 @@ public extension Api { let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 14) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 18) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.Chat.chat(flags: _1!, id: _2!, title: _3!, photo: _4!, participantsCount: _5!, date: _6!, version: _7!, migratedTo: _8, adminRights: _9, defaultBannedRights: _10) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + return Api.Chat.chat(flags: _1!, id: _2!, title: _3!, photo: _4!, participantsCount: _5!, date: _6!, version: _7!, migratedTo: _8, adminRights: _9, defaultBannedRights: _10) } public static func parse_chatEmpty(_ reader: BufferReader) -> Chat? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Chat.chatEmpty(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Chat.chatEmpty(id: _1!) } public static func parse_chatForbidden(_ reader: BufferReader) -> Chat? { var _1: Int64? @@ -818,12 +805,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Chat.chatForbidden(id: _1!, title: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Chat.chatForbidden(id: _1!, title: _2!) } } @@ -854,12 +838,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.ChatAdminRights.chatAdminRights(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChatAdminRights.chatAdminRights(flags: _1!) } } @@ -898,12 +878,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.ChatAdminWithInvites.chatAdminWithInvites(adminId: _1!, invitesCount: _2!, revokedInvitesCount: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.ChatAdminWithInvites.chatAdminWithInvites(adminId: _1!, invitesCount: _2!, revokedInvitesCount: _3!) } } @@ -938,12 +916,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChatBannedRights.chatBannedRights(flags: _1!, untilDate: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChatBannedRights.chatBannedRights(flags: _1!, untilDate: _2!) } } @@ -1238,12 +1213,54 @@ public extension Api { let _c45 = (Int(_2!) & Int(1 << 18) == 0) || _45 != nil let _c46 = (Int(_2!) & Int(1 << 21) == 0) || _46 != nil let _c47 = (Int(_2!) & Int(1 << 22) == 0) || _47 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 && _c41 && _c42 && _c43 && _c44 && _c45 && _c46 && _c47 { - return Api.ChatFull.channelFull(flags: _1!, flags2: _2!, id: _3!, about: _4!, participantsCount: _5, adminsCount: _6, kickedCount: _7, bannedCount: _8, onlineCount: _9, readInboxMaxId: _10!, readOutboxMaxId: _11!, unreadCount: _12!, chatPhoto: _13!, notifySettings: _14!, exportedInvite: _15, botInfo: _16!, migratedFromChatId: _17, migratedFromMaxId: _18, pinnedMsgId: _19, stickerset: _20, availableMinId: _21, folderId: _22, linkedChatId: _23, location: _24, slowmodeSeconds: _25, slowmodeNextSendDate: _26, statsDc: _27, pts: _28!, call: _29, ttlPeriod: _30, pendingSuggestions: _31, groupcallDefaultJoinAs: _32, themeEmoticon: _33, requestsPending: _34, recentRequesters: _35, defaultSendAs: _36, availableReactions: _37, reactionsLimit: _38, stories: _39, wallpaper: _40, boostsApplied: _41, boostsUnrestrict: _42, emojiset: _43, botVerification: _44, stargiftsCount: _45, sendPaidMessagesStars: _46, mainTab: _47) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + if !_c20 { return nil } + if !_c21 { return nil } + if !_c22 { return nil } + if !_c23 { return nil } + if !_c24 { return nil } + if !_c25 { return nil } + if !_c26 { return nil } + if !_c27 { return nil } + if !_c28 { return nil } + if !_c29 { return nil } + if !_c30 { return nil } + if !_c31 { return nil } + if !_c32 { return nil } + if !_c33 { return nil } + if !_c34 { return nil } + if !_c35 { return nil } + if !_c36 { return nil } + if !_c37 { return nil } + if !_c38 { return nil } + if !_c39 { return nil } + if !_c40 { return nil } + if !_c41 { return nil } + if !_c42 { return nil } + if !_c43 { return nil } + if !_c44 { return nil } + if !_c45 { return nil } + if !_c46 { return nil } + if !_c47 { return nil } + return Api.ChatFull.channelFull(flags: _1!, flags2: _2!, id: _3!, about: _4!, participantsCount: _5, adminsCount: _6, kickedCount: _7, bannedCount: _8, onlineCount: _9, readInboxMaxId: _10!, readOutboxMaxId: _11!, unreadCount: _12!, chatPhoto: _13!, notifySettings: _14!, exportedInvite: _15, botInfo: _16!, migratedFromChatId: _17, migratedFromMaxId: _18, pinnedMsgId: _19, stickerset: _20, availableMinId: _21, folderId: _22, linkedChatId: _23, location: _24, slowmodeSeconds: _25, slowmodeNextSendDate: _26, statsDc: _27, pts: _28!, call: _29, ttlPeriod: _30, pendingSuggestions: _31, groupcallDefaultJoinAs: _32, themeEmoticon: _33, requestsPending: _34, recentRequesters: _35, defaultSendAs: _36, availableReactions: _37, reactionsLimit: _38, stories: _39, wallpaper: _40, boostsApplied: _41, boostsUnrestrict: _42, emojiset: _43, botVerification: _44, stargiftsCount: _45, sendPaidMessagesStars: _46, mainTab: _47) } public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? { var _1: Int32? @@ -1318,12 +1335,25 @@ public extension Api { let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil let _c17 = (Int(_1!) & Int(1 << 18) == 0) || _17 != nil let _c18 = (Int(_1!) & Int(1 << 20) == 0) || _18 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 { - return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttlPeriod: _12, groupcallDefaultJoinAs: _13, themeEmoticon: _14, requestsPending: _15, recentRequesters: _16, availableReactions: _17, reactionsLimit: _18) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttlPeriod: _12, groupcallDefaultJoinAs: _13, themeEmoticon: _14, requestsPending: _15, recentRequesters: _16, availableReactions: _17, reactionsLimit: _18) } } @@ -1421,12 +1451,17 @@ public extension Api { let _c8 = (Int(_1!) & Int(1 << 10) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 12) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 13) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.ChatInvite.chatInvite(flags: _1!, title: _2!, about: _3, photo: _4!, participantsCount: _5!, participants: _6, color: _7!, subscriptionPricing: _8, subscriptionFormId: _9, botVerification: _10) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + return Api.ChatInvite.chatInvite(flags: _1!, title: _2!, about: _3, photo: _4!, participantsCount: _5!, participants: _6, color: _7!, subscriptionPricing: _8, subscriptionFormId: _9, botVerification: _10) } public static func parse_chatInviteAlready(_ reader: BufferReader) -> ChatInvite? { var _1: Api.Chat? @@ -1434,12 +1469,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Chat } let _c1 = _1 != nil - if _c1 { - return Api.ChatInvite.chatInviteAlready(chat: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChatInvite.chatInviteAlready(chat: _1!) } public static func parse_chatInvitePeek(_ reader: BufferReader) -> ChatInvite? { var _1: Api.Chat? @@ -1450,12 +1481,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChatInvite.chatInvitePeek(chat: _1!, expires: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChatInvite.chatInvitePeek(chat: _1!, expires: _2!) } } diff --git a/submodules/TelegramApi/Sources/Api5.swift b/submodules/TelegramApi/Sources/Api5.swift index 2ce916e3..87fba995 100644 --- a/submodules/TelegramApi/Sources/Api5.swift +++ b/submodules/TelegramApi/Sources/Api5.swift @@ -40,12 +40,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.ChatInviteImporter.chatInviteImporter(flags: _1!, userId: _2!, date: _3!, about: _4, approvedBy: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.ChatInviteImporter.chatInviteImporter(flags: _1!, userId: _2!, date: _3!, about: _4, approvedBy: _5) } } @@ -76,12 +76,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.ChatOnlines.chatOnlines(onlines: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChatOnlines.chatOnlines(onlines: _1!) } } @@ -140,12 +136,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.ChatParticipant.chatParticipant(userId: _1!, inviterId: _2!, date: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.ChatParticipant.chatParticipant(userId: _1!, inviterId: _2!, date: _3!) } public static func parse_chatParticipantAdmin(_ reader: BufferReader) -> ChatParticipant? { var _1: Int64? @@ -157,23 +151,17 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.ChatParticipant.chatParticipantAdmin(userId: _1!, inviterId: _2!, date: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.ChatParticipant.chatParticipantAdmin(userId: _1!, inviterId: _2!, date: _3!) } public static func parse_chatParticipantCreator(_ reader: BufferReader) -> ChatParticipant? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.ChatParticipant.chatParticipantCreator(userId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChatParticipant.chatParticipantCreator(userId: _1!) } } @@ -229,12 +217,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.ChatParticipants.chatParticipants(chatId: _1!, participants: _2!, version: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.ChatParticipants.chatParticipants(chatId: _1!, participants: _2!, version: _3!) } public static func parse_chatParticipantsForbidden(_ reader: BufferReader) -> ChatParticipants? { var _1: Int32? @@ -248,12 +234,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.ChatParticipants.chatParticipantsForbidden(flags: _1!, chatId: _2!, selfParticipant: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.ChatParticipants.chatParticipantsForbidden(flags: _1!, chatId: _2!, selfParticipant: _3) } } @@ -305,12 +289,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.ChatPhoto.chatPhoto(flags: _1!, photoId: _2!, strippedThumb: _3, dcId: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.ChatPhoto.chatPhoto(flags: _1!, photoId: _2!, strippedThumb: _3, dcId: _4!) } public static func parse_chatPhotoEmpty(_ reader: BufferReader) -> ChatPhoto? { return Api.ChatPhoto.chatPhotoEmpty @@ -366,12 +349,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.ChatReactions.chatReactionsAll(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChatReactions.chatReactionsAll(flags: _1!) } public static func parse_chatReactionsNone(_ reader: BufferReader) -> ChatReactions? { return Api.ChatReactions.chatReactionsNone @@ -382,12 +361,8 @@ public extension Api { _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) } let _c1 = _1 != nil - if _c1 { - return Api.ChatReactions.chatReactionsSome(reactions: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChatReactions.chatReactionsSome(reactions: _1!) } } @@ -432,12 +407,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.ChatTheme.chatTheme(emoticon: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ChatTheme.chatTheme(emoticon: _1!) } public static func parse_chatThemeUniqueGift(_ reader: BufferReader) -> ChatTheme? { var _1: Api.StarGift? @@ -450,12 +421,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChatTheme.chatThemeUniqueGift(gift: _1!, themeSettings: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ChatTheme.chatThemeUniqueGift(gift: _1!, themeSettings: _2!) } } @@ -506,12 +474,11 @@ public extension Api { let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.CodeSettings.codeSettings(flags: _1!, logoutTokens: _2, token: _3, appSandbox: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.CodeSettings.codeSettings(flags: _1!, logoutTokens: _2, token: _3, appSandbox: _4) } } @@ -720,12 +687,50 @@ public extension Api { let _c41 = (Int(_1!) & Int(1 << 2) == 0) || _41 != nil let _c42 = (Int(_1!) & Int(1 << 15) == 0) || _42 != nil let _c43 = (Int(_1!) & Int(1 << 16) == 0) || _43 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 && _c41 && _c42 && _c43 { - return Api.Config.config(flags: _1!, date: _2!, expires: _3!, testMode: _4!, thisDc: _5!, dcOptions: _6!, dcTxtDomainName: _7!, chatSizeMax: _8!, megagroupSizeMax: _9!, forwardedCountMax: _10!, onlineUpdatePeriodMs: _11!, offlineBlurTimeoutMs: _12!, offlineIdleTimeoutMs: _13!, onlineCloudTimeoutMs: _14!, notifyCloudDelayMs: _15!, notifyDefaultDelayMs: _16!, pushChatPeriodMs: _17!, pushChatLimit: _18!, editTimeLimit: _19!, revokeTimeLimit: _20!, revokePmTimeLimit: _21!, ratingEDecay: _22!, stickersRecentLimit: _23!, channelsReadMediaPeriod: _24!, tmpSessions: _25, callReceiveTimeoutMs: _26!, callRingTimeoutMs: _27!, callConnectTimeoutMs: _28!, callPacketTimeoutMs: _29!, meUrlPrefix: _30!, autoupdateUrlPrefix: _31, gifSearchUsername: _32, venueSearchUsername: _33, imgSearchUsername: _34, staticMapsProvider: _35, captionLengthMax: _36!, messageLengthMax: _37!, webfileDcId: _38!, suggestedLangCode: _39, langPackVersion: _40, baseLangPackVersion: _41, reactionsDefault: _42, autologinToken: _43) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + if !_c17 { return nil } + if !_c18 { return nil } + if !_c19 { return nil } + if !_c20 { return nil } + if !_c21 { return nil } + if !_c22 { return nil } + if !_c23 { return nil } + if !_c24 { return nil } + if !_c25 { return nil } + if !_c26 { return nil } + if !_c27 { return nil } + if !_c28 { return nil } + if !_c29 { return nil } + if !_c30 { return nil } + if !_c31 { return nil } + if !_c32 { return nil } + if !_c33 { return nil } + if !_c34 { return nil } + if !_c35 { return nil } + if !_c36 { return nil } + if !_c37 { return nil } + if !_c38 { return nil } + if !_c39 { return nil } + if !_c40 { return nil } + if !_c41 { return nil } + if !_c42 { return nil } + if !_c43 { return nil } + return Api.Config.config(flags: _1!, date: _2!, expires: _3!, testMode: _4!, thisDc: _5!, dcOptions: _6!, dcTxtDomainName: _7!, chatSizeMax: _8!, megagroupSizeMax: _9!, forwardedCountMax: _10!, onlineUpdatePeriodMs: _11!, offlineBlurTimeoutMs: _12!, offlineIdleTimeoutMs: _13!, onlineCloudTimeoutMs: _14!, notifyCloudDelayMs: _15!, notifyDefaultDelayMs: _16!, pushChatPeriodMs: _17!, pushChatLimit: _18!, editTimeLimit: _19!, revokeTimeLimit: _20!, revokePmTimeLimit: _21!, ratingEDecay: _22!, stickersRecentLimit: _23!, channelsReadMediaPeriod: _24!, tmpSessions: _25, callReceiveTimeoutMs: _26!, callRingTimeoutMs: _27!, callConnectTimeoutMs: _28!, callPacketTimeoutMs: _29!, meUrlPrefix: _30!, autoupdateUrlPrefix: _31, gifSearchUsername: _32, venueSearchUsername: _33, imgSearchUsername: _34, staticMapsProvider: _35, captionLengthMax: _36!, messageLengthMax: _37!, webfileDcId: _38!, suggestedLangCode: _39, langPackVersion: _40, baseLangPackVersion: _41, reactionsDefault: _42, autologinToken: _43) } } @@ -772,12 +777,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.ConnectedBot.connectedBot(flags: _1!, botId: _2!, recipients: _3!, rights: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.ConnectedBot.connectedBot(flags: _1!, botId: _2!, recipients: _3!, rights: _4!) } } @@ -836,12 +840,15 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.ConnectedBotStarRef.connectedBotStarRef(flags: _1!, url: _2!, date: _3!, botId: _4!, commissionPermille: _5!, durationMonths: _6, participants: _7!, revenue: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.ConnectedBotStarRef.connectedBotStarRef(flags: _1!, url: _2!, date: _3!, botId: _4!, commissionPermille: _5!, durationMonths: _6, participants: _7!, revenue: _8!) } } @@ -878,12 +885,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Contact.contact(userId: _1!, mutual: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.Contact.contact(userId: _1!, mutual: _2!) } } @@ -920,12 +924,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ContactBirthday.contactBirthday(contactId: _1!, birthday: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ContactBirthday.contactBirthday(contactId: _1!, birthday: _2!) } } @@ -962,12 +963,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ContactStatus.contactStatus(userId: _1!, status: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ContactStatus.contactStatus(userId: _1!, status: _2!) } } @@ -998,12 +996,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.DataJSON.dataJSON(data: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.DataJSON.dataJSON(data: _1!) } } @@ -1050,12 +1044,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 10) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.DcOption.dcOption(flags: _1!, id: _2!, ipAddress: _3!, port: _4!, secret: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.DcOption.dcOption(flags: _1!, id: _2!, ipAddress: _3!, port: _4!, secret: _5) } } @@ -1086,12 +1080,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.DefaultHistoryTTL.defaultHistoryTTL(period: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.DefaultHistoryTTL.defaultHistoryTTL(period: _1!) } } @@ -1192,12 +1182,20 @@ public extension Api { let _c11 = (Int(_1!) & Int(1 << 1) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.Dialog.dialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadMentionsCount: _7!, unreadReactionsCount: _8!, notifySettings: _9!, pts: _10, draft: _11, folderId: _12, ttlPeriod: _13) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + return Api.Dialog.dialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadMentionsCount: _7!, unreadReactionsCount: _8!, notifySettings: _9!, pts: _10, draft: _11, folderId: _12, ttlPeriod: _13) } public static func parse_dialogFolder(_ reader: BufferReader) -> Dialog? { var _1: Int32? @@ -1228,12 +1226,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Dialog.dialogFolder(flags: _1!, folder: _2!, peer: _3!, topMessage: _4!, unreadMutedPeersCount: _5!, unreadUnmutedPeersCount: _6!, unreadMutedMessagesCount: _7!, unreadUnmutedMessagesCount: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.Dialog.dialogFolder(flags: _1!, folder: _2!, peer: _3!, topMessage: _4!, unreadMutedPeersCount: _5!, unreadUnmutedPeersCount: _6!, unreadMutedMessagesCount: _7!, unreadUnmutedMessagesCount: _8!) } } @@ -1344,12 +1345,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.DialogFilter.dialogFilter(flags: _1!, id: _2!, title: _3!, emoticon: _4, color: _5, pinnedPeers: _6!, includePeers: _7!, excludePeers: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.DialogFilter.dialogFilter(flags: _1!, id: _2!, title: _3!, emoticon: _4, color: _5, pinnedPeers: _6!, includePeers: _7!, excludePeers: _8!) } public static func parse_dialogFilterChatlist(_ reader: BufferReader) -> DialogFilter? { var _1: Int32? @@ -1379,12 +1383,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 27) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.DialogFilter.dialogFilterChatlist(flags: _1!, id: _2!, title: _3!, emoticon: _4, color: _5, pinnedPeers: _6!, includePeers: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.DialogFilter.dialogFilterChatlist(flags: _1!, id: _2!, title: _3!, emoticon: _4, color: _5, pinnedPeers: _6!, includePeers: _7!) } public static func parse_dialogFilterDefault(_ reader: BufferReader) -> DialogFilter? { return Api.DialogFilter.dialogFilterDefault @@ -1424,12 +1430,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.DialogFilterSuggested.dialogFilterSuggested(filter: _1!, description: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.DialogFilterSuggested.dialogFilterSuggested(filter: _1!, description: _2!) } } @@ -1471,23 +1474,15 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.Peer } let _c1 = _1 != nil - if _c1 { - return Api.DialogPeer.dialogPeer(peer: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.DialogPeer.dialogPeer(peer: _1!) } public static func parse_dialogPeerFolder(_ reader: BufferReader) -> DialogPeer? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.DialogPeer.dialogPeerFolder(folderId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.DialogPeer.dialogPeerFolder(folderId: _1!) } } diff --git a/submodules/TelegramApi/Sources/Api6.swift b/submodules/TelegramApi/Sources/Api6.swift index ca7545a0..5cb12927 100644 --- a/submodules/TelegramApi/Sources/Api6.swift +++ b/submodules/TelegramApi/Sources/Api6.swift @@ -24,12 +24,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.DisallowedGiftsSettings.disallowedGiftsSettings(flags: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.DisallowedGiftsSettings.disallowedGiftsSettings(flags: _1!) } } @@ -127,23 +123,25 @@ public extension Api { let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil let _c10 = _10 != nil let _c11 = _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.Document.document(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, mimeType: _6!, size: _7!, thumbs: _8, videoThumbs: _9, dcId: _10!, attributes: _11!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + return Api.Document.document(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, mimeType: _6!, size: _7!, thumbs: _8, videoThumbs: _9, dcId: _10!, attributes: _11!) } public static func parse_documentEmpty(_ reader: BufferReader) -> Document? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil - if _c1 { - return Api.Document.documentEmpty(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.Document.documentEmpty(id: _1!) } } @@ -268,12 +266,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) } public static func parse_documentAttributeCustomEmoji(_ reader: BufferReader) -> DocumentAttribute? { var _1: Int32? @@ -287,23 +285,17 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.DocumentAttribute.documentAttributeCustomEmoji(flags: _1!, alt: _2!, stickerset: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.DocumentAttribute.documentAttributeCustomEmoji(flags: _1!, alt: _2!, stickerset: _3!) } public static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.DocumentAttribute.documentAttributeFilename(fileName: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.DocumentAttribute.documentAttributeFilename(fileName: _1!) } public static func parse_documentAttributeHasStickers(_ reader: BufferReader) -> DocumentAttribute? { return Api.DocumentAttribute.documentAttributeHasStickers @@ -315,12 +307,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) } public static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { var _1: Int32? @@ -339,12 +328,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.DocumentAttribute.documentAttributeSticker(flags: _1!, alt: _2!, stickerset: _3!, maskCoords: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.DocumentAttribute.documentAttributeSticker(flags: _1!, alt: _2!, stickerset: _3!, maskCoords: _4) } public static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { var _1: Int32? @@ -368,12 +356,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!, preloadPrefixSize: _5, videoStartTs: _6, videoCodec: _7) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!, preloadPrefixSize: _5, videoStartTs: _6, videoCodec: _7) } } @@ -454,12 +444,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 8) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.DraftMessage.draftMessage(flags: _1!, replyTo: _2, message: _3!, entities: _4, media: _5, date: _6!, effect: _7, suggestedPost: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.DraftMessage.draftMessage(flags: _1!, replyTo: _2, message: _3!, entities: _4, media: _5, date: _6!, effect: _7, suggestedPost: _8) } public static func parse_draftMessageEmpty(_ reader: BufferReader) -> DraftMessage? { var _1: Int32? @@ -468,12 +461,9 @@ public extension Api { if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.DraftMessage.draftMessageEmpty(flags: _1!, date: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.DraftMessage.draftMessageEmpty(flags: _1!, date: _2) } } @@ -522,34 +512,22 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.EmailVerification.emailVerificationApple(token: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.EmailVerification.emailVerificationApple(token: _1!) } public static func parse_emailVerificationCode(_ reader: BufferReader) -> EmailVerification? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.EmailVerification.emailVerificationCode(code: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.EmailVerification.emailVerificationCode(code: _1!) } public static func parse_emailVerificationGoogle(_ reader: BufferReader) -> EmailVerification? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.EmailVerification.emailVerificationGoogle(token: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.EmailVerification.emailVerificationGoogle(token: _1!) } } @@ -605,12 +583,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.EmailVerifyPurpose.emailVerifyPurposeLoginSetup(phoneNumber: _1!, phoneCodeHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.EmailVerifyPurpose.emailVerifyPurposeLoginSetup(phoneNumber: _1!, phoneCodeHash: _2!) } public static func parse_emailVerifyPurposePassport(_ reader: BufferReader) -> EmailVerifyPurpose? { return Api.EmailVerifyPurpose.emailVerifyPurposePassport @@ -683,12 +658,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.EmojiGroup.emojiGroup(title: _1!, iconEmojiId: _2!, emoticons: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.EmojiGroup.emojiGroup(title: _1!, iconEmojiId: _2!, emoticons: _3!) } public static func parse_emojiGroupGreeting(_ reader: BufferReader) -> EmojiGroup? { var _1: String? @@ -702,12 +675,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.EmojiGroup.emojiGroupGreeting(title: _1!, iconEmojiId: _2!, emoticons: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.EmojiGroup.emojiGroupGreeting(title: _1!, iconEmojiId: _2!, emoticons: _3!) } public static func parse_emojiGroupPremium(_ reader: BufferReader) -> EmojiGroup? { var _1: String? @@ -716,12 +687,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.EmojiGroup.emojiGroupPremium(title: _1!, iconEmojiId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.EmojiGroup.emojiGroupPremium(title: _1!, iconEmojiId: _2!) } } @@ -776,12 +744,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.EmojiKeyword.emojiKeyword(keyword: _1!, emoticons: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.EmojiKeyword.emojiKeyword(keyword: _1!, emoticons: _2!) } public static func parse_emojiKeywordDeleted(_ reader: BufferReader) -> EmojiKeyword? { var _1: String? @@ -792,12 +757,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.EmojiKeyword.emojiKeywordDeleted(keyword: _1!, emoticons: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.EmojiKeyword.emojiKeywordDeleted(keyword: _1!, emoticons: _2!) } } @@ -846,12 +808,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.EmojiKeywordsDifference.emojiKeywordsDifference(langCode: _1!, fromVersion: _2!, version: _3!, keywords: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.EmojiKeywordsDifference.emojiKeywordsDifference(langCode: _1!, fromVersion: _2!, version: _3!, keywords: _4!) } } @@ -882,12 +843,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.EmojiLanguage.emojiLanguage(langCode: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.EmojiLanguage.emojiLanguage(langCode: _1!) } } @@ -937,12 +894,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.EmojiList.emojiList(hash: _1!, documentId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.EmojiList.emojiList(hash: _1!, documentId: _2!) } public static func parse_emojiListNotModified(_ reader: BufferReader) -> EmojiList? { return Api.EmojiList.emojiListNotModified @@ -1023,12 +977,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.EmojiStatus.emojiStatus(flags: _1!, documentId: _2!, until: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.EmojiStatus.emojiStatus(flags: _1!, documentId: _2!, until: _3) } public static func parse_emojiStatusCollectible(_ reader: BufferReader) -> EmojiStatus? { var _1: Int32? @@ -1064,12 +1016,18 @@ public extension Api { let _c9 = _9 != nil let _c10 = _10 != nil let _c11 = (Int(_1!) & Int(1 << 0) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.EmojiStatus.emojiStatusCollectible(flags: _1!, collectibleId: _2!, documentId: _3!, title: _4!, slug: _5!, patternDocumentId: _6!, centerColor: _7!, edgeColor: _8!, patternColor: _9!, textColor: _10!, until: _11) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + return Api.EmojiStatus.emojiStatusCollectible(flags: _1!, collectibleId: _2!, documentId: _3!, title: _4!, slug: _5!, patternDocumentId: _6!, centerColor: _7!, edgeColor: _8!, patternColor: _9!, textColor: _10!, until: _11) } public static func parse_emojiStatusEmpty(_ reader: BufferReader) -> EmojiStatus? { return Api.EmojiStatus.emojiStatusEmpty @@ -1084,12 +1042,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.EmojiStatus.inputEmojiStatusCollectible(flags: _1!, collectibleId: _2!, until: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.EmojiStatus.inputEmojiStatusCollectible(flags: _1!, collectibleId: _2!, until: _3) } } diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index 1ad2e7de..2a85bd69 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -24,12 +24,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.EmojiURL.emojiURL(url: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.EmojiURL.emojiURL(url: _1!) } } @@ -132,12 +128,14 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.EncryptedChat.encryptedChat(id: _1!, accessHash: _2!, date: _3!, adminId: _4!, participantId: _5!, gAOrB: _6!, keyFingerprint: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.EncryptedChat.encryptedChat(id: _1!, accessHash: _2!, date: _3!, adminId: _4!, participantId: _5!, gAOrB: _6!, keyFingerprint: _7!) } public static func parse_encryptedChatDiscarded(_ reader: BufferReader) -> EncryptedChat? { var _1: Int32? @@ -146,23 +144,16 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.EncryptedChat.encryptedChatDiscarded(flags: _1!, id: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.EncryptedChat.encryptedChatDiscarded(flags: _1!, id: _2!) } public static func parse_encryptedChatEmpty(_ reader: BufferReader) -> EncryptedChat? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.EncryptedChat.encryptedChatEmpty(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.EncryptedChat.encryptedChatEmpty(id: _1!) } public static func parse_encryptedChatRequested(_ reader: BufferReader) -> EncryptedChat? { var _1: Int32? @@ -189,12 +180,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.EncryptedChat.encryptedChatRequested(flags: _1!, folderId: _2, id: _3!, accessHash: _4!, date: _5!, adminId: _6!, participantId: _7!, gA: _8!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.EncryptedChat.encryptedChatRequested(flags: _1!, folderId: _2, id: _3!, accessHash: _4!, date: _5!, adminId: _6!, participantId: _7!, gA: _8!) } public static func parse_encryptedChatWaiting(_ reader: BufferReader) -> EncryptedChat? { var _1: Int32? @@ -212,12 +206,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.EncryptedChat.encryptedChatWaiting(id: _1!, accessHash: _2!, date: _3!, adminId: _4!, participantId: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.EncryptedChat.encryptedChatWaiting(id: _1!, accessHash: _2!, date: _3!, adminId: _4!, participantId: _5!) } } @@ -273,12 +267,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.EncryptedFile.encryptedFile(id: _1!, accessHash: _2!, size: _3!, dcId: _4!, keyFingerprint: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.EncryptedFile.encryptedFile(id: _1!, accessHash: _2!, size: _3!, dcId: _4!, keyFingerprint: _5!) } public static func parse_encryptedFileEmpty(_ reader: BufferReader) -> EncryptedFile? { return Api.EncryptedFile.encryptedFileEmpty @@ -342,12 +336,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.EncryptedMessage.encryptedMessage(randomId: _1!, chatId: _2!, date: _3!, bytes: _4!, file: _5!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.EncryptedMessage.encryptedMessage(randomId: _1!, chatId: _2!, date: _3!, bytes: _4!, file: _5!) } public static func parse_encryptedMessageService(_ reader: BufferReader) -> EncryptedMessage? { var _1: Int64? @@ -362,12 +356,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.EncryptedMessage.encryptedMessageService(randomId: _1!, chatId: _2!, date: _3!, bytes: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.EncryptedMessage.encryptedMessageService(randomId: _1!, chatId: _2!, date: _3!, bytes: _4!) } } @@ -453,12 +446,19 @@ public extension Api { let _c10 = (Int(_1!) & Int(1 << 10) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 8) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 9) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.ExportedChatInvite.chatInviteExported(flags: _1!, link: _2!, adminId: _3!, date: _4!, startDate: _5, expireDate: _6, usageLimit: _7, usage: _8, requested: _9, subscriptionExpired: _10, title: _11, subscriptionPricing: _12) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + return Api.ExportedChatInvite.chatInviteExported(flags: _1!, link: _2!, adminId: _3!, date: _4!, startDate: _5, expireDate: _6, usageLimit: _7, usage: _8, requested: _9, subscriptionExpired: _10, title: _11, subscriptionPricing: _12) } public static func parse_chatInvitePublicJoinRequests(_ reader: BufferReader) -> ExportedChatInvite? { return Api.ExportedChatInvite.chatInvitePublicJoinRequests @@ -510,12 +510,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.ExportedChatlistInvite.exportedChatlistInvite(flags: _1!, title: _2!, url: _3!, peers: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.ExportedChatlistInvite.exportedChatlistInvite(flags: _1!, title: _2!, url: _3!, peers: _4!) } } @@ -550,12 +549,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ExportedContactToken.exportedContactToken(url: _1!, expires: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ExportedContactToken.exportedContactToken(url: _1!, expires: _2!) } } @@ -590,12 +586,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ExportedMessageLink.exportedMessageLink(link: _1!, html: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ExportedMessageLink.exportedMessageLink(link: _1!, html: _2!) } } @@ -626,12 +619,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.ExportedStoryLink.exportedStoryLink(link: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ExportedStoryLink.exportedStoryLink(link: _1!) } } @@ -676,12 +665,11 @@ public extension Api { let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.FactCheck.factCheck(flags: _1!, country: _2, text: _3, hash: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.FactCheck.factCheck(flags: _1!, country: _2, text: _3, hash: _4!) } } @@ -720,12 +708,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.FileHash.fileHash(offset: _1!, limit: _2!, hash: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.FileHash.fileHash(offset: _1!, limit: _2!, hash: _3!) } } @@ -770,12 +756,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Folder.folder(flags: _1!, id: _2!, title: _3!, photo: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.Folder.folder(flags: _1!, id: _2!, title: _3!, photo: _4) } } @@ -812,12 +797,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.FolderPeer.folderPeer(peer: _1!, folderId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.FolderPeer.folderPeer(peer: _1!, folderId: _2!) } } @@ -925,23 +907,30 @@ public extension Api { let _c14 = _14 != nil let _c15 = _15 != nil let _c16 = (Int(_1!) & Int(1 << 4) == 0) || _16 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 { - return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, peer: _4!, title: _5!, iconColor: _6!, iconEmojiId: _7, topMessage: _8!, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, unreadMentionsCount: _12!, unreadReactionsCount: _13!, fromId: _14!, notifySettings: _15!, draft: _16) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + if !_c15 { return nil } + if !_c16 { return nil } + return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, peer: _4!, title: _5!, iconColor: _6!, iconEmojiId: _7, topMessage: _8!, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, unreadMentionsCount: _12!, unreadReactionsCount: _13!, fromId: _14!, notifySettings: _15!, draft: _16) } public static func parse_forumTopicDeleted(_ reader: BufferReader) -> ForumTopic? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.ForumTopic.forumTopicDeleted(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.ForumTopic.forumTopicDeleted(id: _1!) } } @@ -980,12 +969,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.FoundStory.foundStory(peer: _1!, story: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.FoundStory.foundStory(peer: _1!, story: _2!) } } @@ -1048,12 +1034,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Game.game(flags: _1!, id: _2!, accessHash: _3!, shortName: _4!, title: _5!, description: _6!, photo: _7!, document: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.Game.game(flags: _1!, id: _2!, accessHash: _3!, shortName: _4!, title: _5!, description: _6!, photo: _7!, document: _8) } } @@ -1109,12 +1098,12 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.GeoPoint.geoPoint(flags: _1!, long: _2!, lat: _3!, accessHash: _4!, accuracyRadius: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.GeoPoint.geoPoint(flags: _1!, long: _2!, lat: _3!, accessHash: _4!, accuracyRadius: _5) } public static func parse_geoPointEmpty(_ reader: BufferReader) -> GeoPoint? { return Api.GeoPoint.geoPointEmpty @@ -1164,12 +1153,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.GeoPointAddress.geoPointAddress(flags: _1!, countryIso2: _2!, state: _3, city: _4, street: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.GeoPointAddress.geoPointAddress(flags: _1!, countryIso2: _2!, state: _3, city: _4, street: _5) } } @@ -1210,12 +1199,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 5) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 6) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.GlobalPrivacySettings.globalPrivacySettings(flags: _1!, noncontactPeersPaidStars: _2, disallowedGifts: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.GlobalPrivacySettings.globalPrivacySettings(flags: _1!, noncontactPeersPaidStars: _2, disallowedGifts: _3) } } @@ -1311,12 +1298,21 @@ public extension Api { let _c12 = (Int(_1!) & Int(1 << 16) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 20) == 0) || _13 != nil let _c14 = (Int(_1!) & Int(1 << 21) == 0) || _14 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { - return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, unmutedVideoCount: _9, unmutedVideoLimit: _10!, version: _11!, inviteLink: _12, sendPaidMessagesStars: _13, defaultSendAs: _14) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + if !_c12 { return nil } + if !_c13 { return nil } + if !_c14 { return nil } + return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, unmutedVideoCount: _9, unmutedVideoLimit: _10!, version: _11!, inviteLink: _12, sendPaidMessagesStars: _13, defaultSendAs: _14) } public static func parse_groupCallDiscarded(_ reader: BufferReader) -> GroupCall? { var _1: Int64? @@ -1328,12 +1324,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.GroupCall.groupCallDiscarded(id: _1!, accessHash: _2!, duration: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.GroupCall.groupCallDiscarded(id: _1!, accessHash: _2!, duration: _3!) } } @@ -1374,12 +1368,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.GroupCallDonor.groupCallDonor(flags: _1!, peerId: _2, stars: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.GroupCallDonor.groupCallDonor(flags: _1!, peerId: _2, stars: _3!) } } @@ -1434,12 +1426,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.GroupCallMessage.groupCallMessage(flags: _1!, id: _2!, fromId: _3!, date: _4!, message: _5!, paidMessageStars: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.GroupCallMessage.groupCallMessage(flags: _1!, id: _2!, fromId: _3!, date: _4!, message: _5!, paidMessageStars: _6) } } diff --git a/submodules/TelegramApi/Sources/Api8.swift b/submodules/TelegramApi/Sources/Api8.swift index 0b617cce..78862824 100644 --- a/submodules/TelegramApi/Sources/Api8.swift +++ b/submodules/TelegramApi/Sources/Api8.swift @@ -70,12 +70,18 @@ public extension Api { let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil let _c11 = (Int(_1!) & Int(1 << 16) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, peer: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, about: _7, raiseHandRating: _8, video: _9, presentation: _10, paidStarsTotal: _11) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + if !_c10 { return nil } + if !_c11 { return nil } + return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, peer: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, about: _7, raiseHandRating: _8, video: _9, presentation: _10, paidStarsTotal: _11) } } @@ -124,12 +130,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.GroupCallParticipantVideo.groupCallParticipantVideo(flags: _1!, endpoint: _2!, sourceGroups: _3!, audioSource: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.GroupCallParticipantVideo.groupCallParticipantVideo(flags: _1!, endpoint: _2!, sourceGroups: _3!, audioSource: _4) } } @@ -170,12 +175,9 @@ public extension Api { } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.GroupCallParticipantVideoSourceGroup.groupCallParticipantVideoSourceGroup(semantics: _1!, sources: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.GroupCallParticipantVideoSourceGroup.groupCallParticipantVideoSourceGroup(semantics: _1!, sources: _2!) } } @@ -214,12 +216,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.GroupCallStreamChannel.groupCallStreamChannel(channel: _1!, scale: _2!, lastTimestampMs: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.GroupCallStreamChannel.groupCallStreamChannel(channel: _1!, scale: _2!, lastTimestampMs: _3!) } } @@ -258,12 +258,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.HighScore.highScore(pos: _1!, userId: _2!, score: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.HighScore.highScore(pos: _1!, userId: _2!, score: _3!) } } @@ -298,12 +296,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ImportedContact.importedContact(userId: _1!, clientId: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.ImportedContact.importedContact(userId: _1!, clientId: _2!) } } @@ -338,12 +333,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InlineBotSwitchPM.inlineBotSwitchPM(text: _1!, startParam: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InlineBotSwitchPM.inlineBotSwitchPM(text: _1!, startParam: _2!) } } @@ -378,12 +370,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InlineBotWebView.inlineBotWebView(text: _1!, url: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InlineBotWebView.inlineBotWebView(text: _1!, url: _2!) } } @@ -516,12 +505,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputAppEvent.inputAppEvent(time: _1!, type: _2!, peer: _3!, data: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputAppEvent.inputAppEvent(time: _1!, type: _2!, peer: _3!, data: _4!) } } @@ -566,12 +554,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputBotApp.inputBotAppID(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputBotApp.inputBotAppID(id: _1!, accessHash: _2!) } public static func parse_inputBotAppShortName(_ reader: BufferReader) -> InputBotApp? { var _1: Api.InputUser? @@ -582,12 +567,9 @@ public extension Api { _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputBotApp.inputBotAppShortName(botId: _1!, shortName: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputBotApp.inputBotAppShortName(botId: _1!, shortName: _2!) } } @@ -734,12 +716,9 @@ public extension Api { } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil - if _c1 && _c2 { - return Api.InputBotInlineMessage.inputBotInlineMessageGame(flags: _1!, replyMarkup: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputBotInlineMessage.inputBotInlineMessageGame(flags: _1!, replyMarkup: _2) } public static func parse_inputBotInlineMessageMediaAuto(_ reader: BufferReader) -> InputBotInlineMessage? { var _1: Int32? @@ -758,12 +737,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputBotInlineMessage.inputBotInlineMessageMediaAuto(flags: _1!, message: _2!, entities: _3, replyMarkup: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputBotInlineMessage.inputBotInlineMessageMediaAuto(flags: _1!, message: _2!, entities: _3, replyMarkup: _4) } public static func parse_inputBotInlineMessageMediaContact(_ reader: BufferReader) -> InputBotInlineMessage? { var _1: Int32? @@ -786,12 +764,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputBotInlineMessage.inputBotInlineMessageMediaContact(flags: _1!, phoneNumber: _2!, firstName: _3!, lastName: _4!, vcard: _5!, replyMarkup: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.InputBotInlineMessage.inputBotInlineMessageMediaContact(flags: _1!, phoneNumber: _2!, firstName: _3!, lastName: _4!, vcard: _5!, replyMarkup: _6) } public static func parse_inputBotInlineMessageMediaGeo(_ reader: BufferReader) -> InputBotInlineMessage? { var _1: Int32? @@ -816,12 +795,13 @@ public extension Api { let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputBotInlineMessage.inputBotInlineMessageMediaGeo(flags: _1!, geoPoint: _2!, heading: _3, period: _4, proximityNotificationRadius: _5, replyMarkup: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.InputBotInlineMessage.inputBotInlineMessageMediaGeo(flags: _1!, geoPoint: _2!, heading: _3, period: _4, proximityNotificationRadius: _5, replyMarkup: _6) } public static func parse_inputBotInlineMessageMediaInvoice(_ reader: BufferReader) -> InputBotInlineMessage? { var _1: Int32? @@ -859,12 +839,16 @@ public extension Api { let _c7 = _7 != nil let _c8 = _8 != nil let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.InputBotInlineMessage.inputBotInlineMessageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, invoice: _5!, payload: _6!, provider: _7!, providerData: _8!, replyMarkup: _9) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.InputBotInlineMessage.inputBotInlineMessageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, invoice: _5!, payload: _6!, provider: _7!, providerData: _8!, replyMarkup: _9) } public static func parse_inputBotInlineMessageMediaVenue(_ reader: BufferReader) -> InputBotInlineMessage? { var _1: Int32? @@ -895,12 +879,15 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.InputBotInlineMessage.inputBotInlineMessageMediaVenue(flags: _1!, geoPoint: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!, venueType: _7!, replyMarkup: _8) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + return Api.InputBotInlineMessage.inputBotInlineMessageMediaVenue(flags: _1!, geoPoint: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!, venueType: _7!, replyMarkup: _8) } public static func parse_inputBotInlineMessageMediaWebPage(_ reader: BufferReader) -> InputBotInlineMessage? { var _1: Int32? @@ -922,12 +909,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputBotInlineMessage.inputBotInlineMessageMediaWebPage(flags: _1!, message: _2!, entities: _3, url: _4!, replyMarkup: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputBotInlineMessage.inputBotInlineMessageMediaWebPage(flags: _1!, message: _2!, entities: _3, url: _4!, replyMarkup: _5) } public static func parse_inputBotInlineMessageText(_ reader: BufferReader) -> InputBotInlineMessage? { var _1: Int32? @@ -946,12 +933,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputBotInlineMessage.inputBotInlineMessageText(flags: _1!, message: _2!, entities: _3, replyMarkup: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputBotInlineMessage.inputBotInlineMessageText(flags: _1!, message: _2!, entities: _3, replyMarkup: _4) } } @@ -1002,12 +988,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputBotInlineMessageID.inputBotInlineMessageID(dcId: _1!, id: _2!, accessHash: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputBotInlineMessageID.inputBotInlineMessageID(dcId: _1!, id: _2!, accessHash: _3!) } public static func parse_inputBotInlineMessageID64(_ reader: BufferReader) -> InputBotInlineMessageID? { var _1: Int32? @@ -1022,12 +1006,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputBotInlineMessageID.inputBotInlineMessageID64(dcId: _1!, ownerId: _2!, id: _3!, accessHash: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputBotInlineMessageID.inputBotInlineMessageID64(dcId: _1!, ownerId: _2!, id: _3!, accessHash: _4!) } } @@ -1134,12 +1117,16 @@ public extension Api { let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.InputBotInlineResult.inputBotInlineResult(flags: _1!, id: _2!, type: _3!, title: _4, description: _5, url: _6, thumb: _7, content: _8, sendMessage: _9!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + if !_c8 { return nil } + if !_c9 { return nil } + return Api.InputBotInlineResult.inputBotInlineResult(flags: _1!, id: _2!, type: _3!, title: _4, description: _5, url: _6, thumb: _7, content: _8, sendMessage: _9!) } public static func parse_inputBotInlineResultDocument(_ reader: BufferReader) -> InputBotInlineResult? { var _1: Int32? @@ -1167,12 +1154,14 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.InputBotInlineResult.inputBotInlineResultDocument(flags: _1!, id: _2!, type: _3!, title: _4, description: _5, document: _6!, sendMessage: _7!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + if !_c7 { return nil } + return Api.InputBotInlineResult.inputBotInlineResultDocument(flags: _1!, id: _2!, type: _3!, title: _4, description: _5, document: _6!, sendMessage: _7!) } public static func parse_inputBotInlineResultGame(_ reader: BufferReader) -> InputBotInlineResult? { var _1: String? @@ -1186,12 +1175,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputBotInlineResult.inputBotInlineResultGame(id: _1!, shortName: _2!, sendMessage: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputBotInlineResult.inputBotInlineResultGame(id: _1!, shortName: _2!, sendMessage: _3!) } public static func parse_inputBotInlineResultPhoto(_ reader: BufferReader) -> InputBotInlineResult? { var _1: String? @@ -1210,12 +1197,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputBotInlineResult.inputBotInlineResultPhoto(id: _1!, type: _2!, photo: _3!, sendMessage: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputBotInlineResult.inputBotInlineResultPhoto(id: _1!, type: _2!, photo: _3!, sendMessage: _4!) } } @@ -1262,12 +1248,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputBusinessAwayMessage.inputBusinessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, recipients: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputBusinessAwayMessage.inputBusinessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, recipients: _4!) } } diff --git a/submodules/TelegramApi/Sources/Api9.swift b/submodules/TelegramApi/Sources/Api9.swift index a5bae125..2bdcfb3e 100644 --- a/submodules/TelegramApi/Sources/Api9.swift +++ b/submodules/TelegramApi/Sources/Api9.swift @@ -44,12 +44,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 6) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputBusinessBotRecipients.inputBusinessBotRecipients(flags: _1!, users: _2, excludeUsers: _3) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputBusinessBotRecipients.inputBusinessBotRecipients(flags: _1!, users: _2, excludeUsers: _3) } } @@ -98,12 +96,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputBusinessChatLink.inputBusinessChatLink(flags: _1!, message: _2!, entities: _3, title: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputBusinessChatLink.inputBusinessChatLink(flags: _1!, message: _2!, entities: _3, title: _4) } } @@ -144,12 +141,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputBusinessGreetingMessage.inputBusinessGreetingMessage(shortcutId: _1!, recipients: _2!, noActivityDays: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputBusinessGreetingMessage.inputBusinessGreetingMessage(shortcutId: _1!, recipients: _2!, noActivityDays: _3!) } } @@ -194,12 +189,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputBusinessIntro.inputBusinessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputBusinessIntro.inputBusinessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4) } } @@ -240,12 +234,9 @@ public extension Api { } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil - if _c1 && _c2 { - return Api.InputBusinessRecipients.inputBusinessRecipients(flags: _1!, users: _2) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputBusinessRecipients.inputBusinessRecipients(flags: _1!, users: _2) } } @@ -300,12 +291,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputChannel.inputChannel(channelId: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputChannel.inputChannel(channelId: _1!, accessHash: _2!) } public static func parse_inputChannelEmpty(_ reader: BufferReader) -> InputChannel? { return Api.InputChannel.inputChannelEmpty @@ -322,12 +310,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputChannel.inputChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputChannel.inputChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!) } } @@ -382,12 +368,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputPhoto } let _c1 = _1 != nil - if _c1 { - return Api.InputChatPhoto.inputChatPhoto(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputChatPhoto.inputChatPhoto(id: _1!) } public static func parse_inputChatPhotoEmpty(_ reader: BufferReader) -> InputChatPhoto? { return Api.InputChatPhoto.inputChatPhotoEmpty @@ -414,12 +396,12 @@ public extension Api { let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputChatPhoto.inputChatUploadedPhoto(flags: _1!, file: _2, video: _3, videoStartTs: _4, videoEmojiMarkup: _5) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + return Api.InputChatPhoto.inputChatUploadedPhoto(flags: _1!, file: _2, video: _3, videoStartTs: _4, videoEmojiMarkup: _5) } } @@ -468,12 +450,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputChatTheme.inputChatTheme(emoticon: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputChatTheme.inputChatTheme(emoticon: _1!) } public static func parse_inputChatThemeEmpty(_ reader: BufferReader) -> InputChatTheme? { return Api.InputChatTheme.inputChatThemeEmpty @@ -482,12 +460,8 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputChatTheme.inputChatThemeUniqueGift(slug: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputChatTheme.inputChatThemeUniqueGift(slug: _1!) } } @@ -518,12 +492,8 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.InputChatlist.inputChatlistDialogFilter(filterId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputChatlist.inputChatlistDialogFilter(filterId: _1!) } } @@ -574,12 +544,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputCheckPasswordSRP.inputCheckPasswordSRP(srpId: _1!, A: _2!, M1: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputCheckPasswordSRP.inputCheckPasswordSRP(srpId: _1!, A: _2!, M1: _3!) } } @@ -614,12 +582,9 @@ public extension Api { _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputClientProxy.inputClientProxy(address: _1!, port: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputClientProxy.inputClientProxy(address: _1!, port: _2!) } } @@ -659,23 +624,15 @@ public extension Api { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputCollectible.inputCollectiblePhone(phone: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputCollectible.inputCollectiblePhone(phone: _1!) } public static func parse_inputCollectibleUsername(_ reader: BufferReader) -> InputCollectible? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.InputCollectible.inputCollectibleUsername(username: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputCollectible.inputCollectibleUsername(username: _1!) } } @@ -728,12 +685,13 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputContact.inputPhoneContact(flags: _1!, clientId: _2!, phone: _3!, firstName: _4!, lastName: _5!, note: _6) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + if !_c5 { return nil } + if !_c6 { return nil } + return Api.InputContact.inputPhoneContact(flags: _1!, clientId: _2!, phone: _3!, firstName: _4!, lastName: _5!, note: _6) } } @@ -775,23 +733,15 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputPeer } let _c1 = _1 != nil - if _c1 { - return Api.InputDialogPeer.inputDialogPeer(peer: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputDialogPeer.inputDialogPeer(peer: _1!) } public static func parse_inputDialogPeerFolder(_ reader: BufferReader) -> InputDialogPeer? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.InputDialogPeer.inputDialogPeerFolder(folderId: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputDialogPeer.inputDialogPeerFolder(folderId: _1!) } } @@ -839,12 +789,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputDocument.inputDocument(id: _1!, accessHash: _2!, fileReference: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputDocument.inputDocument(id: _1!, accessHash: _2!, fileReference: _3!) } public static func parse_inputDocumentEmpty(_ reader: BufferReader) -> InputDocument? { return Api.InputDocument.inputDocumentEmpty @@ -882,12 +830,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputEncryptedChat.inputEncryptedChat(chatId: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputEncryptedChat.inputEncryptedChat(chatId: _1!, accessHash: _2!) } } @@ -954,12 +899,9 @@ public extension Api { _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputEncryptedFile.inputEncryptedFile(id: _1!, accessHash: _2!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + return Api.InputEncryptedFile.inputEncryptedFile(id: _1!, accessHash: _2!) } public static func parse_inputEncryptedFileBigUploaded(_ reader: BufferReader) -> InputEncryptedFile? { var _1: Int64? @@ -971,12 +913,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputEncryptedFile.inputEncryptedFileBigUploaded(id: _1!, parts: _2!, keyFingerprint: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputEncryptedFile.inputEncryptedFileBigUploaded(id: _1!, parts: _2!, keyFingerprint: _3!) } public static func parse_inputEncryptedFileEmpty(_ reader: BufferReader) -> InputEncryptedFile? { return Api.InputEncryptedFile.inputEncryptedFileEmpty @@ -994,12 +934,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputEncryptedFile.inputEncryptedFileUploaded(id: _1!, parts: _2!, md5Checksum: _3!, keyFingerprint: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputEncryptedFile.inputEncryptedFileUploaded(id: _1!, parts: _2!, md5Checksum: _3!, keyFingerprint: _4!) } } @@ -1062,12 +1001,11 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputFile.inputFile(id: _1!, parts: _2!, name: _3!, md5Checksum: _4!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + if !_c4 { return nil } + return Api.InputFile.inputFile(id: _1!, parts: _2!, name: _3!, md5Checksum: _4!) } public static func parse_inputFileBig(_ reader: BufferReader) -> InputFile? { var _1: Int64? @@ -1079,12 +1017,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputFile.inputFileBig(id: _1!, parts: _2!, name: _3!) - } - else { - return nil - } + if !_c1 { return nil } + if !_c2 { return nil } + if !_c3 { return nil } + return Api.InputFile.inputFileBig(id: _1!, parts: _2!, name: _3!) } public static func parse_inputFileStoryDocument(_ reader: BufferReader) -> InputFile? { var _1: Api.InputDocument? @@ -1092,12 +1028,8 @@ public extension Api { _1 = Api.parse(reader, signature: signature) as? Api.InputDocument } let _c1 = _1 != nil - if _c1 { - return Api.InputFile.inputFileStoryDocument(id: _1!) - } - else { - return nil - } + if !_c1 { return nil } + return Api.InputFile.inputFileStoryDocument(id: _1!) } } diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index 74588d00..87cad945 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -14,18 +14,6 @@ import PresentationDataUtils import TelegramCallsUI import UndoUI -public enum MediaAccessoryPanelVisibility { - case none - case specific(size: ContainerViewLayoutSizeClass) - case always -} - -public enum LocationBroadcastPanelSource { - case none - case summary - case peer(PeerId) -} - private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) { let presentImpl: (EngineMessage?) -> Void = { [weak controller] message in if let message = message, let strongController = controller { @@ -65,332 +53,31 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { public var accessoryPanelContainer: ASDisplayNode? public private(set) var accessoryPanelContainerHeight: CGFloat = 0.0 - public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility public var tempHideAccessoryPanels: Bool = false - public let locationBroadcastPanelSource: LocationBroadcastPanelSource - public let groupCallPanelSource: GroupCallPanelSource - - private var mediaStatusDisposable: Disposable? - private var locationBroadcastDisposable: Disposable? - private var currentGroupCallDisposable: Disposable? - - public private(set) var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)? - private var playlistLocation: SharedMediaPlaylistLocation? - - public var tempVoicePlaylistEnded: (() -> Void)? - public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)? - public var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem? - - public private(set) var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? - - private var locationBroadcastMode: LocationBroadcastNavigationAccessoryPanelMode? - private var locationBroadcastPeers: [EnginePeer]? - private var locationBroadcastMessages: [EngineMessage.Id: EngineMessage]? - private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel? - private var giftAuctionAccessoryPanel: GiftAuctionAccessoryPanel? private var giftAuctionStates: [GiftAuctionContext.State] = [] private var giftAuctionDisposable: Disposable? - private var groupCallPanelData: GroupCallPanelData? - public private(set) var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel? - private var dismissingPanel: ASDisplayNode? - private weak var audioRateTooltipController: UndoOverlayController? - private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - private var playlistPreloadDisposable: Disposable? override open var additionalNavigationBarHeight: CGFloat { - var height: CGFloat = 0.0 - if self.accessoryPanelContainer == nil { - if let _ = self.groupCallAccessoryPanel { - height += 50.0 - } - if let _ = self.mediaAccessoryPanel { - height += MediaNavigationAccessoryHeaderNode.minimizedHeight - } - if let _ = self.locationBroadcastAccessoryPanel { - height += MediaNavigationAccessoryHeaderNode.minimizedHeight - } - } - return height + return 0.0 } - public init(context: AccountContext, navigationBarPresentationData: NavigationBarPresentationData?, mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility, locationBroadcastPanelSource: LocationBroadcastPanelSource, groupCallPanelSource: GroupCallPanelSource) { + public init(context: AccountContext, navigationBarPresentationData: NavigationBarPresentationData?) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.mediaAccessoryPanelVisibility = mediaAccessoryPanelVisibility - self.locationBroadcastPanelSource = locationBroadcastPanelSource - self.groupCallPanelSource = groupCallPanelSource super.init(navigationBarPresentationData: navigationBarPresentationData) - if case .none = mediaAccessoryPanelVisibility { - } else { - self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState - |> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in - if let (account, state, type) = playlistStateAndType { - switch state { - case let .state(state): - return .single((account, state, type)) - case .loading: - return .single(nil) |> delay(0.2, queue: .mainQueue()) - } - } else { - return .single(nil) - } - } - |> deliverOnMainQueue).start(next: { [weak self] playlistStateAndType in - guard let strongSelf = self else { - return - } - if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) || - !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) || - !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) || - strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order || strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 { - var previousVoiceItem: SharedMediaPlaylistItem? - if let playlistStateAndType = strongSelf.playlistStateAndType, playlistStateAndType.4 == .voice { - previousVoiceItem = playlistStateAndType.0 - } - - var updatedVoiceItem: SharedMediaPlaylistItem? - if let playlistStateAndType = playlistStateAndType, playlistStateAndType.2 == .voice { - updatedVoiceItem = playlistStateAndType.1.item - } - - strongSelf.tempVoicePlaylistCurrentItem = updatedVoiceItem - strongSelf.tempVoicePlaylistItemChanged?(previousVoiceItem, updatedVoiceItem) - if let playlistStateAndType = playlistStateAndType { - strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.previousItem, playlistStateAndType.1.nextItem, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0) - } else { - var voiceEnded = false - if strongSelf.playlistStateAndType?.4 == .voice { - voiceEnded = true - } - strongSelf.playlistStateAndType = nil - if voiceEnded { - strongSelf.tempVoicePlaylistEnded?() - } - } - strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) - } - strongSelf.playlistLocation = playlistStateAndType?.1.playlistLocation - }) - } - - if let liveLocationManager = context.liveLocationManager { - switch locationBroadcastPanelSource { - case .none: - self.locationBroadcastMode = nil - case .summary, .peer: - let signal: Signal<([EnginePeer]?, [EngineMessage.Id: EngineMessage]?), NoError> - switch locationBroadcastPanelSource { - case let .peer(peerId): - self.locationBroadcastMode = .peer - signal = combineLatest(liveLocationManager.summaryManager.peersBroadcastingTo(peerId: peerId), liveLocationManager.summaryManager.broadcastingToMessages()) - |> map { peersAndMessages, outgoingMessages in - var peers = peersAndMessages?.map { $0.0 } - for message in outgoingMessages.values { - if message.id.peerId == peerId, let author = message.author { - if peers == nil { - peers = [] - } - peers?.append(author) - } - } - return (peers, outgoingMessages) - } - default: - self.locationBroadcastMode = .summary - signal = liveLocationManager.summaryManager.broadcastingToMessages() - |> map { messages -> ([EnginePeer]?, [EngineMessage.Id: EngineMessage]?) in - if messages.isEmpty { - return (nil, nil) - } else { - var peers: [EnginePeer] = [] - for message in messages.values.sorted(by: { $0.index < $1.index }) { - if let peer = message.peers[message.id.peerId] { - peers.append(EnginePeer(peer)) - } - } - return (peers, messages) - } - } - - } - - self.locationBroadcastDisposable = (signal - |> deliverOnMainQueue).start(next: { [weak self] peers, messages in - if let strongSelf = self { - var updated = false - if let current = strongSelf.locationBroadcastPeers, let peers = peers { - updated = current != peers - } else if (strongSelf.locationBroadcastPeers != nil) != (peers != nil) { - updated = true - } - - strongSelf.locationBroadcastMessages = messages - - if updated { - let wasEmpty = strongSelf.locationBroadcastPeers == nil - strongSelf.locationBroadcastPeers = peers - if wasEmpty != (peers == nil) { - strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) - } else if let peers = peers, let locationBroadcastMode = strongSelf.locationBroadcastMode { - var canClose = true - if case let .peer(peerId) = strongSelf.locationBroadcastPanelSource, let messages = messages { - canClose = false - for messageId in messages.keys { - if messageId.peerId == peerId { - canClose = true - } - } - } - strongSelf.locationBroadcastAccessoryPanel?.update(peers: peers, mode: locationBroadcastMode, canClose: canClose) - } - } - } - }) - } - } - - if let callManager = context.sharedContext.callManager { - switch groupCallPanelSource { - case .none, .all: - break - case let .peer(peerId): - let currentGroupCall: Signal = callManager.currentGroupCallSignal - |> distinctUntilChanged(isEqual: { lhs, rhs in - return lhs == rhs - }) - |> map { call -> PresentationGroupCall? in - guard case let .group(call) = call else { - return nil - } - guard call.peerId == peerId && call.account.peerId == context.account.peerId else { - return nil - } - return call - } - - let availableGroupCall: Signal - if case let .peer(peerId) = groupCallPanelSource { - availableGroupCall = context.account.viewTracker.peerView(peerId) - |> map { peerView -> (CachedChannelData.ActiveCall?, EnginePeer?) in - let peer = peerView.peers[peerId].flatMap(EnginePeer.init) - if let cachedData = peerView.cachedData as? CachedChannelData { - return (cachedData.activeCall, peer) - } else if let cachedData = peerView.cachedData as? CachedGroupData { - return (cachedData.activeCall, peer) - } else { - return (nil, peer) - } - } - |> distinctUntilChanged(isEqual: { lhs, rhs in - return lhs.0 == rhs.0 - }) - |> mapToSignal { activeCall, peer -> Signal in - guard let activeCall = activeCall else { - return .single(nil) - } - - var isChannel = false - if let peer = peer, case let .channel(channel) = peer, case .broadcast = channel.info { - isChannel = true - } - - return Signal { [weak context] subscriber in - guard let context = context, let callContextCache = context.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl else { - return EmptyDisposable - } - - let disposable = MetaDisposable() - - callContextCache.impl.syncWith { impl in - let callContext = impl.get(account: context.account, engine: context.engine, peerId: peerId, isChannel: isChannel, call: EngineGroupCallDescription(activeCall)) - disposable.set((callContext.context.panelData - |> deliverOnMainQueue).start(next: { panelData in - callContext.keep() - var updatedPanelData = panelData - if let panelData { - var updatedInfo = panelData.info - updatedInfo.subscribedToScheduled = activeCall.subscribedToScheduled - updatedPanelData = panelData.withInfo(updatedInfo) - } - subscriber.putNext(updatedPanelData) - })) - } - - return ActionDisposable { - disposable.dispose() - } - } - |> runOn(.mainQueue()) - } - } else { - availableGroupCall = .single(nil) - } - - let previousCurrentGroupCall = Atomic(value: nil) - self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(), availableGroupCall, currentGroupCall).start(next: { [weak self] availableState, currentGroupCall in - guard let strongSelf = self else { - return - } - - let previousCurrentGroupCall = previousCurrentGroupCall.swap(currentGroupCall) - - let panelData: GroupCallPanelData? - if previousCurrentGroupCall != nil && currentGroupCall == nil && availableState?.participantCount == 1 { - panelData = nil - } else { - panelData = currentGroupCall != nil || (availableState?.participantCount == 0 && availableState?.info.scheduleTimestamp == nil && availableState?.info.isStream == false) ? nil : availableState - } - - let wasEmpty = strongSelf.groupCallPanelData == nil - strongSelf.groupCallPanelData = panelData - let isEmpty = strongSelf.groupCallPanelData == nil - if wasEmpty != isEmpty { - strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) - } else if let groupCallPanelData = strongSelf.groupCallPanelData { - strongSelf.groupCallAccessoryPanel?.update(data: groupCallPanelData) - } - }) - } - } - - if let giftAuctionsManager = context.giftAuctionsManager, case .summary = locationBroadcastPanelSource { - self.giftAuctionDisposable = (giftAuctionsManager.state - |> deliverOnMainQueue).start(next: { [weak self] states in - guard let self else { - return - } - self.giftAuctionStates = states.filter { state in - if case .ongoing = state.auctionState { - return true - } else { - return false - } - } - }) - } - self.presentationDataDisposable = (self.updatedPresentationData.1 |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { - let previousTheme = strongSelf.presentationData.theme - let previousStrings = strongSelf.presentationData.strings - strongSelf.presentationData = presentationData - - if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { - strongSelf.mediaAccessoryPanel?.0.containerNode.updatePresentationData(presentationData) - strongSelf.locationBroadcastAccessoryPanel?.updatePresentationData(presentationData) - strongSelf.groupCallAccessoryPanel?.updatePresentationData(presentationData) - } } }) } @@ -400,11 +87,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { } deinit { - self.mediaStatusDisposable?.dispose() - self.locationBroadcastDisposable?.dispose() - self.currentGroupCallDisposable?.dispose() self.presentationDataDisposable?.dispose() - self.playlistPreloadDisposable?.dispose() } required public init(coder aDecoder: NSCoder) { @@ -429,118 +112,8 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { super.containerLayoutUpdated(layout, transition: transition) - let navigationHeight = super.navigationLayout(layout: layout).navigationFrame.height - self.additionalNavigationBarHeight - - let mediaAccessoryPanelHidden: Bool - if self.tempHideAccessoryPanels { - mediaAccessoryPanelHidden = true - } else { - switch self.mediaAccessoryPanelVisibility { - case .always: - mediaAccessoryPanelHidden = false - case .none: - mediaAccessoryPanelHidden = true - case let .specific(size): - mediaAccessoryPanelHidden = size != layout.metrics.widthClass - } - } - var additionalHeight: CGFloat = 0.0 var panelStartY: CGFloat = 0.0 - if self.accessoryPanelContainer == nil { - var negativeHeight: CGFloat = 0.0 - if let _ = self.groupCallPanelData { - negativeHeight += 50.0 - } - if let _ = self.locationBroadcastPeers, let _ = self.locationBroadcastMode { - negativeHeight += MediaNavigationAccessoryHeaderNode.minimizedHeight - } - if let _ = self.playlistStateAndType, !mediaAccessoryPanelHidden { - negativeHeight += MediaNavigationAccessoryHeaderNode.minimizedHeight - } - panelStartY = navigationHeight.isZero ? (-negativeHeight) : (navigationHeight + additionalHeight + UIScreenPixel) - } - - if let groupCallPanelData = self.groupCallPanelData { - let panelHeight: CGFloat = 50.0 - let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight)) - additionalHeight += panelHeight - panelStartY += panelHeight - - let groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel - if let current = self.groupCallAccessoryPanel { - groupCallAccessoryPanel = current - transition.updateFrame(node: groupCallAccessoryPanel, frame: panelFrame) - groupCallAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: transition) - } else { - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - groupCallAccessoryPanel = GroupCallNavigationAccessoryPanel(context: self.context, presentationData: presentationData, tapAction: { [weak self] in - guard let strongSelf = self, let groupCallPanelData = strongSelf.groupCallPanelData else { - return - } - strongSelf.joinGroupCall( - peerId: groupCallPanelData.peerId, - invite: nil, - activeCall: EngineGroupCallDescription(id: groupCallPanelData.info.id, accessHash: groupCallPanelData.info.accessHash, title: groupCallPanelData.info.title, scheduleTimestamp: groupCallPanelData.info.scheduleTimestamp, subscribedToScheduled: groupCallPanelData.info.subscribedToScheduled, isStream: groupCallPanelData.info.isStream) - ) - }, notifyScheduledTapAction: { [weak self] in - guard let self, let groupCallPanelData = self.groupCallPanelData else { - return - } - if groupCallPanelData.info.scheduleTimestamp != nil && !groupCallPanelData.info.subscribedToScheduled { - let _ = self.context.engine.calls.toggleScheduledGroupCallSubscription(peerId: groupCallPanelData.peerId, reference: .id(id: groupCallPanelData.info.id, accessHash: groupCallPanelData.info.accessHash), subscribe: true).startStandalone() - - let controller = UndoOverlayController( - presentationData: presentationData, - content: .universal( - animation: "anim_set_notification", - scale: 0.06, - colors: [ - "Middle.Group 1.Fill 1": UIColor.white, - "Top.Group 1.Fill 1": UIColor.white, - "Bottom.Group 1.Fill 1": UIColor.white, - "EXAMPLE.Group 1.Fill 1": UIColor.white, - "Line.Group 1.Stroke 1": UIColor.white - ], - title: nil, - text: presentationData.strings.Chat_ToastSubscribedToScheduledLiveStream_Text, - customUndoText: nil, - timeout: nil - ), - elevatedLayout: false, - animateInAsReplacement: false, - action: { _ in - return true - } - ) - self.audioRateTooltipController = controller - self.present(controller, in: .current) - } - }) - if let accessoryPanelContainer = self.accessoryPanelContainer { - accessoryPanelContainer.addSubnode(groupCallAccessoryPanel) - } else { - self.navigationBar?.additionalContentNode.addSubnode(groupCallAccessoryPanel) - } - self.groupCallAccessoryPanel = groupCallAccessoryPanel - groupCallAccessoryPanel.frame = panelFrame - - groupCallAccessoryPanel.update(data: groupCallPanelData) - groupCallAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: .immediate) - if transition.isAnimated { - groupCallAccessoryPanel.animateIn(transition) - } - } - } else if let groupCallAccessoryPanel = self.groupCallAccessoryPanel { - self.groupCallAccessoryPanel = nil - if transition.isAnimated { - groupCallAccessoryPanel.animateOut(transition, completion: { [weak groupCallAccessoryPanel] in - groupCallAccessoryPanel?.removeFromSupernode() - }) - } else { - groupCallAccessoryPanel.removeFromSupernode() - } - } if !self.giftAuctionStates.isEmpty { let panelHeight: CGFloat = 56.0 @@ -598,454 +171,6 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { giftAuctionAccessoryPanel.removeFromSupernode() } } - - if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode { - let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight - let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight)) - additionalHeight += panelHeight - panelStartY += panelHeight - - let locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel - if let current = self.locationBroadcastAccessoryPanel { - locationBroadcastAccessoryPanel = current - transition.updateFrame(node: locationBroadcastAccessoryPanel, frame: panelFrame) - locationBroadcastAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: transition) - } else { - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - locationBroadcastAccessoryPanel = LocationBroadcastNavigationAccessoryPanel(accountPeerId: self.context.account.peerId, theme: presentationData.theme, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, tapAction: { [weak self] in - if let strongSelf = self { - switch strongSelf.locationBroadcastPanelSource { - case .none: - break - case .summary: - if let locationBroadcastMessages = strongSelf.locationBroadcastMessages { - let messages = locationBroadcastMessages.values.sorted(by: { $0.index > $1.index }) - - if messages.count == 1 { - presentLiveLocationController(context: strongSelf.context, peerId: messages[0].id.peerId, controller: strongSelf) - } else { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = ActionSheetController(presentationData: presentationData) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } - var items: [ActionSheetItem] = [] - if !messages.isEmpty { - items.append(ActionSheetTextItem(title: presentationData.strings.LiveLocation_MenuChatsCount(Int32(messages.count)))) - for message in messages { - if let peer = message.peers[message.id.peerId] { - var beginTimeAndTimeout: (Double, Double)? - for media in message.media { - if let media = media as? TelegramMediaMap, let timeout = media.liveBroadcastingTimeout { - beginTimeAndTimeout = (Double(message.timestamp), Double(timeout)) - } - } - - if let beginTimeAndTimeout = beginTimeAndTimeout { - items.append(LocationBroadcastActionSheetItem(context: strongSelf.context, peer: peer, title: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), beginTimestamp: beginTimeAndTimeout.0, timeout: beginTimeAndTimeout.1, strings: presentationData.strings, action: { - dismissAction() - if let strongSelf = self { - presentLiveLocationController(context: strongSelf.context, peerId: peer.id, controller: strongSelf) - } - })) - } - } - } - items.append(ActionSheetButtonItem(title: presentationData.strings.LiveLocation_MenuStopAll, color: .destructive, action: { - dismissAction() - if let locationBroadcastPeers = strongSelf.locationBroadcastPeers { - for peer in locationBroadcastPeers { - self?.context.liveLocationManager?.cancelLiveLocation(peerId: peer.id) - } - } - })) - } - controller.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - strongSelf.view.endEditing(true) - strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - } - } - case let .peer(peerId): - presentLiveLocationController(context: strongSelf.context, peerId: peerId, controller: strongSelf) - } - } - }, close: { [weak self] in - if let strongSelf = self { - var closePeers: [EnginePeer]? - var closePeerId: EnginePeer.Id? - switch strongSelf.locationBroadcastPanelSource { - case .none: - break - case .summary: - if let locationBroadcastPeers = strongSelf.locationBroadcastPeers { - if locationBroadcastPeers.count > 1 { - closePeers = locationBroadcastPeers - } else { - closePeerId = locationBroadcastPeers.first?.id - } - } - case let .peer(peerId): - closePeerId = peerId - } - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = ActionSheetController(presentationData: presentationData) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } - var items: [ActionSheetItem] = [] - if let closePeers = closePeers, !closePeers.isEmpty { - items.append(ActionSheetTextItem(title: presentationData.strings.LiveLocation_MenuChatsCount(Int32(closePeers.count)))) - for peer in closePeers { - items.append(ActionSheetButtonItem(title: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), action: { - dismissAction() - if let strongSelf = self { - presentLiveLocationController(context: strongSelf.context, peerId: peer.id, controller: strongSelf) - } - })) - } - items.append(ActionSheetButtonItem(title: presentationData.strings.LiveLocation_MenuStopAll, color: .destructive, action: { - dismissAction() - for peer in closePeers { - self?.context.liveLocationManager?.cancelLiveLocation(peerId: peer.id) - } - })) - } else if let closePeerId = closePeerId { - items.append(ActionSheetButtonItem(title: presentationData.strings.Map_StopLiveLocation, color: .destructive, action: { - dismissAction() - self?.context.liveLocationManager?.cancelLiveLocation(peerId: closePeerId) - })) - } - controller.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - strongSelf.view.endEditing(true) - strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - } - }) - if let accessoryPanelContainer = self.accessoryPanelContainer { - accessoryPanelContainer.addSubnode(locationBroadcastAccessoryPanel) - } else { - self.navigationBar?.additionalContentNode.addSubnode(locationBroadcastAccessoryPanel) - } - self.locationBroadcastAccessoryPanel = locationBroadcastAccessoryPanel - locationBroadcastAccessoryPanel.frame = panelFrame - - var canClose = true - if case let .peer(peerId) = self.locationBroadcastPanelSource, let messages = self.locationBroadcastMessages { - canClose = false - for messageId in messages.keys { - if messageId.peerId == peerId { - canClose = true - } - } - } - - locationBroadcastAccessoryPanel.update(peers: locationBroadcastPeers, mode: locationBroadcastMode, canClose: canClose) - locationBroadcastAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: .immediate) - if transition.isAnimated { - locationBroadcastAccessoryPanel.animateIn(transition) - } - } - } else if let locationBroadcastAccessoryPanel = self.locationBroadcastAccessoryPanel { - self.locationBroadcastAccessoryPanel = nil - if transition.isAnimated { - locationBroadcastAccessoryPanel.animateOut(transition, completion: { [weak locationBroadcastAccessoryPanel] in - locationBroadcastAccessoryPanel?.removeFromSupernode() - }) - } else { - locationBroadcastAccessoryPanel.removeFromSupernode() - } - } - - var isViewOnceMessage = false - if let (item, _, _, _, _, _) = self.playlistStateAndType, let source = item.playbackData?.source, case let .telegramFile(_, _, isViewOnce) = source, isViewOnce { - isViewOnceMessage = true - } - - if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType, !mediaAccessoryPanelHidden && !isViewOnceMessage { - let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight - let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight)) - additionalHeight += panelHeight - panelStartY += panelHeight - - if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type { - transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame) - mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: transition) - switch order { - case .regular: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem) - case .reversed: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nextItem, previousItem) - case .random: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil) - } - let delayedStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState - |> mapToSignal { value -> Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> in - guard let value = value else { - return .single(nil) - } - switch value.1 { - case .state: - return .single(value) - case .loading: - return .single(value) |> delay(0.1, queue: .mainQueue()) - } - } - - mediaAccessoryPanel.containerNode.headerNode.playbackStatus = delayedStatus - |> map { state -> MediaPlayerStatus in - if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading { - return state.status - } else { - return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) - } - } - } else { - if let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel { - self.mediaAccessoryPanel = nil - self.dismissingPanel = mediaAccessoryPanel - self.audioRateTooltipController?.dismissWithCommitAction() - mediaAccessoryPanel.animateOut(transition: transition, completion: { [weak self, weak mediaAccessoryPanel] in - mediaAccessoryPanel?.removeFromSupernode() - if let strongSelf = self, strongSelf.dismissingPanel === mediaAccessoryPanel { - strongSelf.dismissingPanel = nil - } - }) - } - - let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, presentationData: self.updatedPresentationData.0) - mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo - mediaAccessoryPanel.getController = { [weak self] in - return self - } - mediaAccessoryPanel.presentInGlobalOverlay = { [weak self] c in - self?.presentInGlobalOverlay(c) - } - mediaAccessoryPanel.close = { [weak self] in - if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { - strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause)) - } - } - mediaAccessoryPanel.setRate = { [weak self] rate, changeType in - guard let strongSelf = self else { - return - } - let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in - let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings)?.get(MusicPlaybackSettings.self) ?? MusicPlaybackSettings.defaultSettings - - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in - return PreferencesEntry(settings.withUpdatedVoicePlaybackRate(rate)) - }) - return rate - } - |> deliverOnMainQueue).start(next: { baseRate in - guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else { - return - } - strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type) - - var hasTooltip = false - strongSelf.forEachController({ controller in - if let controller = controller as? UndoOverlayController { - hasTooltip = true - controller.dismissWithCommitAction() - } - return true - }) - - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let text: String? - let rate: CGFloat? - if case let .sliderCommit(previousValue, newValue) = changeType { - let value = String(format: "%0.1f", baseRate.doubleValue) - if baseRate == .x1 { - text = presentationData.strings.Conversation_AudioRateTooltipNormal - } else { - text = presentationData.strings.Conversation_AudioRateTooltipCustom(value).string - } - if newValue > previousValue { - rate = .infinity - } else if newValue < previousValue { - rate = -.infinity - } else { - rate = nil - } - } else if baseRate == .x1 { - text = presentationData.strings.Conversation_AudioRateTooltipNormal - rate = 1.0 - } else if baseRate == .x1_5 { - text = presentationData.strings.Conversation_AudioRateTooltip15X - rate = 1.5 - } else if baseRate == .x2 { - text = presentationData.strings.Conversation_AudioRateTooltipSpeedUp - rate = 2.0 - } else { - text = nil - rate = nil - } - var showTooltip = true - if case .sliderChange = changeType { - showTooltip = false - } - if let rate, let text, showTooltip { - let controller = UndoOverlayController( - presentationData: presentationData, - content: .audioRate( - rate: rate, - text: text - ), - elevatedLayout: false, - animateInAsReplacement: hasTooltip, - action: { action in - return true - } - ) - strongSelf.audioRateTooltipController = controller - strongSelf.present(controller, in: .current) - } - }) - } - mediaAccessoryPanel.togglePlayPause = { [weak self] in - if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { - strongSelf.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: type) - } - } - mediaAccessoryPanel.playPrevious = { [weak self] in - if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { - strongSelf.context.sharedContext.mediaManager.playlistControl(.next, type: type) - } - } - mediaAccessoryPanel.playNext = { [weak self] in - if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { - strongSelf.context.sharedContext.mediaManager.playlistControl(.previous, type: type) - } - } - mediaAccessoryPanel.tapAction = { [weak self] in - guard let strongSelf = self, let _ = strongSelf.navigationController as? NavigationController, let (state, _, _, order, type, account) = strongSelf.playlistStateAndType else { - return - } - if let id = state.id as? PeerMessagesMediaPlaylistItemId, let playlistLocation = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation { - if type == .music { - switch playlistLocation { - case .custom, .savedMusic: - let controllerContext: AccountContext - if account.id == strongSelf.context.account.id { - controllerContext = strongSelf.context - } else { - controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account) - } - let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: playlistLocation, parentNavigationController: strongSelf.navigationController as? NavigationController) - strongSelf.displayNode.view.window?.endEditing(true) - strongSelf.present(controller, in: .window(.root)) - case let .messages(chatLocation, _, _): - let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId)), count: 60, highlight: true, setupReply: false), id: 0), context: strongSelf.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic(value: nil), tag: .tag(MessageTags.music)) - - var cancelImpl: (() -> Void)? - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - self?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = MetaDisposable() - var progressStarted = false - strongSelf.playlistPreloadDisposable?.dispose() - strongSelf.playlistPreloadDisposable = (signal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - |> deliverOnMainQueue).start(next: { index in - guard let strongSelf = self else { - return - } - if let _ = index.0 { - let controllerContext: AccountContext - if account.id == strongSelf.context.account.id { - controllerContext = strongSelf.context - } else { - controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account) - } - let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: nil, parentNavigationController: strongSelf.navigationController as? NavigationController) - strongSelf.displayNode.view.window?.endEditing(true) - strongSelf.present(controller, in: .window(.root)) - } else if index.1 { - if !progressStarted { - progressStarted = true - progressDisposable.set(progressSignal.start()) - } - } - }, completed: { - }) - cancelImpl = { - self?.playlistPreloadDisposable?.dispose() - } - default: - break - } - } else { - strongSelf.context.sharedContext.navigateToChat(accountId: strongSelf.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId) - } - } - } - mediaAccessoryPanel.frame = panelFrame - if let dismissingPanel = self.dismissingPanel { - if let accessoryPanelContainer = self.accessoryPanelContainer { - accessoryPanelContainer.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel) - } else { - self.navigationBar?.additionalContentNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel) - } - } else { - if let accessoryPanelContainer = self.accessoryPanelContainer { - accessoryPanelContainer.addSubnode(mediaAccessoryPanel) - } else { - self.navigationBar?.additionalContentNode.addSubnode(mediaAccessoryPanel) - } - } - self.mediaAccessoryPanel = (mediaAccessoryPanel, type) - mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: .immediate) - switch order { - case .regular: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem) - case .reversed: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nextItem, previousItem) - case .random: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil) - } - mediaAccessoryPanel.containerNode.headerNode.playbackStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState - |> map { state -> MediaPlayerStatus in - if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading { - return state.status - } else { - return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) - } - } - mediaAccessoryPanel.animateIn(transition: transition) - } - } else if let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel { - self.mediaAccessoryPanel = nil - self.dismissingPanel = mediaAccessoryPanel - self.audioRateTooltipController?.dismissWithCommitAction() - mediaAccessoryPanel.animateOut(transition: transition, completion: { [weak self, weak mediaAccessoryPanel] in - mediaAccessoryPanel?.removeFromSupernode() - if let strongSelf = self, strongSelf.dismissingPanel === mediaAccessoryPanel { - strongSelf.dismissingPanel = nil - } - }) - } self.suspendNavigationBarLayout = false if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout { diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index bad899d0..c771b6f4 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -115,7 +115,6 @@ swift_library( "//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/Stories/PeerListItemComponent", "//submodules/TelegramUI/Components/BackButtonComponent", - "//submodules/TelegramUI/Components/AlertComponent", "//submodules/Components/BlurredBackgroundComponent", "//submodules/DirectMediaImageCache", "//submodules/FastBlur", @@ -131,6 +130,8 @@ swift_library( "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/PremiumUI", "//submodules/TelegramUI/Components/Calls/VideoChatMicButtonComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramCallsUI/Sources/AccountGroupCallContextImpl.swift b/submodules/TelegramCallsUI/Sources/AccountGroupCallContextImpl.swift index 5b348c93..44f7f27b 100644 --- a/submodules/TelegramCallsUI/Sources/AccountGroupCallContextImpl.swift +++ b/submodules/TelegramCallsUI/Sources/AccountGroupCallContextImpl.swift @@ -24,6 +24,46 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext { var disposable: Disposable? public var participantsContext: GroupCallParticipantsContext? + public final class GroupCallPanelData { + public let peerId: EnginePeer.Id + public let isChannel: Bool + public let info: GroupCallInfo + public let topParticipants: [GroupCallParticipantsContext.Participant] + public let participantCount: Int + public let activeSpeakers: Set + public let groupCall: PresentationGroupCall? + + public init( + peerId: EnginePeer.Id, + isChannel: Bool, + info: GroupCallInfo, + topParticipants: [GroupCallParticipantsContext.Participant], + participantCount: Int, + activeSpeakers: Set, + groupCall: PresentationGroupCall? + ) { + self.peerId = peerId + self.isChannel = isChannel + self.info = info + self.topParticipants = topParticipants + self.participantCount = participantCount + self.activeSpeakers = activeSpeakers + self.groupCall = groupCall + } + + public func withInfo(_ info: GroupCallInfo) -> GroupCallPanelData { + return GroupCallPanelData( + peerId: self.peerId, + isChannel: self.isChannel, + info: info, + topParticipants: self.topParticipants, + participantCount: self.participantCount, + activeSpeakers: self.activeSpeakers, + groupCall: self.groupCall + ) + } + } + private let panelDataPromise = Promise() public var panelData: Signal { return self.panelDataPromise.get() diff --git a/submodules/TelegramCallsUI/Sources/CallFeedbackController.swift b/submodules/TelegramCallsUI/Sources/CallFeedbackController.swift index 4a47a8b8..9e8a0454 100644 --- a/submodules/TelegramCallsUI/Sources/CallFeedbackController.swift +++ b/submodules/TelegramCallsUI/Sources/CallFeedbackController.swift @@ -188,11 +188,11 @@ private enum CallFeedbackControllerEntry: ItemListNodeEntry { case let .reasonsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .reason(_, reason, title, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, maximumNumberOfLines: 2, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, maximumNumberOfLines: 2, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleReason(reason, value) }) case let .comment(_, text, placeholder): - return ItemListMultilineInputItem(presentationData: presentationData, text: text, placeholder: placeholder, maxLength: nil, sectionId: self.section, style: .blocks, textUpdated: { updatedText in + return ItemListMultilineInputItem(presentationData: presentationData, systemStyle: .glass, text: text, placeholder: placeholder, maxLength: nil, sectionId: self.section, style: .blocks, textUpdated: { updatedText in arguments.updateComment(updatedText) }, updatedFocus: { focused in if focused { @@ -200,7 +200,7 @@ private enum CallFeedbackControllerEntry: ItemListNodeEntry { } }, tag: CallFeedbackControllerEntryTag.comment) case let .includeLogs(_, title, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleIncludeLogs(value) }) case let .includeLogsInfo(_, text): diff --git a/submodules/TelegramCallsUI/Sources/CallRatingController.swift b/submodules/TelegramCallsUI/Sources/CallRatingController.swift index fa86baa0..7a086261 100644 --- a/submodules/TelegramCallsUI/Sources/CallRatingController.swift +++ b/submodules/TelegramCallsUI/Sources/CallRatingController.swift @@ -3,285 +3,13 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display +import ComponentFlow import Postbox import TelegramCore import TelegramPresentationData import TelegramVoip import AccountContext -import AppBundle - -private final class CallRatingAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - private let apply: (Int) -> Void - - var rating: Int? - - private let titleNode: ASTextNode - private var starContainerNode: ASDisplayNode - private let starNodes: [ASButtonNode] - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let disposable = MetaDisposable() - - private var validLayout: CGSize? - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], dismiss: @escaping () -> Void, apply: @escaping (Int) -> Void) { - self.strings = strings - self.apply = apply - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 3 - - self.starContainerNode = ASDisplayNode() - - var starNodes: [ASButtonNode] = [] - for _ in 0 ..< 5 { - starNodes.append(ASButtonNode()) - } - self.starNodes = starNodes - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - - self.addSubnode(self.starContainerNode) - - for node in self.starNodes { - node.addTarget(self, action: #selector(self.starPressed(_:)), forControlEvents: .touchDown) - node.addTarget(self, action: #selector(self.starReleased(_:)), forControlEvents: .touchUpInside) - self.starContainerNode.addSubnode(node) - } - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - } - - deinit { - self.disposable.dispose() - } - - override func didLoad() { - super.didLoad() - - self.starContainerNode.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))) - } - - @objc func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) { - let location = gestureRecognizer.location(in: self.starContainerNode.view) - var selectedNode: ASButtonNode? - for node in self.starNodes { - if node.frame.contains(location) { - selectedNode = node - break - } - } - if let selectedNode = selectedNode { - switch gestureRecognizer.state { - case .began, .changed: - self.starPressed(selectedNode) - case .ended: - self.starReleased(selectedNode) - case .cancelled: - self.resetStars() - default: - break - } - } else { - self.resetStars() - } - } - - private func resetStars() { - for i in 0 ..< self.starNodes.count { - let node = self.starNodes[i] - node.isSelected = false - } - } - - @objc func starPressed(_ sender: ASButtonNode) { - if let index = self.starNodes.firstIndex(of: sender) { - self.rating = index + 1 - for i in 0 ..< self.starNodes.count { - let node = self.starNodes[i] - node.isSelected = i <= index - } - } - } - - @objc func starReleased(_ sender: ASButtonNode) { - if let index = self.starNodes.firstIndex(of: sender) { - self.rating = index + 1 - for i in 0 ..< self.starNodes.count { - let node = self.starNodes[i] - node.isSelected = i <= index - } - if let rating = self.rating { - self.apply(rating) - } - } - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.strings.Calls_RatingTitle, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - - for node in self.starNodes { - node.setImage(generateTintedImage(image: UIImage(bundleImageName: "Call/Star"), color: theme.accentColor), for: []) - let highlighted = generateTintedImage(image: UIImage(bundleImageName: "Call/StarHighlighted"), color: theme.accentColor) - node.setImage(highlighted, for: [.selected]) - node.setImage(highlighted, for: [.selected, .highlighted]) - } - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width , 270.0) - - self.validLayout = size - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - - var contentWidth = max(titleSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((resultWidth - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 13.0 - - let starSize = CGSize(width: 42.0, height: 38.0) - let starsOrigin = floorToScreenPixels((resultWidth - starSize.width * 5.0) / 2.0) - self.starContainerNode.frame = CGRect(origin: CGPoint(x: starsOrigin, y: origin.y), size: CGSize(width: starSize.width * CGFloat(self.starNodes.count), height: starSize.height)) - for i in 0 ..< self.starNodes.count { - let node = self.starNodes[i] - transition.updateFrame(node: node, frame: CGRect(x: starSize.width * CGFloat(i), y: 0.0, width: starSize.width, height: starSize.height)) - } - origin.y += titleSize.height - - let resultSize = CGSize(width: resultWidth, height: titleSize.height + actionsHeight + 56.0 + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} +import AlertComponent func rateCallAndSendLogs(engine: TelegramEngine, callId: CallId, starsCount: Int, comment: String, userInitiated: Bool, includeLogs: Bool) -> Signal { let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4244000)) @@ -309,35 +37,199 @@ func rateCallAndSendLogs(engine: TelegramEngine, callId: CallId, starsCount: Int } } -public func callRatingController(sharedContext: SharedAccountContext, account: Account, callId: CallId, userInitiated: Bool, isVideo: Bool, present: @escaping (ViewController, Any) -> Void, push: @escaping (ViewController) -> Void) -> AlertController { - let presentationData = sharedContext.currentPresentationData.with { $0 } - let theme = presentationData.theme - let strings = presentationData.strings +public func callRatingController( + sharedContext: SharedAccountContext, + account: Account, + callId: CallId, + userInitiated: Bool, + isVideo: Bool, + present: @escaping (ViewController, Any) -> Void, + push: @escaping (ViewController) -> Void +) -> ViewController { + let strings = sharedContext.currentPresentationData.with { $0 }.strings - var dismissImpl: ((Bool) -> Void)? - var contentNode: CallRatingAlertContentNode? - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: { - dismissImpl?(true) - })] + var dismissImpl: (() -> Void)? - contentNode = CallRatingAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, actions: actions, dismiss: { - dismissImpl?(true) - }, apply: { rating in - dismissImpl?(true) - if rating < 4 { - push(callFeedbackController(sharedContext: sharedContext, account: account, callId: callId, rating: rating, userInitiated: userInitiated, isVideo: isVideo)) - } else { - let _ = rateCallAndSendLogs(engine: TelegramEngine(account: account), callId: callId, starsCount: rating, comment: "", userInitiated: userInitiated, includeLogs: false).start() + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent( + title: strings.Calls_RatingTitle, + alignment: .center + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "stars", + component: AnyComponent( + AlertCallRatingComponent(completion: { rating in + dismissImpl?() + if rating < 4 { + push(callFeedbackController(sharedContext: sharedContext, account: account, callId: callId, rating: rating, userInitiated: userInitiated, isVideo: isVideo)) + } else { + let _ = rateCallAndSendLogs(engine: TelegramEngine(account: account), callId: callId, starsCount: rating, comment: "", userInitiated: userInitiated, includeLogs: false).start() + } + }) + ) + )) + + let alertController = AlertScreen( + sharedContext: sharedContext, + content: content, + actions: [ + .init(title: strings.Common_NotNow) + ] + ) + + dismissImpl = { [weak alertController] in + alertController?.dismiss(completion: nil) + } + + return alertController +} + +private final class AlertCallRatingComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + private let completion: (Int) -> Void + + public init(completion: @escaping (Int) -> Void) { + self.completion = completion + } + + public static func ==(lhs: AlertCallRatingComponent, rhs: AlertCallRatingComponent) -> Bool { + return true + } + + public final class View: UIView { + private var containerView: UIView + private let starButtons: [HighlightTrackingButton] + + var rating: Int? + + private var component: AlertCallRatingComponent? + private weak var state: EmptyComponentState? + + public override init(frame: CGRect) { + self.containerView = UIView() + + var starButtons: [HighlightTrackingButton] = [] + for _ in 0 ..< 5 { + starButtons.append(HighlightTrackingButton()) + } + self.starButtons = starButtons + + super.init(frame: frame) + + self.addSubview(self.containerView) + + for button in self.starButtons { + button.addTarget(self, action: #selector(self.starPressed(_:)), for: .touchDown) + button.addTarget(self, action: #selector(self.starReleased(_:)), for: .touchUpInside) + self.containerView.addSubview(button) + } + + self.containerView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))) } - }) - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() + + public required init?(coder: NSCoder) { + preconditionFailure() + } + + @objc func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) { + let location = gestureRecognizer.location(in: self.containerView) + var selectedButton: HighlightTrackingButton? + for button in self.starButtons { + if button.frame.contains(location) { + selectedButton = button + break + } + } + if let selectedButton = selectedButton { + switch gestureRecognizer.state { + case .began, .changed: + self.starPressed(selectedButton) + case .ended: + self.starReleased(selectedButton) + case .cancelled: + self.resetStars() + default: + break + } + } else { + self.resetStars() + } + } + + private func resetStars() { + for i in 0 ..< self.starButtons.count { + let node = self.starButtons[i] + node.isSelected = false + } + } + + @objc func starPressed(_ sender: HighlightTrackingButton) { + if let index = self.starButtons.firstIndex(of: sender) { + self.rating = index + 1 + for i in 0 ..< self.starButtons.count { + let node = self.starButtons[i] + node.isSelected = i <= index + } + } + } + + @objc func starReleased(_ sender: HighlightTrackingButton) { + guard let component = self.component else { + return + } + if let index = self.starButtons.firstIndex(of: sender) { + self.rating = index + 1 + for i in 0 ..< self.starButtons.count { + let node = self.starButtons[i] + node.isSelected = i <= index + } + if let rating = self.rating { + component.completion(rating) + } + } + } + + func update(component: AlertCallRatingComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + if self.component == nil { + for i in 0 ..< self.starButtons.count { + let button = self.starButtons[i] + button.setImage(UIImage(bundleImageName: "Call/Star")?.withRenderingMode(.alwaysTemplate), for: .normal) + button.setImage(UIImage(bundleImageName: "Call/StarHighlighted")?.withRenderingMode(.alwaysTemplate), for: .selected) + button.setImage(UIImage(bundleImageName: "Call/StarHighlighted")?.withRenderingMode(.alwaysTemplate), for: [.selected, .highlighted]) + } + } + + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let buttonCount = CGFloat(self.starButtons.count) + let starSize = CGSize(width: 42.0, height: 38.0) + let starsOrigin = floorToScreenPixels((availableSize.width - starSize.width * buttonCount) / 2.0) + self.containerView.frame = CGRect(origin: CGPoint(x: starsOrigin, y: 0.0), size: CGSize(width: starSize.width * buttonCount, height: starSize.height)) + for i in 0 ..< self.starButtons.count { + let button = self.starButtons[i] + button.imageView?.tintColor = environment.theme.actionSheet.controlAccentColor + + transition.setFrame(view: button, frame: CGRect(x: starSize.width * CGFloat(i), y: 0.0, width: starSize.width, height: starSize.height)) + } + + return CGSize(width: availableSize.width, height: 38.0) } } - return controller + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } } diff --git a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift index e40dafcf..04529dbe 100644 --- a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift @@ -22,6 +22,22 @@ private let pink = UIColor(rgb: 0xef436c) private let latePurple = UIColor(rgb: 0xaa56a6) private let latePink = UIColor(rgb: 0xef476f) +private func textForTimeout(value: Int32) -> String { + if value < 3600 { + let minutes = value / 60 + let seconds = value % 60 + let secondsPadding = seconds < 10 ? "0" : "" + return "\(minutes):\(secondsPadding)\(seconds)" + } else { + let hours = value / 3600 + let minutes = (value % 3600) / 60 + let minutesPadding = minutes < 10 ? "0" : "" + let seconds = value % 60 + let secondsPadding = seconds < 10 ? "0" : "" + return "\(hours):\(minutesPadding)\(minutes):\(secondsPadding)\(seconds)" + } +} + private class CallStatusBarBackgroundNode: ASDisplayNode { enum State { case connecting diff --git a/submodules/TelegramCallsUI/Sources/CallSuggestTabController.swift b/submodules/TelegramCallsUI/Sources/CallSuggestTabController.swift index 2d3cfa22..cfb4c157 100644 --- a/submodules/TelegramCallsUI/Sources/CallSuggestTabController.swift +++ b/submodules/TelegramCallsUI/Sources/CallSuggestTabController.swift @@ -3,237 +3,117 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences import AccountContext import AppBundle +import ComponentFlow +import AlertComponent +import BundleIconComponent -private final class CallSuggestTabAlertContentNode: AlertContentNode { - private let strings: PresentationStrings +public func callSuggestTabController(sharedContext: SharedAccountContext) -> ViewController { + let strings = sharedContext.currentPresentationData.with { $0 }.strings + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertCallSuggestHeaderComponent() + ) + )) + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Calls_CallTabTitle) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.Calls_CallTabDescription)) + ) + )) - private let titleNode: ASTextNode - private let textNode: ASTextNode - private let iconNode: ASImageNode - private let accentIconNode: ASImageNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled + let alertController = AlertScreen( + sharedContext: sharedContext, + content: content, + actions: [ + .init(title: strings.Common_NotNow), + .init(title: strings.Calls_AddTab, type: .default, action: { + let _ = updateCallListSettingsInteractively(accountManager: sharedContext.accountManager, { + $0.withUpdatedShowTab(true) + }).start() + }) + ] + ) + return alertController +} + +private final class AlertCallSuggestHeaderComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + public init() { } - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction]) { - self.strings = strings - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 2 - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true - - self.accentIconNode = ASImageNode() - self.accentIconNode.displaysAsynchronously = false - self.accentIconNode.displayWithoutProcessing = true - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.accentIconNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) + public static func ==(lhs: AlertCallSuggestHeaderComponent, rhs: AlertCallSuggestHeaderComponent) -> Bool { + return true } - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: strings.Calls_CallTabTitle, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = NSAttributedString(string: strings.Calls_CallTabDescription, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call List/AlertIcon"), color: theme.controlBorderColor) - self.accentIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call List/AlertAccentIcon"), color: theme.accentColor) + public final class View: UIView { + private let image = ComponentView() + private let accentImage = ComponentView() - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } + private var component: AlertCallSuggestHeaderComponent? + private weak var state: EmptyComponentState? - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width , 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let titleSize = self.titleNode.measure(size) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 13.0 - - var iconSize = CGSize() - if let icon = self.iconNode.image { - iconSize = icon.size - let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - iconSize.width) / 2.0), y: origin.y), size: iconSize) - transition.updateFrame(node: self.iconNode, frame: iconFrame) - transition.updateFrame(node: self.accentIconNode, frame: iconFrame) - origin.y += iconSize.height + 16.0 - } - - let textSize = self.textNode.measure(size) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - var contentWidth = max(titleSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - let resultSize = CGSize(width: resultWidth, height: titleSize.height + iconSize.height + textSize.height + actionsHeight + 34.0 + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + func update(component: AlertCallSuggestHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let imageSize = self.image.update( + transition: .immediate, + component: AnyComponent( + BundleIconComponent(name: "Call List/AlertIcon", tintColor: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.2)) + ), + environment: {}, + containerSize: availableSize + ) + let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: 0.0), size: imageSize) + if let imageView = self.image.view { + if imageView.superview == nil { + self.addSubview(imageView) } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width + imageView.frame = imageFrame } - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight + let _ = self.accentImage.update( + transition: .immediate, + component: AnyComponent( + BundleIconComponent(name: "Call List/AlertAccentIcon", tintColor: environment.theme.actionSheet.controlAccentColor) + ), + environment: {}, + containerSize: availableSize + ) + let accentImageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: 0.0), size: imageSize) + if let accentImageView = self.accentImage.view { + if accentImageView.superview == nil { + self.addSubview(accentImageView) + } + accentImageView.frame = accentImageFrame } - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} - -func callSuggestTabController(sharedContext: SharedAccountContext) -> AlertController { - let presentationData = sharedContext.currentPresentationData.with { $0 } - let theme = presentationData.theme - let strings = presentationData.strings - - var dismissImpl: ((Bool) -> Void)? - var contentNode: CallSuggestTabAlertContentNode? - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: { - dismissImpl?(true) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Calls_AddTab, action: { - dismissImpl?(true) - let _ = updateCallListSettingsInteractively(accountManager: sharedContext.accountManager, { - $0.withUpdatedShowTab(true) - }).start() - })] - - contentNode = CallSuggestTabAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, actions: actions) - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() + return CGSize(width: availableSize.width, height: imageSize.height) } } - return controller + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } } diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index 5edc8d27..3a62b8af 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -508,7 +508,7 @@ public final class MediaStreamComponent: CombinedComponent { let title: String = presentationData.strings.LiveStream_EditTitle let text: String = presentationData.strings.LiveStream_EditTitleText - let editController = voiceChatTitleEditController(sharedContext: call.accountContext.sharedContext, account: call.accountContext.account, forceTheme: defaultDarkPresentationTheme, title: title, text: text, placeholder: EnginePeer(chatPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak call] title in + let editController = voiceChatTitleEditController(context: call.accountContext, forceTheme: defaultDarkPresentationTheme, title: title, text: text, placeholder: EnginePeer(chatPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak call] title in guard let call = call else { return } @@ -574,7 +574,7 @@ public final class MediaStreamComponent: CombinedComponent { title = presentationData.strings.LiveStream_StartRecordingTitle text = presentationData.strings.LiveStream_StartRecordingTextVideo - let editController = voiceChatTitleEditController(sharedContext: call.accountContext.sharedContext, account: call.accountContext.account, forceTheme: defaultDarkPresentationTheme, title: title, text: text, placeholder: placeholder, value: nil, maxLength: 40, apply: { [weak call, weak controller] title in + let editController = voiceChatTitleEditController(context: call.accountContext, forceTheme: defaultDarkPresentationTheme, title: title, text: text, placeholder: placeholder, value: nil, maxLength: 40, apply: { [weak call, weak controller] title in guard let call = call, let controller = controller else { return } diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index fc1fac25..4cb912c6 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -10,6 +10,7 @@ import TelegramVoip import TelegramAudio import TelegramUIPreferences import TelegramPresentationData +import PresentationDataUtils import DeviceAccess import UniversalMediaPlayer import AccountContext @@ -2134,12 +2135,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } if case .anonymousNotAllowed = error { let presentationData = self.accountContext.sharedContext.currentPresentationData.with { $0 } - self.accountContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: self.isChannel ? presentationData.strings.LiveStream_AnonymousDisabledAlertText : presentationData.strings.VoiceChat_AnonymousDisabledAlertText, actions: [ + self.accountContext.sharedContext.mainWindow?.present(textAlertController(context: self.accountContext, title: nil, text: self.isChannel ? presentationData.strings.LiveStream_AnonymousDisabledAlertText : presentationData.strings.VoiceChat_AnonymousDisabledAlertText, actions: [ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {}) ]), on: .root, blockInteraction: false, completion: {}) } else if case .tooManyParticipants = error { let presentationData = self.accountContext.sharedContext.currentPresentationData.with { $0 } - self.accountContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: self.isChannel ? presentationData.strings.LiveStream_ChatFullAlertText : presentationData.strings.VoiceChat_ChatFullAlertText, actions: [ + self.accountContext.sharedContext.mainWindow?.present(textAlertController(context: self.accountContext, title: nil, text: self.isChannel ? presentationData.strings.LiveStream_ChatFullAlertText : presentationData.strings.VoiceChat_ChatFullAlertText, actions: [ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {}) ]), on: .root, blockInteraction: false, completion: {}) } else if case .invalidJoinAsPeer = error { @@ -3805,7 +3806,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { break } - self.accountContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [ + self.accountContext.sharedContext.mainWindow?.present(textAlertController(context: self.accountContext, title: nil, text: errorText, actions: [ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {}) ]), on: .root, blockInteraction: false, completion: {}) } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScheduledInfoComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatScheduledInfoComponent.swift index c9ae7d1d..1143f2ea 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScheduledInfoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScheduledInfoComponent.swift @@ -13,6 +13,22 @@ private let pink = UIColor(rgb: 0xef436c) private let latePurple = UIColor(rgb: 0x974aa9) private let latePink = UIColor(rgb: 0xf0436c) +private func textForTimeout(value: Int32) -> String { + if value < 3600 { + let minutes = value / 60 + let seconds = value % 60 + let secondsPadding = seconds < 10 ? "0" : "" + return "\(minutes):\(secondsPadding)\(seconds)" + } else { + let hours = value / 3600 + let minutes = (value % 3600) / 60 + let minutesPadding = minutes < 10 ? "0" : "" + let seconds = value % 60 + let secondsPadding = seconds < 10 ? "0" : "" + return "\(hours):\(minutesPadding)\(minutes):\(secondsPadding)\(seconds)" + } +} + final class VideoChatScheduledInfoComponent: Component { let timestamp: Int32 let strings: PresentationStrings diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index b5146f66..fcf96a12 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -691,7 +691,7 @@ final class VideoChatScreenComponent: Component { text = environment.strings.VoiceChat_EditTitleText } - let controller = voiceChatTitleEditController(sharedContext: groupCall.accountContext.sharedContext, account: groupCall.accountContext.account, forceTheme: environment.theme, title: title, text: text, placeholder: EnginePeer(chatPeer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak self] title in + let controller = voiceChatTitleEditController(context: groupCall.accountContext, forceTheme: environment.theme, title: title, text: text, placeholder: EnginePeer(chatPeer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak self] title in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } @@ -3448,7 +3448,6 @@ final class VideoChatScreenComponent: Component { pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift index e77e72bf..fed8bd66 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift @@ -469,7 +469,7 @@ extension VideoChatScreenComponent.View { } } - let controller = voiceChatTitleEditController(sharedContext: currentCall.accountContext.sharedContext, account: currentCall.accountContext.account, forceTheme: environment.theme, title: title, text: text, placeholder: placeholder, value: nil, maxLength: 40, apply: { [weak self] title in + let controller = voiceChatTitleEditController(context: currentCall.accountContext, forceTheme: environment.theme, title: title, text: text, placeholder: placeholder, value: nil, maxLength: 40, apply: { [weak self] title in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall, let peer = self.peer, let title else { return } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift index dff33a3b..3b031887 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift @@ -117,7 +117,7 @@ extension VideoChatScreenComponent.View { } else { maxBioLength = 100 } - let controller = voiceChatTitleEditController(sharedContext: currentCall.accountContext.sharedContext, account: currentCall.accountContext.account, forceTheme: environment.theme, title: environment.strings.VoiceChat_EditBioTitle, text: environment.strings.VoiceChat_EditBioText, placeholder: environment.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: environment.strings.VoiceChat_EditBioSave, value: participant.about, maxLength: maxBioLength, apply: { [weak self] bio in + let controller = voiceChatTitleEditController(context: currentCall.accountContext, forceTheme: environment.theme, title: environment.strings.VoiceChat_EditBioTitle, text: environment.strings.VoiceChat_EditBioText, placeholder: environment.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: environment.strings.VoiceChat_EditBioSave, value: participant.about, maxLength: maxBioLength, apply: { [weak self] bio in guard let self, let environment = self.environment, let currentCall = self.currentCall, let bio else { return } @@ -149,7 +149,7 @@ extension VideoChatScreenComponent.View { guard let self, let environment = self.environment, let currentCall = self.currentCall else { return } - let controller = voiceChatUserNameController(sharedContext: currentCall.accountContext.sharedContext, account: currentCall.accountContext.account, forceTheme: environment.theme, title: environment.strings.VoiceChat_ChangeNameTitle, firstNamePlaceholder: environment.strings.UserInfo_FirstNamePlaceholder, lastNamePlaceholder: environment.strings.UserInfo_LastNamePlaceholder, doneButtonTitle: environment.strings.VoiceChat_EditBioSave, firstName: peer.firstName, lastName: peer.lastName, maxLength: 128, apply: { [weak self] firstAndLastName in + let controller = voiceChatUserNameController(context: currentCall.accountContext, forceTheme: environment.theme, title: environment.strings.VoiceChat_ChangeNameTitle, firstNamePlaceholder: environment.strings.UserInfo_FirstNamePlaceholder, lastNamePlaceholder: environment.strings.UserInfo_LastNamePlaceholder, doneButtonTitle: environment.strings.VoiceChat_EditBioSave, firstName: peer.firstName, lastName: peer.lastName, maxLength: 128, apply: { [weak self] firstAndLastName in guard let self, let environment = self.environment, let currentCall = self.currentCall, let (firstName, lastName) = firstAndLastName else { return } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatActionItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatActionItem.swift index 320041de..c2822f5f 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatActionItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatActionItem.swift @@ -129,7 +129,7 @@ class VoiceChatActionItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.highlightContainerNode.addSubnode(self.highlightedBackgroundNode) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift index 58367150..fc623650 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift @@ -259,7 +259,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { self.actionContainerNode = ASDisplayNode() self.actionButtonNode = HighlightableButtonNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatMicrophoneNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatMicrophoneNode.swift index c644942d..b296906e 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatMicrophoneNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatMicrophoneNode.swift @@ -23,15 +23,15 @@ private final class VoiceChatMicrophoneNodeDrawingState: NSObject { } } -final class VoiceChatMicrophoneNode: ASDisplayNode { - class State: Equatable { - let muted: Bool - let color: UIColor - let filled: Bool - let shadowColor: UIColor? - let shadowBlur: CGFloat +public final class VoiceChatMicrophoneNode: ASDisplayNode { + public class State: Equatable { + public let muted: Bool + public let color: UIColor + public let filled: Bool + public let shadowColor: UIColor? + public let shadowBlur: CGFloat - init(muted: Bool, filled: Bool, color: UIColor, shadowColor: UIColor? = nil, shadowBlur: CGFloat = 0.0) { + public init(muted: Bool, filled: Bool, color: UIColor, shadowColor: UIColor? = nil, shadowBlur: CGFloat = 0.0) { self.muted = muted self.filled = filled self.color = color @@ -39,7 +39,7 @@ final class VoiceChatMicrophoneNode: ASDisplayNode { self.shadowBlur = shadowBlur } - static func ==(lhs: State, rhs: State) -> Bool { + public static func ==(lhs: State, rhs: State) -> Bool { if lhs.muted != rhs.muted { return false } @@ -77,13 +77,13 @@ final class VoiceChatMicrophoneNode: ASDisplayNode { private var state: State = State(muted: false, filled: false, color: .black) private var transitionContext: TransitionContext? - override init() { + override public init() { super.init() self.isOpaque = false } - func update(state: State, animated: Bool) { + public func update(state: State, animated: Bool) { var animated = animated if !self.hasState { self.hasState = true diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index 81722b4d..820b98cd 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -344,7 +344,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { self.highlightedBackgroundNode = ASDisplayNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.isAccessibilityElement = true diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTileGridNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTileGridNode.swift index 1b8b43df..037905bd 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTileGridNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTileGridNode.swift @@ -257,7 +257,7 @@ final class VoiceChatTilesGridItemNode: ListViewItemNode { self.limitLabel = TextNode() self.limitLabel.alpha = 0.0 - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.cornersNode) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift index 3fb3b29b..6f4abcd3 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift @@ -12,6 +12,22 @@ private let pink = UIColor(rgb: 0xef436c) private let latePurple = UIColor(rgb: 0x974aa9) private let latePink = UIColor(rgb: 0xf0436c) +private func textForTimeout(value: Int32) -> String { + if value < 3600 { + let minutes = value / 60 + let seconds = value % 60 + let secondsPadding = seconds < 10 ? "0" : "" + return "\(minutes):\(secondsPadding)\(seconds)" + } else { + let hours = value / 3600 + let minutes = (value % 3600) / 60 + let minutesPadding = minutes < 10 ? "0" : "" + let seconds = value % 60 + let secondsPadding = seconds < 10 ? "0" : "" + return "\(hours):\(minutesPadding)\(minutes):\(secondsPadding)\(seconds)" + } +} + final class VoiceChatTimerNode: ASDisplayNode { private let strings: PresentationStrings private let dateTimeFormat: PresentationDateTimeFormat diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift index 1ba276b3..505fb7d2 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift @@ -7,767 +7,179 @@ import TelegramCore import TelegramPresentationData import AccountContext import UrlEscaping +import ComponentFlow +import AlertComponent +import AlertInputFieldComponent -private final class VoiceChatTitleEditInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { - private var theme: PresentationTheme - private let backgroundNode: ASImageNode - private let textInputNode: EditableTextNode - private let placeholderNode: ASTextNode - private let clearButton: HighlightableButtonNode - - var updateHeight: (() -> Void)? - var complete: (() -> Void)? - var textChanged: ((String) -> Void)? - - private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0) - private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0) - - var text: String { - get { - return self.textInputNode.attributedText?.string ?? "" - } - set { - self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor) - self.placeholderNode.isHidden = !newValue.isEmpty - if self.textInputNode.isFirstResponder() { - self.clearButton.isHidden = newValue.isEmpty - } else { - self.clearButton.isHidden = true - } - } - } - - var placeholder: String = "" { - didSet { - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - } - } - - private let maxLength: Int - - init(theme: PresentationTheme, placeholder: String, maxLength: Int, returnKeyType: UIReturnKeyType = .done) { - self.theme = theme - self.maxLength = maxLength - - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - - self.textInputNode = EditableTextNode() - self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor] - self.textInputNode.clipsToBounds = true - self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) - self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0) - self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance - self.textInputNode.keyboardType = .default - self.textInputNode.autocapitalizationType = .sentences - self.textInputNode.returnKeyType = returnKeyType - self.textInputNode.autocorrectionType = .default - self.textInputNode.tintColor = theme.actionSheet.controlAccentColor - - self.placeholderNode = ASTextNode() - self.placeholderNode.isUserInteractionEnabled = false - self.placeholderNode.displaysAsynchronously = false - self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - - self.clearButton = HighlightableButtonNode() - self.clearButton.imageNode.displaysAsynchronously = false - self.clearButton.imageNode.displayWithoutProcessing = true - self.clearButton.displaysAsynchronously = false - self.clearButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.actionSheet.inputClearButtonColor), for: []) - self.clearButton.isHidden = true - - super.init() - - self.textInputNode.delegate = self - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.textInputNode) - self.addSubnode(self.placeholderNode) - self.addSubnode(self.clearButton) - - self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside) - } - - func updateTheme(_ theme: PresentationTheme) { - self.theme = theme - - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor - self.clearButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.actionSheet.inputClearButtonColor), for: []) - } - - func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let textFieldHeight = self.calculateTextFieldMetrics(width: width) - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - - let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom)) - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - - let placeholderSize = self.placeholderNode.measure(backgroundFrame.size) - transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) - - transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - 20.0, height: backgroundFrame.size.height))) - - if let image = self.clearButton.image(for: []) { - transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX - 8.0 - image.size.width, y: backgroundFrame.minY + floor((backgroundFrame.size.height - image.size.height) / 2.0)), size: image.size)) - } - - return panelHeight - } - - func activateInput() { - self.textInputNode.becomeFirstResponder() - } - - func deactivateInput() { - self.textInputNode.resignFirstResponder() - } - - @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { - self.updateTextNodeText(animated: true) - self.textChanged?(editableTextNode.textView.text) - self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty - self.clearButton.isHidden = !self.placeholderNode.isHidden - } - - func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) { - self.clearButton.isHidden = (editableTextNode.textView.text ?? "").isEmpty - } - - func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) { - self.clearButton.isHidden = true - } - - func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text) - if updatedText.count > maxLength { - self.textInputNode.layer.addShakeAnimation() - return false - } - if text == "\n" { - self.complete?() - return false - } - return true - } - - private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right - 20.0, height: CGFloat.greatestFiniteMagnitude)).height)) - - return min(61.0, max(33.0, unboundTextFieldHeight)) - } - - private func updateTextNodeText(animated: Bool) { - let backgroundInsets = self.backgroundInsets - - let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width) - - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - if !self.bounds.size.height.isEqual(to: panelHeight) { - self.updateHeight?() - } - } - - @objc func clearPressed() { - self.placeholderNode.isHidden = false - self.clearButton.isHidden = true - - self.textInputNode.attributedText = nil - self.updateHeight?() - } -} - -private final class VoiceChatTitleEditAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - private let title: String - private let text: String - - private let titleNode: ASTextNode - private let textNode: ASTextNode - let inputFieldNode: VoiceChatTitleEditInputFieldNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let disposable = MetaDisposable() - - private var validLayout: CGSize? - - private let hapticFeedback = HapticFeedback() - - var complete: (() -> Void)? { - didSet { - self.inputFieldNode.complete = self.complete - } - } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?, maxLength: Int) { - self.strings = strings - self.title = title - self.text = text - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 2 - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 8 - - self.inputFieldNode = VoiceChatTitleEditInputFieldNode(theme: ptheme, placeholder: placeholder, maxLength: maxLength) - self.inputFieldNode.text = value ?? "" - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - - self.addSubnode(self.inputFieldNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.inputFieldNode.updateHeight = { [weak self] in - if let strongSelf = self { - if let _ = strongSelf.validLayout { - strongSelf.requestLayout?(.animated(duration: 0.15, curve: .spring)) - } - } - } - - self.updateTheme(theme) - } - - deinit { - self.disposable.dispose() - } - - var value: String { - return self.inputFieldNode.text - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) - - let hadValidLayout = self.validLayout != nil - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - let spacing: CGFloat = 5.0 - - let titleSize = self.titleNode.measure(measureSize) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 4.0 - - let textSize = self.textNode.measure(measureSize) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 6.0 + spacing - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) - - var contentWidth = max(titleSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - let inputFieldWidth = resultWidth - let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) - let inputHeight = inputFieldHeight - transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight)) - transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0) - - let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if !hadValidLayout { - self.inputFieldNode.activateInput() - } - - return resultSize - } - - func animateError() { - self.inputFieldNode.layer.addShakeAnimation() - self.hapticFeedback.error() - } -} - -func voiceChatTitleEditController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, text: String, placeholder: String, doneButtonTitle: String? = nil, value: String?, maxLength: Int, apply: @escaping (String?) -> Void) -> AlertController { - var presentationData = sharedContext.currentPresentationData.with { $0 } - if let forceTheme = forceTheme { +func voiceChatTitleEditController( + context: AccountContext, + forceTheme: PresentationTheme?, + title: String, + text: String, + placeholder: String, + doneButtonTitle: String? = nil, + value: String?, + maxLength: Int, + apply: @escaping (String?) -> Void +) -> ViewController { + var presentationData = context.sharedContext.currentPresentationData.with { $0 } + if let forceTheme { presentationData = presentationData.withUpdated(theme: forceTheme) } - - var dismissImpl: ((Bool) -> Void)? + let strings = presentationData.strings + + let inputState = AlertInputFieldComponent.ExternalState() + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + var applyImpl: (() -> Void)? - - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - }), TextAlertAction(type: .defaultAction, title: doneButtonTitle ?? presentationData.strings.Common_Done, action: { - applyImpl?() - })] - - let contentNode = VoiceChatTitleEditAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength) - contentNode.complete = { - applyImpl?() - } - applyImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - dismissImpl?(true) - + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + initialValue: value, + placeholder: placeholder, + characterLimit: maxLength, + hasClearButton: true, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) + + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: doneButtonTitle ?? strings.Common_Done, type: .default, action: { + applyImpl?() + }) + ], + updatedPresentationData: (presentationData, .single(presentationData)) + ) + applyImpl = { let previousValue = value ?? "" - let newValue = contentNode.value.trimmingCharacters(in: .whitespacesAndNewlines) + let newValue = inputState.value.trimmingCharacters(in: .whitespacesAndNewlines) apply(previousValue != newValue || value == nil ? newValue : nil) } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in - var presentationData = presentationData - if let forceTheme = forceTheme { - presentationData = presentationData.withUpdated(theme: forceTheme) - } - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.inputFieldNode.updateTheme(presentationData.theme) - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - } - dismissImpl = { [weak controller, weak contentNode] animated in - contentNode?.inputFieldNode.deactivateInput() - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + return alertController } -private final class VoiceChatUserNameEditAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - private let title: String - - private let titleNode: ASTextNode - let firstNameInputFieldNode: VoiceChatTitleEditInputFieldNode - let lastNameInputFieldNode: VoiceChatTitleEditInputFieldNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let disposable = MetaDisposable() - - private var validLayout: CGSize? - - private let hapticFeedback = HapticFeedback() - - var complete: (() -> Void)? { - didSet { - self.lastNameInputFieldNode.complete = self.complete - } - } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, firstNamePlaceholder: String, lastNamePlaceholder: String, firstNameValue: String?, lastNameValue: String?, maxLength: Int) { - self.strings = strings - self.title = title - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 2 - - self.firstNameInputFieldNode = VoiceChatTitleEditInputFieldNode(theme: ptheme, placeholder: firstNamePlaceholder, maxLength: maxLength, returnKeyType: .next) - self.firstNameInputFieldNode.text = firstNameValue ?? "" - - self.lastNameInputFieldNode = VoiceChatTitleEditInputFieldNode(theme: ptheme, placeholder: lastNamePlaceholder, maxLength: maxLength) - self.lastNameInputFieldNode.text = lastNameValue ?? "" - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - - self.addSubnode(self.firstNameInputFieldNode) - self.addSubnode(self.lastNameInputFieldNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - - self.firstNameInputFieldNode.complete = { [weak self] in - self?.lastNameInputFieldNode.activateInput() - } - } - - deinit { - self.disposable.dispose() - } - - var firstName: String { - return self.firstNameInputFieldNode.text - } - - var lastName: String { - return self.lastNameInputFieldNode.text - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) - - let hadValidLayout = self.validLayout != nil - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - let spacing: CGFloat = 0.0 - - let titleSize = self.titleNode.measure(measureSize) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 4.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) - - var contentWidth = max(titleSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - let inputFieldWidth = resultWidth - let firstInputFieldHeight = self.firstNameInputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) - transition.updateFrame(node: self.firstNameInputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: firstInputFieldHeight)) - - origin.y += firstInputFieldHeight + spacing - - let lastInputFieldHeight = self.lastNameInputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) - transition.updateFrame(node: self.lastNameInputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: lastInputFieldHeight)) - - let resultSize = CGSize(width: resultWidth, height: titleSize.height + firstInputFieldHeight + spacing + lastInputFieldHeight + actionsHeight + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if !hadValidLayout { - self.firstNameInputFieldNode.activateInput() - } - - return resultSize - } - - func animateError() { - if self.firstNameInputFieldNode.text.isEmpty { - self.firstNameInputFieldNode.layer.addShakeAnimation() - } - self.hapticFeedback.error() - } -} - -func voiceChatUserNameController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, firstNamePlaceholder: String, lastNamePlaceholder: String, doneButtonTitle: String? = nil, firstName: String?, lastName: String?, maxLength: Int, apply: @escaping ((String, String)?) -> Void) -> AlertController { - var presentationData = sharedContext.currentPresentationData.with { $0 } - if let forceTheme = forceTheme { +func voiceChatUserNameController( + context: AccountContext, + forceTheme: PresentationTheme?, + title: String, + firstNamePlaceholder: String, + lastNamePlaceholder: String, + doneButtonTitle: String? = nil, + firstName: String?, + lastName: String?, + maxLength: Int, + apply: @escaping ((String, String)?) -> Void +) -> ViewController { + var presentationData = context.sharedContext.currentPresentationData.with { $0 } + if let forceTheme { presentationData = presentationData.withUpdated(theme: forceTheme) } + let strings = presentationData.strings + + let firstNameState = AlertInputFieldComponent.ExternalState() + let lastNameState = AlertInputFieldComponent.ExternalState() + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: title) + ) + )) - var dismissImpl: ((Bool) -> Void)? + var nextImpl: (() -> Void)? var applyImpl: (() -> Void)? + content.append(AnyComponentWithIdentity( + id: "firstName", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + initialValue: firstName, + placeholder: firstNamePlaceholder, + characterLimit: maxLength, + hasClearButton: true, + returnKeyType: .next, + isInitiallyFocused: true, + externalState: firstNameState, + returnKeyAction: { + nextImpl?() + } + ) + ) + )) - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - }), TextAlertAction(type: .defaultAction, title: doneButtonTitle ?? presentationData.strings.Common_Done, action: { - applyImpl?() - })] - - let contentNode = VoiceChatUserNameEditAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, firstNamePlaceholder: firstNamePlaceholder, lastNamePlaceholder: lastNamePlaceholder, firstNameValue: firstName, lastNameValue: lastName, maxLength: maxLength) - contentNode.complete = { - applyImpl?() + content.append(AnyComponentWithIdentity( + id: "lastName", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + initialValue: lastName, + placeholder: lastNamePlaceholder, + characterLimit: maxLength, + hasClearButton: true, + isInitiallyFocused: false, + externalState: lastNameState, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) + + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: doneButtonTitle ?? strings.Common_Done, type: .default, action: { + applyImpl?() + }) + ], + updatedPresentationData: (presentationData, .single(presentationData)) + ) + nextImpl = { + lastNameState.activateInput() } - applyImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - + applyImpl = { let previousFirstName = firstName ?? "" let previousLastName = lastName ?? "" - let newFirstName = contentNode.firstName.trimmingCharacters(in: .whitespacesAndNewlines) - let newLastName = contentNode.lastName.trimmingCharacters(in: .whitespacesAndNewlines) + let newFirstName = firstNameState.value.trimmingCharacters(in: .whitespacesAndNewlines) + let newLastName = lastNameState.value.trimmingCharacters(in: .whitespacesAndNewlines) if newFirstName.isEmpty { - contentNode.animateError() + firstNameState.animateError() return } - - dismissImpl?(true) - + if previousFirstName != newFirstName || previousLastName != newLastName { apply((newFirstName, newLastName)) } else { apply(nil) } } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in - var presentationData = presentationData - if let forceTheme = forceTheme { - presentationData = presentationData.withUpdated(theme: forceTheme) - } - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.firstNameInputFieldNode.updateTheme(presentationData.theme) - contentNode?.lastNameInputFieldNode.updateTheme(presentationData.theme) - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - } - dismissImpl = { [weak controller, weak contentNode] animated in - contentNode?.firstNameInputFieldNode.deactivateInput() - contentNode?.lastNameInputFieldNode.deactivateInput() - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + return alertController } diff --git a/submodules/TelegramCore/BUILD b/submodules/TelegramCore/BUILD index fe9bc15d..53c9a721 100644 --- a/submodules/TelegramCore/BUILD +++ b/submodules/TelegramCore/BUILD @@ -25,6 +25,7 @@ swift_library( "//submodules/Emoji", "//submodules/TelegramCore/FlatBuffers", "//submodules/TelegramCore/FlatSerialization", + "//submodules/MurMurHash32" ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 7c0a3db5..fbc85e9e 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -139,6 +139,7 @@ enum AccountStateMutationOperation { case UpdateMonoForumNoPaidException(peerId: PeerId, threadId: Int64, isFree: Bool) case UpdateStarGiftAuctionState(giftId: Int64, state: GiftAuctionContext.State.AuctionState) case UpdateStarGiftAuctionMyState(giftId: Int64, state: GiftAuctionContext.State.MyState) + case UpdateEmojiGameInfo(info: EmojiGameInfo) } struct HoleFromPreviousState { @@ -737,9 +738,13 @@ struct AccountMutableState { self.addOperation(.UpdateStarGiftAuctionMyState(giftId: giftId, state: state)) } + mutating func updateEmojiGameInfo(info: EmojiGameInfo) { + self.addOperation(.UpdateEmojiGameInfo(info: info)) + } + mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo: break case let .AddMessages(messages, location): for message in messages { @@ -892,6 +897,7 @@ struct AccountReplayedFinalState { let addedConferenceInvitationMessagesIds: [MessageId] let updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] let updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] + let updatedEmojiGameInfo: EmojiGameInfo? } struct AccountFinalStateEvents { @@ -927,12 +933,13 @@ struct AccountFinalStateEvents { let addedConferenceInvitationMessagesIds: [MessageId] let updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] let updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] + let updatedEmojiGameInfo: EmojiGameInfo? var isEmpty: Bool { - return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty + return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty && self.updatedEmojiGameInfo == nil } - init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:]) { + init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:], updatedEmojiGameInfo: EmojiGameInfo? = nil) { self.addedIncomingMessageIds = addedIncomingMessageIds self.addedReactionEvents = addedReactionEvents self.wasScheduledMessageIds = wasScheduledMessageIds @@ -965,6 +972,7 @@ struct AccountFinalStateEvents { self.addedConferenceInvitationMessagesIds = addedConferenceInvitationMessagesIds self.updatedStarGiftAuctionState = updatedStarGiftAuctionState self.updatedStarGiftAuctionMyState = updatedStarGiftAuctionMyState + self.updatedEmojiGameInfo = updatedEmojiGameInfo } init(state: AccountReplayedFinalState) { @@ -1000,6 +1008,7 @@ struct AccountFinalStateEvents { self.addedConferenceInvitationMessagesIds = state.addedConferenceInvitationMessagesIds self.updatedStarGiftAuctionState = state.updatedStarGiftAuctionState self.updatedStarGiftAuctionMyState = state.updatedStarGiftAuctionMyState + self.updatedEmojiGameInfo = state.updatedEmojiGameInfo } func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents { @@ -1068,7 +1077,8 @@ struct AccountFinalStateEvents { reportMessageDelivery: reportMessageDelivery, addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds, updatedStarGiftAuctionState: self.updatedStarGiftAuctionState.merging(other.updatedStarGiftAuctionState, uniquingKeysWith: { lhs, _ in lhs }), - updatedStarGiftAuctionMyState: self.updatedStarGiftAuctionMyState.merging(other.updatedStarGiftAuctionMyState, uniquingKeysWith: { lhs, _ in lhs }) + updatedStarGiftAuctionMyState: self.updatedStarGiftAuctionMyState.merging(other.updatedStarGiftAuctionMyState, uniquingKeysWith: { lhs, _ in lhs }), + updatedEmojiGameInfo: self.updatedEmojiGameInfo ) } } diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index d2031258..9897b02c 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -240,6 +240,7 @@ private var declaredEncodables: Void = { declareEncodable(PublishedSuggestedPostMessageAttribute.self, f: { PublishedSuggestedPostMessageAttribute(decoder: $0) }) declareEncodable(TelegramMediaLiveStream.self, f: { TelegramMediaLiveStream(decoder: $0) }) declareEncodable(ScheduledRepeatAttribute.self, f: { ScheduledRepeatAttribute(decoder: $0) }) + declareEncodable(SummarizationMessageAttribute.self, f: { SummarizationMessageAttribute(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/AntiDelete/LocalEditManager.swift b/submodules/TelegramCore/Sources/AntiDelete/LocalEditManager.swift new file mode 100644 index 00000000..75f9af07 --- /dev/null +++ b/submodules/TelegramCore/Sources/AntiDelete/LocalEditManager.swift @@ -0,0 +1,85 @@ +import Foundation + +/// ะœะตะฝะตะดะถะตั€ ะดะปั ะปะพะบะฐะปัŒะฝะพะณะพ ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธั ัะพะพะฑั‰ะตะฝะธะน (ั‚ะพะปัŒะบะพ ะฝะฐ ัั‚ะพั€ะพะฝะต ะบะปะธะตะฝั‚ะฐ) +/// ะฅั€ะฐะฝะธั‚ ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธั ะฒ ะฟะฐะผัั‚ะธ - ัะฑั€ะฐัั‹ะฒะฐัŽั‚ัั ะฟั€ะธ ะฟะตั€ะตะทะฐะฟัƒัะบะต ะฟั€ะธะปะพะถะตะฝะธั +public final class LocalEditManager { + + public static let shared = LocalEditManager() + + // MARK: - Storage + + /// ะฅั€ะฐะฝะธะปะธั‰ะต ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธะน: "peerId_messageId" -> ะฝะพะฒั‹ะน ั‚ะตะบัั‚ + private var edits: [String: String] = [:] + private let lock = NSLock() + + private init() {} + + // MARK: - Public API + + /// ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ะปะพะบะฐะปัŒะฝะพะต ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธะต ะดะปั ัะพะพะฑั‰ะตะฝะธั + /// - Parameters: + /// - peerId: ID ั‡ะฐั‚ะฐ + /// - messageId: ID ัะพะพะฑั‰ะตะฝะธั + /// - newText: ะะพะฒั‹ะน ั‚ะตะบัั‚ ัะพะพะฑั‰ะตะฝะธั + public func setLocalEdit(peerId: Int64, messageId: Int32, newText: String) { + let key = makeKey(peerId: peerId, messageId: messageId) + lock.lock() + defer { lock.unlock() } + edits[key] = newText + } + + /// ะŸะพะปัƒั‡ะธั‚ัŒ ะปะพะบะฐะปัŒะฝะพะต ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธะต ะดะปั ัะพะพะฑั‰ะตะฝะธั + /// - Parameters: + /// - peerId: ID ั‡ะฐั‚ะฐ + /// - messageId: ID ัะพะพะฑั‰ะตะฝะธั + /// - Returns: ะžั‚ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะฝั‹ะน ั‚ะตะบัั‚ ะธะปะธ nil ะตัะปะธ ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธั ะฝะตั‚ + public func getLocalEdit(peerId: Int64, messageId: Int32) -> String? { + let key = makeKey(peerId: peerId, messageId: messageId) + lock.lock() + defer { lock.unlock() } + return edits[key] + } + + /// ะฃะดะฐะปะธั‚ัŒ ะปะพะบะฐะปัŒะฝะพะต ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธะต ะดะปั ัะพะพะฑั‰ะตะฝะธั + /// - Parameters: + /// - peerId: ID ั‡ะฐั‚ะฐ + /// - messageId: ID ัะพะพะฑั‰ะตะฝะธั + public func removeLocalEdit(peerId: Int64, messageId: Int32) { + let key = makeKey(peerId: peerId, messageId: messageId) + lock.lock() + defer { lock.unlock() } + edits.removeValue(forKey: key) + } + + /// ะŸั€ะพะฒะตั€ะธั‚ัŒ ะฝะฐะปะธั‡ะธะต ะปะพะบะฐะปัŒะฝะพะณะพ ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธั + /// - Parameters: + /// - peerId: ID ั‡ะฐั‚ะฐ + /// - messageId: ID ัะพะพะฑั‰ะตะฝะธั + /// - Returns: true ะตัะปะธ ะตัั‚ัŒ ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธะต + public func hasLocalEdit(peerId: Int64, messageId: Int32) -> Bool { + let key = makeKey(peerId: peerId, messageId: messageId) + lock.lock() + defer { lock.unlock() } + return edits[key] != nil + } + + /// ะžั‡ะธัั‚ะธั‚ัŒ ะฒัะต ะปะพะบะฐะปัŒะฝั‹ะต ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธั + public func clearAllEdits() { + lock.lock() + defer { lock.unlock() } + edits.removeAll() + } + + /// ะšะพะปะธั‡ะตัั‚ะฒะพ ะฐะบั‚ะธะฒะฝั‹ั… ั€ะตะดะฐะบั‚ะธั€ะพะฒะฐะฝะธะน + public var editCount: Int { + lock.lock() + defer { lock.unlock() } + return edits.count + } + + // MARK: - Private + + private func makeKey(peerId: Int64, messageId: Int32) -> String { + return "\(peerId)_\(messageId)" + } +} diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index f32fbbe7..ca8956b5 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -128,7 +128,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let chatPeerId = messagePeerId return chatPeerId.peerId case let .messageEmpty(_, _, peerId): @@ -144,7 +144,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = chatPeerId.peerId var result = [peerId] @@ -279,7 +279,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch message { - case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyTo = replyTo { let peerId: PeerId = chatPeerId.peerId @@ -457,8 +457,17 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI } return (TelegramMediaTodo(flags: flags, text: todoText, textEntities: todoEntities, items: list.map(TelegramMediaTodo.Item.init(apiItem:)), completions: todoCompletions), nil, nil, nil, nil, nil) } - case let .messageMediaDice(value, emoticon): - return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil, nil, nil) + case let .messageMediaDice(_, value, emoticon, apiGameOutcome): + var gameOutcome: TelegramMediaDice.GameOutcome? + var tonAmount: Int64? + switch apiGameOutcome { + case let .emojiGameOutcome(seed, stakeTonAmount, outcomeTonAmount): + gameOutcome = TelegramMediaDice.GameOutcome(seed: seed.makeData(), tonAmount: outcomeTonAmount) + tonAmount = stakeTonAmount + default: + break + } + return (TelegramMediaDice(emoji: emoticon, tonAmount: tonAmount, value: value, gameOutcome: gameOutcome), nil, nil, nil, nil, nil) case let .messageMediaStory(flags, peerId, id, _): let isMention = (flags & (1 << 1)) != 0 return (TelegramMediaStory(storyId: StoryId(peerId: peerId.peerId, id: id), isMention: isMention), nil, nil, nil, nil, nil) @@ -703,7 +712,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, scheduledRepeatPeriod): + case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, scheduledRepeatPeriod, summaryFromLanguage): var attributes: [MessageAttribute] = [] if (flags2 & (1 << 4)) != 0 { @@ -964,6 +973,10 @@ extension StoreMessage { attributes.append(ScheduledRepeatAttribute(repeatPeriod: scheduledRepeatPeriod)) } + if let summaryFromLanguage { + attributes.append(SummarizationMessageAttribute(fromLang: summaryFromLanguage)) + } + var entitiesAttribute: TextEntitiesMessageAttribute? if let entities = entities, !entities.isEmpty { let attribute = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities)) @@ -988,7 +1001,7 @@ extension StoreMessage { } } - if (flags & (1 << 17)) != 0 { + if (flags & (1 << 19)) != 0 { attributes.append(ContentRequiresValidationMessageAttribute()) } @@ -1174,7 +1187,7 @@ extension StoreMessage { threadId = 1 } - if (flags & (1 << 17)) != 0 { + if (flags & (1 << 19)) != 0 { attributes.append(ContentRequiresValidationMessageAttribute()) } diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 1940b8d0..4f78188e 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -343,8 +343,26 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post let inputTodo = Api.InputMedia.inputMediaTodo(todo: .todoList(flags: flags, title: .textWithEntities(text: todo.text, entities: apiEntitiesFromMessageTextEntities(todo.textEntities, associatedPeers: SimpleDictionary())), list: todo.items.map { $0.apiItem })) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputTodo, text), reuploadInfo: nil, cacheReferenceKey: nil))) } else if let dice = media as? TelegramMediaDice { - let inputDice = Api.InputMedia.inputMediaDice(emoticon: dice.emoji) - return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputDice, text), reuploadInfo: nil, cacheReferenceKey: nil))) + if let tonAmount = dice.tonAmount { + let seedBytes = malloc(32)! + let _ = SecRandomCopyBytes(nil, 32, seedBytes.assumingMemoryBound(to: UInt8.self)) + let clientSeed = MemoryBuffer(memory: seedBytes, capacity: 32, length: 32, freeWhenDone: true) + + return postbox.transaction { transaction -> Signal in + let gameInfo = currentEmojiGameInfo(transaction: transaction) + if case let .available(info) = gameInfo { + let inputStakeDice = Api.InputMedia.inputMediaStakeDice(gameHash: info.gameHash, tonAmount: tonAmount, clientSeed: Buffer(buffer: clientSeed)) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputStakeDice, text), reuploadInfo: nil, cacheReferenceKey: nil))) + } else { + return .fail(.generic) + } + } + |> castError(PendingMessageUploadError.self) + |> switchToLatest + } else { + let inputDice = Api.InputMedia.inputMediaDice(emoticon: dice.emoji) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputDice, text), reuploadInfo: nil, cacheReferenceKey: nil))) + } } else if let webPage = media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content { var flags: Int32 = 0 flags |= 1 << 2 diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 4c823903..0f6e050e 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1020,6 +1020,8 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) updatedState.updateMinAvailableMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: minId)) case let .updateDeleteMessages(messages, _, _): + // Note: Actual archiving happens in DeleteMessagesWithGlobalIds handler + // where we have access to transaction and can get full message content updatedState.deleteMessagesWithGlobalIds(messages) case let .updatePinnedMessages(flags, peer, messages, _, _): let peerId = peer.peerId @@ -1906,6 +1908,8 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: } case let .updateStarGiftAuctionUserState(giftId, userState): updatedState.updateStarGiftAuctionMyState(giftId: giftId, state: GiftAuctionContext.State.MyState(apiAuctionUserState: userState)) + case let .updateEmojiGameInfo(info): + updatedState.updateEmojiGameInfo(info: EmojiGameInfo(apiEmojiGameInfo: info)) default: break } @@ -3629,7 +3633,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddQuickReplyMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -3772,6 +3776,7 @@ func replayFinalState( var reportMessageDelivery = Set() var updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:] var updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:] + var updatedEmojiGameInfo: EmojiGameInfo? var holesFromPreviousStateMessageIds: [MessageId] = [] var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:] @@ -4224,18 +4229,166 @@ func replayFinalState( } } case let .DeleteMessagesWithGlobalIds(ids): - var resourceIds: [MediaResourceId] = [] - transaction.deleteMessagesWithGlobalIds(ids, forEachMedia: { media in - addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds) - }) - if !resourceIds.isEmpty { - let _ = mediaBox.removeCachedResources(Array(Set(resourceIds)), force: true).start() + // ANTI-DELETE: Archive messages with full content before deletion + if AntiDeleteManager.shared.isEnabled { + let messageIds = transaction.messageIdsForGlobalIds(ids) + for (index, messageId) in messageIds.enumerated() { + if let message = transaction.getMessage(messageId) { + let globalId = index < ids.count ? ids[index] : 0 + + // Extract text content + let textContent = message.text + + // Extract media description + var mediaDesc: String? = nil + for media in message.media { + switch media { + case let image as TelegramMediaImage: + mediaDesc = "๐Ÿ“ท Photo" + if let largest = image.representations.last { + mediaDesc = "๐Ÿ“ท Photo \(largest.dimensions.width)x\(largest.dimensions.height)" + } + case let file as TelegramMediaFile: + if file.isVideo { + mediaDesc = "๐ŸŽฌ Video" + } else if file.isVoice { + mediaDesc = "๐ŸŽค Voice Message" + } else if file.isInstantVideo { + mediaDesc = "๐Ÿ“น Video Message" + } else if file.isSticker { + mediaDesc = "๐ŸŽญ Sticker" + } else { + mediaDesc = "๐Ÿ“Ž \(file.fileName ?? "File")" + } + case is TelegramMediaContact: + mediaDesc = "๐Ÿ‘ค Contact" + case is TelegramMediaMap: + mediaDesc = "๐Ÿ“ Location" + case let poll as TelegramMediaPoll: + mediaDesc = "๐Ÿ“Š Poll: \(poll.text)" + default: + break + } + } + + AntiDeleteManager.shared.archiveMessage( + globalId: globalId, + peerId: messageId.peerId.toInt64(), + messageId: messageId.id, + timestamp: message.timestamp, + authorId: message.author?.id.toInt64(), + text: textContent, + forwardAuthorId: message.forwardInfo?.author?.id.toInt64(), + mediaDescription: mediaDesc + ) + } + } + } + + // ANTI-DELETE: Mark messages as deleted instead of removing them + if AntiDeleteManager.shared.isEnabled { + let messageIds = transaction.messageIdsForGlobalIds(ids) + for messageId in messageIds { + // Mark as deleted for icon display + AntiDeleteManager.shared.markAsDeleted(peerId: messageId.peerId.toInt64(), messageId: messageId.id) + + transaction.updateMessage(messageId, update: { currentMessage in + var attributes = currentMessage.attributes + // Don't add duplicate DeletedMessageAttribute + if !attributes.contains(where: { $0 is DeletedMessageAttribute }) { + attributes.append(DeletedMessageAttribute(deletedAt: Int32(Date().timeIntervalSince1970))) + } + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + // Keep original text, no prefix needed - icon will show deleted status + return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } + } else { + var resourceIds: [MediaResourceId] = [] + transaction.deleteMessagesWithGlobalIds(ids, forEachMedia: { media in + addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds) + }) + if !resourceIds.isEmpty { + let _ = mediaBox.removeCachedResources(Array(Set(resourceIds)), force: true).start() + } } deletedMessageIds.append(contentsOf: ids.map { .global($0) }) case let .DeleteMessages(ids): - _internal_deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, manualAddMessageThreadStatsDifference: { id, add, remove in - addMessageThreadStatsDifference(threadKey: id, remove: remove, addedMessagePeer: nil, addedMessageId: nil, isOutgoing: false) - }) + // ANTI-DELETE: Archive channel messages with full content before deletion + if AntiDeleteManager.shared.isEnabled { + for messageId in ids { + if let message = transaction.getMessage(messageId) { + // Extract text content + let textContent = message.text + + // Extract media description + var mediaDesc: String? = nil + for media in message.media { + switch media { + case let image as TelegramMediaImage: + mediaDesc = "๐Ÿ“ท Photo" + if let largest = image.representations.last { + mediaDesc = "๐Ÿ“ท Photo \(largest.dimensions.width)x\(largest.dimensions.height)" + } + case let file as TelegramMediaFile: + if file.isVideo { + mediaDesc = "๐ŸŽฌ Video" + } else if file.isVoice { + mediaDesc = "๐ŸŽค Voice Message" + } else if file.isInstantVideo { + mediaDesc = "๐Ÿ“น Video Message" + } else if file.isSticker { + mediaDesc = "๐ŸŽญ Sticker" + } else { + mediaDesc = "๐Ÿ“Ž \(file.fileName ?? "File")" + } + case is TelegramMediaContact: + mediaDesc = "๐Ÿ‘ค Contact" + case is TelegramMediaMap: + mediaDesc = "๐Ÿ“ Location" + case let poll as TelegramMediaPoll: + mediaDesc = "๐Ÿ“Š Poll: \(poll.text)" + default: + break + } + } + + AntiDeleteManager.shared.archiveMessage( + globalId: messageId.id, // Use message id as globalId for channel messages + peerId: messageId.peerId.toInt64(), + messageId: messageId.id, + timestamp: message.timestamp, + authorId: message.author?.id.toInt64(), + text: textContent, + forwardAuthorId: message.forwardInfo?.author?.id.toInt64(), + mediaDescription: mediaDesc + ) + } + } + } + + // ANTI-DELETE: Mark messages as deleted instead of removing them + if AntiDeleteManager.shared.isEnabled { + for messageId in ids { + // Mark as deleted for icon display + AntiDeleteManager.shared.markAsDeleted(peerId: messageId.peerId.toInt64(), messageId: messageId.id) + + transaction.updateMessage(messageId, update: { currentMessage in + var attributes = currentMessage.attributes + // Don't add duplicate DeletedMessageAttribute + if !attributes.contains(where: { $0 is DeletedMessageAttribute }) { + attributes.append(DeletedMessageAttribute(deletedAt: Int32(Date().timeIntervalSince1970))) + } + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + // Keep original text, no prefix needed - icon will show deleted status + return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } + } else { + _internal_deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, manualAddMessageThreadStatsDifference: { id, add, remove in + addMessageThreadStatsDifference(threadKey: id, remove: remove, addedMessagePeer: nil, addedMessageId: nil, isOutgoing: false) + }) + } deletedMessageIds.append(contentsOf: ids.map { .messageId($0) }) case let .UpdateMinAvailableMessage(id): if let message = transaction.getMessage(id) { @@ -4294,6 +4447,14 @@ func replayFinalState( updatedAttributes.append(translation) } } + } else { + // GHOSTGRAM: Save original text before edit + EditHistoryManager.shared.saveOriginalText( + peerId: id.peerId.toInt64(), + messageId: id.id, + originalText: previousMessage.text, + editDate: Int32(Date().timeIntervalSince1970) + ) } if let previousFactCheckAttribute = previousMessage.attributes.first(where: { $0 is FactCheckMessageAttribute }) as? FactCheckMessageAttribute, let updatedFactCheckAttribute = message.attributes.first(where: { $0 is FactCheckMessageAttribute }) as? FactCheckMessageAttribute { @@ -5339,6 +5500,8 @@ func replayFinalState( updatedStarGiftAuctionState[giftId] = state case let .UpdateStarGiftAuctionMyState(giftId, state): updatedStarGiftAuctionMyState[giftId] = state + case let .UpdateEmojiGameInfo(info): + updatedEmojiGameInfo = info } } @@ -5889,6 +6052,7 @@ func replayFinalState( reportMessageDelivery: reportMessageDelivery, addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds, updatedStarGiftAuctionState: updatedStarGiftAuctionState, - updatedStarGiftAuctionMyState: updatedStarGiftAuctionMyState + updatedStarGiftAuctionMyState: updatedStarGiftAuctionMyState, + updatedEmojiGameInfo: updatedEmojiGameInfo ) } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index e9cce703..75c8b017 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -374,6 +374,7 @@ public final class AccountStateManager { private let appliedQtsPromise = Promise(nil) private let appliedQtsDisposable = MetaDisposable() private let reportMessageDeliveryDisposable = DisposableSet() + private let updateEmojiGameInfoDisposable = MetaDisposable() let updateConfigRequested: (() -> Void)? let isPremiumUpdated: (() -> Void)? @@ -414,6 +415,7 @@ public final class AccountStateManager { self.appliedMaxMessageIdDisposable.dispose() self.appliedQtsDisposable.dispose() self.reportMessageDeliveryDisposable.dispose() + self.updateEmojiGameInfoDisposable.dispose() } public func reset() { @@ -1137,6 +1139,11 @@ public final class AccountStateManager { if !events.updatedStarGiftAuctionMyState.isEmpty { strongSelf.notifyUpdatedStarGiftAuctionMyState(events.updatedStarGiftAuctionMyState) } + if let updatedEmojiGameInfo = events.updatedEmojiGameInfo { + strongSelf.updateEmojiGameInfoDisposable.set(strongSelf.postbox.transaction({ transaction in + updateEmojiGameInfo(transaction: transaction, { _ in return updatedEmojiGameInfo }) + }).start()) + } if !events.updatedCalls.isEmpty { for call in events.updatedCalls { strongSelf.callSessionManager?.updateSession(call, completion: { _ in }) diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index d470168d..1b644084 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -125,6 +125,7 @@ final class AccountTaskManager { tasks.add(managedPeerColorUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start()) tasks.add(managedSavedMusicIdsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start()) + tasks.add(managedEmojiGameUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) self.managedTopReactionsDisposable.set(managedTopReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index e775e854..ae88fa3c 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -2173,7 +2173,7 @@ public final class AccountViewTracker { fixedCombinedReadStates: .peer([peerId: CombinedPeerReadState(states: [ (Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: Int32.max - 1, maxOutgoingReadId: Int32.max - 1, maxKnownId: Int32.max - 1, count: 0, markedUnread: false)) ])]), - topTaggedMessageIdNamespaces: [], + topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tag: tag, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allNonRegular), @@ -2201,7 +2201,7 @@ public final class AccountViewTracker { count: count, trackHoles: trackHoles, fixedCombinedReadStates: nil, - topTaggedMessageIdNamespaces: [], + topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tag: tag, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allNonRegular), diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index 185ed044..56c75694 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -104,7 +104,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var updatedTimestamp: Int32? if let apiMessage = apiMessage { switch apiMessage { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatedTimestamp = date case .messageEmpty: break @@ -400,7 +400,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage } else if let message = messages.first, let apiMessage = result.messages.first { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { namespace = Namespaces.Message.ScheduledCloud - } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { + } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud } } diff --git a/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift b/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift index 8c8a4fa3..620f1c31 100644 --- a/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift +++ b/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift @@ -43,8 +43,18 @@ private final class AccountPresenceManagerImpl { } private func updatePresence(_ isOnline: Bool) { + // GHOST MODE: Completely block status updates to freeze "last seen" time + if GhostModeManager.shared.shouldHideOnlineStatus { + self.onlineTimer?.invalidate() + self.onlineTimer = nil + return + } + + // ALWAYS ONLINE: Force online status when enabled + let effectiveOnline = MiscSettingsManager.shared.shouldAlwaysBeOnline ? true : isOnline + let request: Signal - if isOnline { + if effectiveOnline { let timer = SignalKitTimer(timeout: 30.0, repeat: false, completion: { [weak self] in guard let strongSelf = self else { return diff --git a/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift index 70157349..ddca0d3f 100644 --- a/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift @@ -479,6 +479,26 @@ private func _internal_clearHistory(transaction: Transaction, postbox: Postbox, |> `catch` { _ -> Signal in return .complete() } + } else if let threadId = operation.threadId { + guard let inputPeer = apiInputPeer(peer) else { + return .complete() + } + return network.request(Api.functions.messages.deleteTopicHistory(peer: inputPeer, topMsgId: Int32(clamping: threadId))) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + if let result = result { + switch result { + case let .affectedHistory(pts, ptsCount, _): + stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)]) + return .complete() + } + } else { + return .complete() + } + } } else { return requestClearHistory(postbox: postbox, network: network, stateManager: stateManager, inputPeer: inputPeer, maxId: operation.topMessageId.id, justClear: true, minTimestamp: operation.minTimestamp, maxTimestamp: operation.maxTimestamp, type: operation.type) } diff --git a/submodules/TelegramCore/Sources/State/ManagedEmojiGameUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedEmojiGameUpdates.swift new file mode 100644 index 00000000..710f7890 --- /dev/null +++ b/submodules/TelegramCore/Sources/State/ManagedEmojiGameUpdates.swift @@ -0,0 +1,104 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit + +public enum EmojiGameInfo: Codable, Equatable { + private enum CodingKeys: String, CodingKey { + case type + case info + } + + public struct Info: Codable, Equatable { + public let gameHash: String + public let previousStake: Int64 + public let currentStreak: Int32 + public let parameters: [Int32] + public let playsLeft: Int32? + } + + case available(Info) + case unavailable + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + + let type = try container.decode(Int32.self, forKey: .type) + switch type { + case 1: + self = .available(try container.decode(Info.self, forKey: .info)) + default: + self = .unavailable + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case let .available(info): + try container.encode(Int32(1), forKey: .type) + try container.encode(info, forKey: .info) + case .unavailable: + try container.encode(Int32(0), forKey: .type) + } + } +} + +extension EmojiGameInfo { + init(apiEmojiGameInfo: Api.messages.EmojiGameInfo) { + switch apiEmojiGameInfo { + case let .emojiGameDiceInfo(_, gameHash, prevStake, currentStreak, params, playsLeft): + self = .available(Info(gameHash: gameHash, previousStake: prevStake, currentStreak: currentStreak, parameters: params, playsLeft: playsLeft)) + case .emojiGameUnavailable: + self = .unavailable + } + } +} + + +public func currentEmojiGameInfo(transaction: Transaction) -> EmojiGameInfo { + if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.emojiGameInfo())?.get(EmojiGameInfo.self) { + return entry + } else { + return .unavailable + } +} + +func updateEmojiGameInfo(transaction: Transaction, _ f: (EmojiGameInfo) -> EmojiGameInfo) { + let current = currentEmojiGameInfo(transaction: transaction) + let updated = f(current) + if updated != current { + transaction.setPreferencesEntry(key: PreferencesKeys.emojiGameInfo(), value: PreferencesEntry(updated)) + } +} + +func updateEmojiGameInfoOnce(postbox: Postbox, network: Network) -> Signal { + return network.request(Api.functions.messages.getEmojiGameInfo()) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result else { + return .complete() + } + return postbox.transaction { transaction -> Void in + let info = EmojiGameInfo(apiEmojiGameInfo: result) + updateEmojiGameInfo(transaction: transaction) { _ in + return info + } + } + } +} + +func managedEmojiGameUpdates(postbox: Postbox, network: Network) -> Signal { + let poll = Signal { subscriber in + return updateEmojiGameInfoOnce(postbox: postbox, network: network).start(completed: { + subscriber.putCompletion() + }) + } + return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart +} diff --git a/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift b/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift index c2fa13e3..099d3c85 100644 --- a/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift +++ b/submodules/TelegramCore/Sources/State/ManagedLocalInputActivities.swift @@ -142,6 +142,11 @@ private func actionFromActivity(_ activity: PeerInputActivity?) -> Api.SendMessa } private func requestActivity(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, threadId: Int64?, activity: PeerInputActivity?) -> Signal { + // GHOST MODE: Block typing indicator + if GhostModeManager.shared.shouldHideTypingIndicator { + return .complete() + } + return postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId) { if peerId == accountPeerId { diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeViewStoriesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeViewStoriesOperations.swift index bebd6915..5f846fe6 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeViewStoriesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeViewStoriesOperations.swift @@ -119,6 +119,11 @@ func managedSynchronizeViewStoriesOperations(postbox: Postbox, network: Network, } private func pushStoriesAreSeen(postbox: Postbox, network: Network, stateManager: AccountStateManager, peer: Peer, operation: SynchronizeViewStoriesOperation) -> Signal { + // GHOST MODE: Don't send story view notifications + if GhostModeManager.shared.shouldHideStoryViews { + return .complete() + } + guard let inputPeer = apiInputPeer(peer) else { return .complete() } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index cbdf0a3a..869b2f9d 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -2076,7 +2076,7 @@ public final class PendingMessageManager { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { isScheduled = true } - if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { + if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { if (flags2 & (1 << 4)) != 0 { isScheduled = true } @@ -2120,7 +2120,7 @@ public final class PendingMessageManager { namespace = Namespaces.Message.QuickReplyCloud } else if let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { namespace = Namespaces.Message.ScheduledCloud - } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { + } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud } } diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 0a868049..72cea0ce 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 220 + return 221 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift b/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift index 4bc06ce1..4e169d54 100644 --- a/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift +++ b/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift @@ -219,6 +219,11 @@ private func validatePeerReadState(network: Network, postbox: Postbox, stateMana } private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId, readState: PeerReadState) -> Signal { + // GHOST MODE: Block read receipts (blue checkmarks) for non-secret chats + if peerId.namespace != Namespaces.Peer.SecretChat && GhostModeManager.shared.shouldHideReadReceipts { + return .single(readState) + } + if peerId.namespace == Namespaces.Peer.SecretChat { return inputSecretChat(postbox: postbox, peerId: peerId) |> mapToSignal { inputPeer -> Signal in diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 716fffa5..655b2380 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod): - let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil, summaryFromLanguage: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService { let generatedPeerId = Api.Peer.peerUser(userId: userId) - let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil, summaryFromLanguage: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index 336b9c43..77f07157 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -104,7 +104,7 @@ extension Api.MessageMedia { extension Api.Message { var rawId: Int32 { switch self { - case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return id case let .messageEmpty(_, id, _): return id @@ -115,7 +115,7 @@ extension Api.Message { func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? { switch self { - case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): var namespace = namespace if (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud @@ -136,7 +136,7 @@ extension Api.Message { var peerId: PeerId? { switch self { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return peerId case let .messageEmpty(_, _, peerId): @@ -149,7 +149,7 @@ extension Api.Message { var timestamp: Int32? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return date case let .messageService(_, _, _, _, _, _, date, _, _, _): return date @@ -160,7 +160,7 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedResources default: return nil @@ -169,7 +169,7 @@ extension Api.Message { var preCachedStories: [StoryId: Api.StoryItem]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedStories default: return nil diff --git a/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift b/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift index 7c1fc585..29255b7e 100644 --- a/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift +++ b/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift @@ -32,9 +32,10 @@ public struct UserLimitsConfiguration: Equatable { public static var defaultValue: UserLimitsConfiguration { return UserLimitsConfiguration( - maxPinnedChatCount: 5, - maxPinnedSavedChatCount: 5, - maxArchivedPinnedChatCount: 100, + // GHOSTGRAM: Unlimited pinned chats bypass + maxPinnedChatCount: 99, + maxPinnedSavedChatCount: 99, + maxArchivedPinnedChatCount: 99, maxChannelsCount: 500, maxPublicLinksCount: 10, maxSavedGifCount: 200, @@ -145,9 +146,10 @@ extension UserLimitsConfiguration { } } - self.maxPinnedChatCount = getValue("dialogs_pinned_limit", orElse: defaultValue.maxPinnedChatCount) - self.maxPinnedSavedChatCount = getValue("saved_dialogs_pinned_limit", orElse: defaultValue.maxPinnedSavedChatCount) - self.maxArchivedPinnedChatCount = getValue("dialogs_folder_pinned_limit", orElse: defaultValue.maxArchivedPinnedChatCount) + // GHOSTGRAM: Force unlimited pinned chats (ignore server limits) + self.maxPinnedChatCount = 99 + self.maxPinnedSavedChatCount = 99 + self.maxArchivedPinnedChatCount = 99 self.maxChannelsCount = getValue("channels_limit", orElse: defaultValue.maxChannelsCount) self.maxPublicLinksCount = getValue("channels_public_limit", orElse: defaultValue.maxPublicLinksCount) self.maxSavedGifCount = getValue("saved_gifs_limit", orElse: defaultValue.maxSavedGifCount) diff --git a/submodules/TelegramCore/Sources/SyncCore/SummarizationMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SummarizationMessageAttribute.swift new file mode 100644 index 00000000..21fa9647 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SummarizationMessageAttribute.swift @@ -0,0 +1,87 @@ +import Foundation +import Postbox +import TelegramApi + +public final class SummarizationMessageAttribute: Equatable, MessageAttribute { + public struct Summary: Equatable, Codable, PostboxCoding { + public let text: String + public let entities: [MessageTextEntity] + + public init( + text: String, + entities: [MessageTextEntity] + ) { + self.text = text + self.entities = entities + } + + public init(decoder: PostboxDecoder) { + self.text = decoder.decodeStringForKey("text", orElse: "") + self.entities = decoder.decodeObjectArrayWithDecoderForKey("entities") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.text, forKey: "text") + encoder.encodeObjectArray(self.entities, forKey: "entities") + } + } + + public let fromLang: String + public let summary: Summary? + public let translated: [String: Summary] + + public init( + fromLang: String, + summary: Summary? = nil, + translated: [String: Summary] = [:] + ) { + self.fromLang = fromLang + self.summary = summary + self.translated = translated + } + + required public init(decoder: PostboxDecoder) { + self.fromLang = decoder.decodeStringForKey("fl", orElse: "") + self.summary = decoder.decodeObjectForKey("s", decoder: { Summary(decoder: $0) }) as? Summary + self.translated = decoder.decodeObjectDictionaryForKey("t", keyDecoder: { decoder in + return decoder.decodeStringForKey("k", orElse: "") + }, valueDecoder: { decoder in + return Summary(decoder: decoder) + }) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.fromLang, forKey: "fl") + if let summary = self.summary { + encoder.encodeObject(summary, forKey: "s") + } else { + encoder.encodeNil(forKey: "s") + } + encoder.encodeObjectDictionary(self.translated, forKey: "t", keyEncoder: { k, e in + e.encodeString(k, forKey: "k") + }) + } + + public static func ==(lhs: SummarizationMessageAttribute, rhs: SummarizationMessageAttribute) -> Bool { + if lhs.fromLang != rhs.fromLang { + return false + } + if lhs.summary != rhs.summary { + return false + } + if lhs.translated != rhs.translated { + return false + } + return true + } +} + +public extension SummarizationMessageAttribute { + func summaryForLang(_ lang: String?) -> Summary? { + if let lang { + return self.translated[lang] + } else { + return self.summary + } + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutoremoveTimeoutMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutoremoveTimeoutMessageAttribute.swift index 1eab2f65..b844a933 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutoremoveTimeoutMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutoremoveTimeoutMessageAttribute.swift @@ -111,6 +111,11 @@ public extension Message { } var minAutoremoveOrClearTimeout: Int32? { + // MISC: Bypass if view-once setting is enabled + if MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete { + return nil + } + var timeout: Int32? for attribute in self.attributes { if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { @@ -137,6 +142,11 @@ public extension Message { } var containsSecretMedia: Bool { + // MISC: Bypass if view-once setting is enabled + if MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete { + return false + } + guard let timeout = self.minAutoremoveOrClearTimeout else { return false } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index b6a82d2f..a6898bfb 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -323,6 +323,7 @@ private enum PreferencesKeyValues: Int32 { case persistentChatInterfaceData = 45 case globalPostSearchState = 46 case savedMusicIds = 47 + case emojiGameInfo = 48 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -591,6 +592,12 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.savedMusicIds.rawValue) return key } + + public static func emojiGameInfo() -> ValueBoxKey { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.emojiGameInfo.rawValue) + return key + } } private enum SharedDataKeyValues: Int32 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChatAdminRights.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChatAdminRights.swift index 733b5f92..5d270bf5 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChatAdminRights.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChatAdminRights.swift @@ -62,7 +62,8 @@ public struct TelegramChatAdminRightsFlags: OptionSet, Hashable { .canPostStories, .canEditStories, .canDeleteStories, - .canManageDirect + .canManageDirect, + .canBanUsers ] public static func peerSpecific(peer: EnginePeer) -> TelegramChatAdminRightsFlags { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift index d142f4d8..c6bdf9d4 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift @@ -1,29 +1,57 @@ +import Foundation import Postbox public final class TelegramMediaDice: Media, Equatable { + public struct GameOutcome: Equatable { + let seed: Data + public let tonAmount: Int64 + } + public let emoji: String + public let tonAmount: Int64? public let value: Int32? + public let gameOutcome: GameOutcome? public let id: MediaId? = nil public let peerIds: [PeerId] = [] - public init(emoji: String, value: Int32? = nil) { + public init(emoji: String, tonAmount: Int64? = nil, value: Int32? = nil, gameOutcome: GameOutcome? = nil) { self.emoji = emoji + self.tonAmount = tonAmount self.value = value + self.gameOutcome = gameOutcome } public init(decoder: PostboxDecoder) { self.emoji = decoder.decodeStringForKey("e", orElse: "๐ŸŽฒ") + self.tonAmount = decoder.decodeOptionalInt64ForKey("ta") self.value = decoder.decodeOptionalInt32ForKey("v") + if let seed = decoder.decodeDataForKey("gos"), let tonAmount = decoder.decodeOptionalInt64ForKey("goa") { + self.gameOutcome = GameOutcome(seed: seed, tonAmount: tonAmount) + } else { + self.gameOutcome = nil + } } public func encode(_ encoder: PostboxEncoder) { encoder.encodeString(self.emoji, forKey: "e") + if let tonAmount = self.tonAmount { + encoder.encodeInt64(tonAmount, forKey: "ta") + } else { + encoder.encodeNil(forKey: "ta") + } if let value = self.value { encoder.encodeInt32(value, forKey: "v") } else { encoder.encodeNil(forKey: "v") } + if let gameOutcome = self.gameOutcome { + encoder.encodeData(gameOutcome.seed, forKey: "gos") + encoder.encodeInt64(gameOutcome.tonAmount, forKey: "goa") + } else { + encoder.encodeNil(forKey: "gos") + encoder.encodeNil(forKey: "goa") + } } public static func ==(lhs: TelegramMediaDice, rhs: TelegramMediaDice) -> Bool { @@ -35,9 +63,15 @@ public final class TelegramMediaDice: Media, Equatable { if self.emoji != other.emoji { return false } + if self.tonAmount != other.tonAmount { + return false + } if self.value != other.value { return false } + if self.gameOutcome != other.gameOutcome { + return false + } return true } return false diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift index e9d24a6c..3724ff8b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift @@ -571,5 +571,26 @@ public extension TelegramEngine.EngineData.Item { return value } } + + public struct EmojiGame: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = EmojiGameInfo + + public init() { + } + + var key: PostboxViewKey { + return .preferences(keys: Set([PreferencesKeys.emojiGameInfo()])) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? PreferencesView else { + preconditionFailure() + } + guard let emojiGameInfo = view.values[PreferencesKeys.emojiGameInfo()]?.get(EmojiGameInfo.self) else { + return .unavailable + } + return emojiGameInfo + } + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index a60886ac..69d51898 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -482,6 +482,12 @@ private class AdMessagesHistoryContextImpl { } self.isActivated = true + // MISC: Block ads if setting enabled + if MiscSettingsManager.shared.shouldBlockAds { + self.stateValue = State(interPostInterval: nil, startDelay: nil, betweenDelay: nil, messages: []) + return + } + let peerId = self.peerId let accountPeerId = self.account.peerId let account = self.account diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift index 7b6a8bac..1ade75ef 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift @@ -64,7 +64,10 @@ func _internal_applyMaxReadIndexInteractively(transaction: Transaction, stateMan } } } else if index.id.peerId.namespace == Namespaces.Peer.CloudUser || index.id.peerId.namespace == Namespaces.Peer.CloudGroup || index.id.peerId.namespace == Namespaces.Peer.CloudChannel { - stateManager.notifyAppliedIncomingReadMessages([index.id]) + // GHOST MODE: Don't send read receipts (blue checkmarks) + if !GhostModeManager.shared.shouldHideReadReceipts { + stateManager.notifyAppliedIncomingReadMessages([index.id]) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift index 57d89f8f..be90d28f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift @@ -51,6 +51,11 @@ func _internal_markMessageContentAsConsumedInteractively(postbox: Postbox, messa for i in 0 ..< updatedAttributes.count { if let attribute = updatedAttributes[i] as? AutoremoveTimeoutMessageAttribute { if attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0 { + // MISC: Don't start countdown for view-once if bypass enabled + if attribute.timeout == viewOnceTimeout && MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete { + continue + } + var timeout = attribute.timeout if let duration = message.secretMediaDuration { timeout = max(timeout, Int32(duration)) @@ -194,7 +199,9 @@ func markMessageContentAsConsumedRemotely(transaction: Transaction, messageId: M if message.id.peerId.namespace == Namespaces.Peer.SecretChat { } else { - if attribute.timeout == viewOnceTimeout || timestamp >= countdownBeginTime + attribute.timeout { + // MISC: Don't expire view-once media if bypass enabled + let shouldExpire = !(attribute.timeout == viewOnceTimeout && MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete) + if shouldExpire && (attribute.timeout == viewOnceTimeout || timestamp >= countdownBeginTime + attribute.timeout) { for i in 0 ..< updatedMedia.count { if let _ = updatedMedia[i] as? TelegramMediaImage { updatedMedia[i] = TelegramMediaExpiredContent(data: .image) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift new file mode 100644 index 00000000..ab55533e --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Summarize.swift @@ -0,0 +1,83 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit + +public enum SummarizeError { + case generic + case invalidMessageId + case limitExceeded + case invalidLanguage + case limitExceededPremium +} + +func _internal_summarizeMessage(account: Account, messageId: EngineMessage.Id, translateToLang: String?) -> Signal { + return account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) + } + |> castError(SummarizeError.self) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer else { + return .never() + } + + var flags: Int32 = 0 + if let _ = translateToLang { + flags |= (1 << 0) + } + + return account.network.request(Api.functions.messages.summarizeText(flags: flags, peer: inputPeer, id: messageId.id, toLang: translateToLang)) + |> map(Optional.init) + |> mapError { error -> SummarizeError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else if error.errorDescription == "MSG_ID_INVALID" { + return .invalidMessageId + } else if error.errorDescription == "TO_LANG_INVALID" { + return .invalidLanguage + } else if error.errorDescription == "SUMMARY_FLOOD_PREMIUM" { + return .limitExceededPremium + } else { + return .generic + } + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction in + switch result { + case let .textWithEntities(text, entities): + transaction.updateMessage(messageId, update: { currentMessage in + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + var attributes = currentMessage.attributes + + let currentAttribute = attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute + let updatedAttribute: SummarizationMessageAttribute + if let translateToLang { + var translated = currentAttribute?.translated ?? [:] + translated[translateToLang] = SummarizationMessageAttribute.Summary(text: text, entities: messageTextEntitiesFromApiEntities(entities)) + updatedAttribute = SummarizationMessageAttribute( + fromLang: currentAttribute?.fromLang ?? "", + summary: currentAttribute?.summary, + translated: translated + ) + } else { + updatedAttribute = SummarizationMessageAttribute( + fromLang: currentAttribute?.fromLang ?? "", + summary: .init(text: text, entities: messageTextEntitiesFromApiEntities(entities)), + translated: currentAttribute?.translated ?? [:] + ) + } + attributes = attributes.filter { !($0 is SummarizationMessageAttribute) } + attributes.append(updatedAttribute) + + return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + default: + break + } + } + |> castError(SummarizeError.self) + } + |> ignoreValues + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 9133c0a5..143106a1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -604,6 +604,10 @@ public extension TelegramEngine { return _internal_togglePeerMessagesTranslationHidden(account: self.account, peerId: peerId, hidden: hidden) } + public func summarizeMessage(messageId: EngineMessage.Id, translateToLang: String?) -> Signal { + return _internal_summarizeMessage(account: self.account, messageId: messageId, translateToLang: translateToLang) + } + public func transcribeAudio(messageId: MessageId) -> Signal { return _internal_transcribeAudio(postbox: self.account.postbox, network: self.account.network, messageId: messageId) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index d3dd832a..95e29856 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -1578,7 +1578,8 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star prepaidUpgradeHash: nil, upgradeSeparate: false, dropOriginalDetailsStars: dropOriginalDetailsStars, - number: nil + number: nil, + isRefunded: false )) } } @@ -2533,6 +2534,7 @@ public final class ProfileGiftsContext { case upgradeSeparate case dropOriginalDetailsStars case number + case isRefunded } public let gift: TelegramCore.StarGift @@ -2556,7 +2558,8 @@ public final class ProfileGiftsContext { public let upgradeSeparate: Bool public let dropOriginalDetailsStars: Int64? public let number: Int32? - + public let isRefunded: Bool + fileprivate let _fromPeerId: EnginePeer.Id? public enum DecodingError: Error { @@ -2584,7 +2587,8 @@ public final class ProfileGiftsContext { prepaidUpgradeHash: String?, upgradeSeparate: Bool, dropOriginalDetailsStars: Int64?, - number: Int32? + number: Int32?, + isRefunded: Bool ) { self.gift = gift self.reference = reference @@ -2608,6 +2612,7 @@ public final class ProfileGiftsContext { self.upgradeSeparate = upgradeSeparate self.dropOriginalDetailsStars = dropOriginalDetailsStars self.number = number + self.isRefunded = isRefunded } public init(from decoder: Decoder) throws { @@ -2641,6 +2646,7 @@ public final class ProfileGiftsContext { self.upgradeSeparate = try container.decodeIfPresent(Bool.self, forKey: .upgradeSeparate) ?? false self.dropOriginalDetailsStars = try container.decodeIfPresent(Int64.self, forKey: .dropOriginalDetailsStars) self.number = try container.decodeIfPresent(Int32.self, forKey: .number) + self.isRefunded = try container.decodeIfPresent(Bool.self, forKey: .isRefunded) ?? false } public func encode(to encoder: Encoder) throws { @@ -2667,6 +2673,8 @@ public final class ProfileGiftsContext { try container.encode(self.upgradeSeparate, forKey: .upgradeSeparate) try container.encodeIfPresent(self.dropOriginalDetailsStars, forKey: .dropOriginalDetailsStars) try container.encodeIfPresent(self.number, forKey: .number) + try container.encode(self.isRefunded, forKey: .isRefunded) + } public func withGift(_ gift: TelegramCore.StarGift) -> StarGift { @@ -2691,7 +2699,8 @@ public final class ProfileGiftsContext { prepaidUpgradeHash: self.prepaidUpgradeHash, upgradeSeparate: self.upgradeSeparate, dropOriginalDetailsStars: self.dropOriginalDetailsStars, - number: self.number + number: self.number, + isRefunded: self.isRefunded ) } @@ -2717,7 +2726,8 @@ public final class ProfileGiftsContext { prepaidUpgradeHash: self.prepaidUpgradeHash, upgradeSeparate: self.upgradeSeparate, dropOriginalDetailsStars: self.dropOriginalDetailsStars, - number: self.number + number: self.number, + isRefunded: self.isRefunded ) } @@ -2743,7 +2753,8 @@ public final class ProfileGiftsContext { prepaidUpgradeHash: self.prepaidUpgradeHash, upgradeSeparate: self.upgradeSeparate, dropOriginalDetailsStars: self.dropOriginalDetailsStars, - number: self.number + number: self.number, + isRefunded: self.isRefunded ) } fileprivate func withFromPeer(_ fromPeer: EnginePeer?) -> StarGift { @@ -2768,7 +2779,8 @@ public final class ProfileGiftsContext { prepaidUpgradeHash: self.prepaidUpgradeHash, upgradeSeparate: self.upgradeSeparate, dropOriginalDetailsStars: self.dropOriginalDetailsStars, - number: self.number + number: self.number, + isRefunded: self.isRefunded ) } @@ -2794,7 +2806,8 @@ public final class ProfileGiftsContext { prepaidUpgradeHash: self.prepaidUpgradeHash, upgradeSeparate: self.upgradeSeparate, dropOriginalDetailsStars: self.dropOriginalDetailsStars, - number: self.number + number: self.number, + isRefunded: self.isRefunded ) } } @@ -3063,6 +3076,7 @@ extension ProfileGiftsContext.State.StarGift { self.upgradeSeparate = (flags & (1 << 17)) != 0 self.dropOriginalDetailsStars = dropOriginalDetailsStars self.number = number + self.isRefunded = (flags & (1 << 9)) != 0 } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift index 10ed47ea..a3652a45 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift @@ -580,7 +580,7 @@ public class GiftAuctionsManager { } public extension GiftAuctionContext.State { - func getPlace(myBid: Int64?, myBidDate: Int32?) -> Int32? { + func getPlace(myBid: Int64?, myBidDate: Int32?) -> (place: Int32, isApproximate: Bool)? { guard case let .ongoing(_, _, _, _, bidLevels, _, _, _, _, _, _, _) = self.auctionState else { return nil } @@ -592,7 +592,7 @@ public extension GiftAuctionContext.State { let levels = bidLevels guard !levels.isEmpty else { - return 1 + return (1, false) } func isWorse(than level: GiftAuctionContext.State.BidLevel) -> Bool { @@ -614,7 +614,7 @@ public extension GiftAuctionContext.State { } } if lowerIndex == -1 { - return 1 + return (1, false) } let lowerPosition = levels[lowerIndex].position @@ -626,14 +626,14 @@ public extension GiftAuctionContext.State { nextPosition = lowerPosition } if nextPosition == lowerPosition + 1 { - return lowerPosition + 1 + return (lowerPosition + 1, false) } else { - return nextPosition + return (lowerPosition, true) } } var place: Int32? { - return self.getPlace(myBid: nil, myBidDate: nil) + return self.getPlace(myBid: nil, myBidDate: nil)?.place } var startDate: Int32 { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 10146634..b51aebda 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -1644,7 +1644,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails, .starGiftAuctionBid: receiptMessageId = nil } - } else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _) = action.action, case let .Id(messageId) = message.id { + } else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, isRefunded, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _) = action.action, case let .Id(messageId) = message.id { let reference: StarGiftReference if let peerId, let savedId { reference = .peer(peerId: peerId, id: savedId) @@ -1672,7 +1672,8 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot prepaidUpgradeHash: nil, upgradeSeparate: false, dropOriginalDetailsStars: dropOriginalDetailsStars, - number: nil + number: nil, + isRefunded: isRefunded ) } } diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index 393234c0..8254b733 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -380,6 +380,11 @@ public extension Message { } func isCopyProtected() -> Bool { + // MISC: Bypass copy protection if enabled + if MiscSettingsManager.shared.shouldBypassCopyProtection { + return false + } + if self.flags.contains(.CopyProtected) { return true } else if let group = self.peers[self.id.peerId] as? TelegramGroup, group.flags.contains(.copyProtectionEnabled) { diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index b1e4e9ac..07340336 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -193,12 +193,8 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case incomingVideoMessagePlayOnceTip = 62 case outgoingVideoMessagePlayOnceTip = 63 case savedMessageTagLabelSuggestion = 65 - case dismissedBusinessBadge = 68 case monetizationIntroDismissed = 70 case businessBotMessageTooltip = 71 - case dismissedBusinessIntroBadge = 72 - case dismissedBusinessLinksBadge = 73 - case dismissedBusinessChatbotsBadge = 74 case captionAboveMediaTooltip = 75 case channelSendGiftTooltip = 76 case starGiftWearTips = 77 @@ -519,10 +515,6 @@ private struct ApplicationSpecificNoticeKeys { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessageTagLabelSuggestion.key) } - static func dismissedBusinessBadge() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessBadge.key) - } - static func dismissedBirthdayPremiumGiftTip(peerId: PeerId) -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: dismissedBirthdayPremiumGiftTipNamespace), key: noticeKey(peerId: peerId, key: 0)) } @@ -543,18 +535,6 @@ private struct ApplicationSpecificNoticeKeys { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.businessBotMessageTooltip.key) } - static func dismissedBusinessIntroBadge() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessIntroBadge.key) - } - - static func dismissedBusinessLinksBadge() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessLinksBadge.key) - } - - static func dismissedBusinessChatbotsBadge() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessChatbotsBadge.key) - } - static func captionAboveMediaTooltip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.captionAboveMediaTooltip.key) } @@ -2154,27 +2134,6 @@ public struct ApplicationSpecificNotice { return Int(previousValue) } } - - public static func setDismissedBusinessBadge(accountManager: AccountManager) -> Signal { - return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { - transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessBadge(), entry) - } - } - |> ignoreValues - } - - public static func dismissedBusinessBadge(accountManager: AccountManager) -> Signal { - return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessBadge()) - |> map { view -> Bool in - if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { - return true - } else { - return false - } - } - |> take(1) - } public static func dismissedBirthdayPremiumGiftTip(accountManager: AccountManager, peerId: PeerId) -> Signal { return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBirthdayPremiumGiftTip(peerId: peerId)) @@ -2286,69 +2245,6 @@ public struct ApplicationSpecificNotice { } } - public static func setDismissedBusinessLinksBadge(accountManager: AccountManager) -> Signal { - return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { - transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessLinksBadge(), entry) - } - } - |> ignoreValues - } - - public static func dismissedBusinessLinksBadge(accountManager: AccountManager) -> Signal { - return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessLinksBadge()) - |> map { view -> Bool in - if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { - return true - } else { - return false - } - } - |> take(1) - } - - public static func setDismissedBusinessIntroBadge(accountManager: AccountManager) -> Signal { - return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { - transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessIntroBadge(), entry) - } - } - |> ignoreValues - } - - public static func dismissedBusinessIntroBadge(accountManager: AccountManager) -> Signal { - return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessIntroBadge()) - |> map { view -> Bool in - if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { - return true - } else { - return false - } - } - |> take(1) - } - - public static func setDismissedBusinessChatbotsBadge(accountManager: AccountManager) -> Signal { - return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { - transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessChatbotsBadge(), entry) - } - } - |> ignoreValues - } - - public static func dismissedBusinessChatbotsBadge(accountManager: AccountManager) -> Signal { - return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessChatbotsBadge()) - |> map { view -> Bool in - if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { - return true - } else { - return false - } - } - |> take(1) - } - public static func getCaptionAboveMediaTooltip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.captionAboveMediaTooltip())?.get(ApplicationSpecificCounterNotice.self) { diff --git a/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift b/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift index 04a7a160..98433348 100644 --- a/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift +++ b/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift @@ -107,7 +107,7 @@ public final class PermissionContentNode: ASDisplayNode { self.textNode.displaysAsynchronously = false self.textNode.isAccessibilityElement = true - self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 52.0, cornerRadius: 9.0, isShimmering: true) + self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), glass: true, height: 52.0, cornerRadius: 26.0, isShimmering: true) self.footerNode = ImmediateTextNode() self.footerNode.textAlignment = .center diff --git a/submodules/TelegramPermissionsUI/Sources/PermissionController.swift b/submodules/TelegramPermissionsUI/Sources/PermissionController.swift index 9589dcc7..d0c2f053 100644 --- a/submodules/TelegramPermissionsUI/Sources/PermissionController.swift +++ b/submodules/TelegramPermissionsUI/Sources/PermissionController.swift @@ -36,15 +36,17 @@ public final class PermissionController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.splashScreen = splashScreen - let navigationBarPresentationData: NavigationBarPresentationData - if splashScreen { - navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)) - } else { - navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) - } +// let navigationBarPresentationData: NavigationBarPresentationData +// if splashScreen { +// navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, style: .glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)) +// } else { + let navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, style: .glass) +// } super.init(navigationBarPresentationData: navigationBarPresentationData) + self._hasGlassStyle = true + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.updateThemeAndStrings() @@ -86,15 +88,15 @@ public final class PermissionController: ViewController { private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - let navigationBarPresentationData: NavigationBarPresentationData - if self.splashScreen { - navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)) - } else { - navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) - } +// let navigationBarPresentationData: NavigationBarPresentationData +// if self.splashScreen { +// navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)) +// } else { + let navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, style: .glass) +// } - self.navigationBar?.updatePresentationData(navigationBarPresentationData) - self.navigationItem.backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil) + self.navigationBar?.updatePresentationData(navigationBarPresentationData, transition: .immediate) + //self.navigationItem.backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil) if self.navigationItem.rightBarButtonItem != nil { self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PermissionController.nextPressed)) } diff --git a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift index 3b76534e..3e60f0ff 100644 --- a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift +++ b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift @@ -46,9 +46,30 @@ public extension ToolbarTheme { } public extension NavigationBarTheme { - convenience init(rootControllerTheme: PresentationTheme, enableBackgroundBlur: Bool = true, hideBackground: Bool = false, hideBadge: Bool = false, hideSeparator: Bool = false) { + convenience init(rootControllerTheme: PresentationTheme, enableBackgroundBlur: Bool = true, hideBackground: Bool = false, hideBadge: Bool = false, hideSeparator: Bool = false, edgeEffectColor: UIColor? = nil, style: NavigationBar.Style = .legacy) { let theme = rootControllerTheme.rootController.navigationBar - self.init(buttonColor: theme.buttonColor, disabledButtonColor: theme.disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.blurredBackgroundColor, opaqueBackgroundColor: hideBackground ? .clear : theme.opaqueBackgroundColor, enableBackgroundBlur: enableBackgroundBlur, separatorColor: hideBackground || hideSeparator ? .clear : theme.separatorColor, badgeBackgroundColor: hideBadge ? .clear : theme.badgeBackgroundColor, badgeStrokeColor: hideBadge ? .clear : theme.badgeStrokeColor, badgeTextColor: hideBadge ? .clear : theme.badgeTextColor) + + let buttonColor: UIColor + let disabledButtonColor: UIColor + let badgeBackgroundColor: UIColor + let badgeTextColor: UIColor + var edgeEffectColor = edgeEffectColor + if case .glass = style { + buttonColor = rootControllerTheme.chat.inputPanel.panelControlColor + disabledButtonColor = buttonColor.withMultipliedAlpha(0.5) + badgeBackgroundColor = rootControllerTheme.chat.inputPanel.panelControlColor + badgeTextColor = rootControllerTheme.overallDarkAppearance ? .black : rootControllerTheme.list.itemCheckColors.foregroundColor + if edgeEffectColor == nil { + edgeEffectColor = rootControllerTheme.list.plainBackgroundColor + } + } else { + buttonColor = theme.buttonColor + disabledButtonColor = theme.disabledButtonColor + badgeBackgroundColor = theme.badgeBackgroundColor + badgeTextColor = theme.badgeTextColor + } + + self.init(overallDarkAppearance: rootControllerTheme.overallDarkAppearance, buttonColor: buttonColor, disabledButtonColor: disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.blurredBackgroundColor, opaqueBackgroundColor: hideBackground ? .clear : theme.opaqueBackgroundColor, enableBackgroundBlur: enableBackgroundBlur, separatorColor: hideBackground || hideSeparator ? .clear : theme.separatorColor, badgeBackgroundColor: hideBadge ? .clear : badgeBackgroundColor, badgeStrokeColor: .clear, badgeTextColor: hideBadge ? .clear : badgeTextColor, edgeEffectColor: edgeEffectColor, style: style) } } @@ -59,16 +80,16 @@ public extension NavigationBarStrings { } public extension NavigationBarPresentationData { - convenience init(presentationData: PresentationData) { - self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings)) + convenience init(presentationData: PresentationData, style: NavigationBar.Style = .legacy) { + self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, style: style), strings: NavigationBarStrings(presentationStrings: presentationData.strings)) } - convenience init(presentationData: PresentationData, hideBackground: Bool, hideBadge: Bool, hideSeparator: Bool = false) { - self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground, hideBadge: hideBadge, hideSeparator: hideSeparator), strings: NavigationBarStrings(presentationStrings: presentationData.strings)) + convenience init(presentationData: PresentationData, hideBackground: Bool, hideBadge: Bool, hideSeparator: Bool = false, style: NavigationBar.Style = .legacy) { + self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground, hideBadge: hideBadge, hideSeparator: hideSeparator, edgeEffectColor: hideBackground ? .clear : nil, style: style), strings: NavigationBarStrings(presentationStrings: presentationData.strings)) } - convenience init(presentationTheme: PresentationTheme, presentationStrings: PresentationStrings) { - self.init(theme: NavigationBarTheme(rootControllerTheme: presentationTheme), strings: NavigationBarStrings(presentationStrings: presentationStrings)) + convenience init(presentationTheme: PresentationTheme, presentationStrings: PresentationStrings, style: NavigationBar.Style = .legacy) { + self.init(theme: NavigationBarTheme(rootControllerTheme: presentationTheme, style: style), strings: NavigationBarStrings(presentationStrings: presentationStrings)) } } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index 82018273..5ded69e4 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -371,7 +371,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati let navigationSearchBar = PresentationThemeNavigationSearchBar( backgroundColor: UIColor(rgb: 0x1c1c1d), accentColor: UIColor(rgb: 0xffffff), - inputFillColor: UIColor(rgb: 0x0f0f0f), + inputFillColor: UIColor(white: 1.0, alpha: 0.1), inputTextColor: UIColor(rgb: 0xffffff), inputPlaceholderTextColor: UIColor(rgb: 0x8f8f8f), inputIconColor: UIColor(rgb: 0x8f8f8f), @@ -504,8 +504,8 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati pinnedBadgeColor: UIColor(rgb: 0x767677), pinnedSearchBarColor: UIColor(rgb: 0x272728), regularSearchBarColor: UIColor(rgb: 0x272728), - sectionHeaderFillColor: UIColor(rgb: 0x1c1c1d), - sectionHeaderTextColor: UIColor(rgb: 0xffffff), + sectionHeaderFillColor: .black, + sectionHeaderTextColor: UIColor(rgb: 0x8d8e93), verifiedIconFillColor: UIColor(rgb: 0xffffff), verifiedIconForegroundColor: UIColor(rgb: 0x000000), secretIconColor: UIColor(rgb: 0x00b12c), diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift index 86c3dad6..0fc5c681 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift @@ -145,7 +145,7 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme navigationSearchBar: rootController.navigationSearchBar.withUpdated( backgroundColor: mainBackgroundColor, accentColor: accentColor, - inputFillColor: mainInputColor, + inputFillColor: UIColor(white: 1.0, alpha: 0.1), inputPlaceholderTextColor: mainSecondaryColor, inputIconColor: mainSecondaryColor, inputClearButtonColor: mainSecondaryColor, @@ -429,7 +429,7 @@ public func customizeDefaultDarkTintedPresentationTheme(theme: PresentationTheme panelBackgroundColor: mainBackgroundColor?.withAlphaComponent(0.9), panelSeparatorColor: mainSeparatorColor, panelControlAccentColor: accentColor, - panelControlColor: mainSecondaryTextColor?.withAlphaComponent(0.5), + panelControlColor: UIColor(rgb: 0xffffff), inputBackgroundColor: inputBackgroundColor, inputStrokeColor: accentColor?.withMultiplied(hue: 1.038, saturation: 0.463, brightness: 0.26), inputPlaceholderColor: mainSecondaryTextColor?.withAlphaComponent(0.4), @@ -581,7 +581,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres let navigationSearchBar = PresentationThemeNavigationSearchBar( backgroundColor: mainBackgroundColor, accentColor: accentColor, - inputFillColor: mainInputColor, + inputFillColor: UIColor(white: 1.0, alpha: 0.1), inputTextColor: UIColor(rgb: 0xffffff), inputPlaceholderTextColor: mainSecondaryColor, inputIconColor: mainSecondaryColor, @@ -714,7 +714,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres pinnedBadgeColor: mainSecondaryTextColor.withAlphaComponent(0.5), pinnedSearchBarColor: accentColor.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12), regularSearchBarColor: accentColor.withMultiplied(hue: 1.029, saturation: 0.609, brightness: 0.12), - sectionHeaderFillColor: mainBackgroundColor, + sectionHeaderFillColor: .black, sectionHeaderTextColor: mainSecondaryTextColor.withAlphaComponent(0.5), verifiedIconFillColor: accentColor, verifiedIconForegroundColor: .white, @@ -874,7 +874,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres panelBackgroundColorNoWallpaper: accentColor.withMultiplied(hue: 1.024, saturation: 0.573, brightness: 0.18), panelSeparatorColor: mainSeparatorColor, panelControlAccentColor: accentColor, - panelControlColor: mainSecondaryTextColor.withAlphaComponent(0.5), + panelControlColor: UIColor(rgb: 0xffffff), panelControlDisabledColor: UIColor(rgb: 0x90979F, alpha: 0.5), panelControlDestructiveColor: UIColor(rgb: 0xff6767), inputBackgroundColor: inputBackgroundColor, diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index aacb1f01..4b5d5bd0 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -565,8 +565,8 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio pinnedBadgeColor: UIColor(rgb: 0xb6b6bb), pinnedSearchBarColor: UIColor(rgb: 0xe5e5e5), regularSearchBarColor: UIColor(rgb: 0xe9e9e9), - sectionHeaderFillColor: UIColor(rgb: 0xf7f7f7), - sectionHeaderTextColor: UIColor(rgb: 0x8e8e93), + sectionHeaderFillColor: .white, + sectionHeaderTextColor: UIColor(rgb: 0x6d6d72), verifiedIconFillColor: defaultDayAccentColor, verifiedIconForegroundColor: UIColor(rgb: 0xffffff), secretIconColor: UIColor(rgb: 0x00b12c), @@ -946,7 +946,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio panelControlColor: UIColor(rgb: 0x000000, alpha: 1.0), panelControlDisabledColor: UIColor(rgb: 0x727b87, alpha: 0.5), panelControlDestructiveColor: UIColor(rgb: 0xff3b30), - inputBackgroundColor: UIColor(rgb: 0xffffff), + inputBackgroundColor: UIColor(white: 1.0, alpha: 0.8), inputStrokeColor: UIColor(rgb: 0x000000, alpha: 0.1), inputPlaceholderColor: UIColor(rgb: 0x000000, alpha: 0.4), inputTextColor: UIColor(rgb: 0x000000), @@ -1032,7 +1032,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio primaryTextColor: UIColor(rgb: 0x000000), secondaryTextColor: UIColor(rgb: 0x8e8e93), controlAccentColor: defaultDayAccentColor, - inputBackgroundColor: UIColor(rgb: 0xe9e9e9), + inputBackgroundColor: UIColor(white: 1.0, alpha: 0.6), inputHollowBackgroundColor: UIColor(rgb: 0xffffff), inputBorderColor: UIColor(rgb: 0xe4e4e6), inputPlaceholderColor: UIColor(rgb: 0x8e8d92), diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index a2c2a1da..4d80c191 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -175,6 +175,11 @@ public final class PrincipalThemeEssentialGraphics { public let outgoingDateAndStatusStarsIcon: UIImage public let mediaStarsIcon: UIImage public let freeStarsIcon: UIImage + + public let incomingDateAndStatusTonIcon: UIImage + public let outgoingDateAndStatusTonIcon: UIImage + public let mediaTonIcon: UIImage + public let freeTonIcon: UIImage public let incomingDateAndStatusPinnedIcon: UIImage public let outgoingDateAndStatusPinnedIcon: UIImage @@ -369,6 +374,12 @@ public final class PrincipalThemeEssentialGraphics { self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)! self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)! self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)! + + let tonImage = generateScaledImage(image: UIImage(bundleImageName: "Ads/TonMedium"), size: CGSize(width: 12.0, height: 12.0), opaque: false)! + self.incomingDateAndStatusTonIcon = generateTintedImage(image: tonImage, color: theme.message.incoming.secondaryTextColor)! + self.outgoingDateAndStatusTonIcon = generateTintedImage(image: tonImage, color: theme.message.outgoing.secondaryTextColor)! + self.mediaTonIcon = generateTintedImage(image: tonImage, color: .white)! + self.freeTonIcon = generateTintedImage(image: tonImage, color: serviceColor.primaryText)! let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")! self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)! @@ -497,6 +508,12 @@ public final class PrincipalThemeEssentialGraphics { self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)! self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)! + let tonImage = generateScaledImage(image: UIImage(bundleImageName: "Ads/TonMedium"), size: CGSize(width: 12.0, height: 12.0), opaque: false)! + self.incomingDateAndStatusTonIcon = generateTintedImage(image: tonImage, color: theme.message.incoming.secondaryTextColor)! + self.outgoingDateAndStatusTonIcon = generateTintedImage(image: tonImage, color: theme.message.outgoing.secondaryTextColor)! + self.mediaTonIcon = generateTintedImage(image: tonImage, color: .white)! + self.freeTonIcon = generateTintedImage(image: tonImage, color: serviceColor.primaryText)! + let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")! self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)! self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)! diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 504d20f5..bdb4dec8 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -308,6 +308,8 @@ public enum PresentationResourceKey: Int32 { case chatFreeShareButtonIcon case chatFreeCloseButtonIcon case chatFreeMoreButtonIcon + case chatFreeExpandButtonIcon + case chatFreeCollapseButtonIcon case chatKeyboardActionButtonMessageIcon case chatKeyboardActionButtonLinkIcon diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index d2e5ce37..ccb452e6 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1174,6 +1174,18 @@ public struct PresentationResourcesChat { }) } + public static func chatFreeExpandButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { + return theme.image(PresentationResourceKey.chatFreeExpandButtonIcon.rawValue, { _ in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ExpandIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) + }) + } + + public static func chatFreeCollapseButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { + return theme.image(PresentationResourceKey.chatFreeCollapseButtonIcon.rawValue, { _ in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/CollapseIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) + }) + } + public static func chatKeyboardActionButtonMessageIconImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatKeyboardActionButtonMessageIcon.rawValue, { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotMessage"), color: theme.chat.inputButtonPanel.buttonTextColor) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift index d486af77..4ae03565 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift @@ -70,7 +70,7 @@ public struct PresentationResourcesRootController { public static func navigationCompactSearchIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.navigationCompactSearchIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: theme.rootController.navigationBar.accentTextColor) + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: theme.chat.inputPanel.panelControlColor) }) } @@ -82,7 +82,7 @@ public struct PresentationResourcesRootController { public static func navigationCompactTagsSearchIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.navigationCompactTagsSearchIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigationSearchTagsIcon"), color: theme.rootController.navigationBar.accentTextColor) + return generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigationSearchTagsIcon"), color: theme.chat.inputPanel.panelControlColor) }) } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 01c037b5..f6d67402 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1663,7 +1663,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, case .stars: priceString = strings.Notification_StarsGiftOffer_OfferYou_Stars(Int32(clamping: amount.amount.value)) case .ton: - priceString = "\(amount.amount) TON" + priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON" } attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGiftOffer_OfferYou(peerName, priceString, giftTitle)._tuple, body: bodyAttributes, argumentAttributes: attributes) @@ -1673,7 +1673,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, case .stars: priceString = strings.Notification_StarsGiftOffer_Offer_Stars(Int32(clamping: amount.amount.value)) case .ton: - priceString = "\(amount.amount) TON" + priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON" } attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGiftOffer_Offer(peerName, priceString, giftTitle)._tuple, body: bodyAttributes, argumentAttributes: attributes) @@ -1696,7 +1696,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, case .stars: priceString = strings.Notification_StarsGiftOffer_ExpiredYou_Stars(Int32(clamping: amount.amount.value)) case .ton: - priceString = "\(amount.amount) TON" + priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON" } var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds) @@ -1709,7 +1709,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, case .stars: priceString = strings.Notification_StarsGiftOffer_Expired_Stars(Int32(clamping: amount.amount.value)) case .ton: - priceString = "\(amount.amount) TON" + priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON" } let timeString = "[TODO]" @@ -1730,7 +1730,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, case .stars: priceString = strings.Notification_StarsGiftOffer_Rejected_Stars(Int32(clamping: amount.amount.value)) case .ton: - priceString = "\(amount.amount) TON" + priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat) + " TON" } var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds) @@ -1764,6 +1764,35 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, resultTitleString = strings.Conversation_StoryExpiredMentionTextOutgoing(compactPeerName) } attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) + } else if let dice = media as? TelegramMediaDice, let gameOutcome = dice.gameOutcome { + if let value = dice.value { + let rawString: String + if message.author?.id == accountPeerId { + if value == 1, let tonAmount = dice.tonAmount { + let value = formatTonAmountText(tonAmount, dateTimeFormat: dateTimeFormat) + rawString = strings.Conversation_EmojiStake_LostYou(value).string + } else { + let value = formatTonAmountText(gameOutcome.tonAmount, dateTimeFormat: dateTimeFormat) + rawString = strings.Conversation_EmojiStake_WonYou(value).string + } + } else { + let compactPeerName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? "" + if value == 1, let tonAmount = dice.tonAmount { + let value = formatTonAmountText(tonAmount, dateTimeFormat: dateTimeFormat) + rawString = strings.Conversation_EmojiStake_Lost(compactPeerName, value).string + } else { + let value = formatTonAmountText(gameOutcome.tonAmount, dateTimeFormat: dateTimeFormat) + rawString = strings.Conversation_EmojiStake_Won(compactPeerName, value).string + } + } + + let attributedText = NSMutableAttributedString(string: rawString, font: titleFont, textColor: primaryTextColor) + if let range = attributedText.string.range(of: "$") { + attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: true)), range: NSRange(range, in: attributedText.string)) + attributedText.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedText.string)) + } + attributedString = attributedText + } } } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 8d395730..8d05f333 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -49,7 +49,7 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//third-party/recaptcha:RecaptchaEnterprise", + "//third-party/recaptcha:RecaptchaEnterpriseSDK", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/SSignalKit/SSignalKit:SSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", @@ -153,7 +153,6 @@ swift_library( "//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader", "//submodules/ItemListPeerItem:ItemListPeerItem", "//submodules/ContactsPeerItem:ContactsPeerItem", - "//submodules/ChatListSearchItemNode:ChatListSearchItemNode", "//submodules/TelegramPermissionsUI:TelegramPermissionsUI", "//submodules/PeersNearbyIconNode:PeersNearbyIconNode", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", @@ -498,12 +497,26 @@ swift_library( "//submodules/TelegramUI/Components/AttachmentFileController", "//submodules/TelegramUI/Components/Contacts/NewContactScreen", "//submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu", + "//submodules/TelegramUI/Components/NavigationBarImpl", + "//submodules/TelegramUI/Components/GlobalControlPanelsContext", + "//submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent", + "//submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent", + "//submodules/TelegramUI/Components/TranslateHeaderPanelComponent", + "//submodules/TelegramUI/Components/AdPanelHeaderPanelComponent", + "//submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent", + "//submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent", + "//submodules/TelegramUI/Components/GroupCallHeaderPanelComponent", + "//submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode", "//submodules/TelegramUI/Components/Settings/PasskeysScreen", "//submodules/TelegramUI/Components/Gifts/GiftDemoScreen", + "//submodules/TelegramUI/Components/EmojiGameStakeScreen", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController", + "//submodules/TelegramUI/Components/CocoonInfoScreen", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], - "@build_bazel_rules_apple//apple:ios_x86_64": [], + "//conditions:default": [], }), visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/BUILD new file mode 100644 index 00000000..cf80f8d1 --- /dev/null +++ b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/BUILD @@ -0,0 +1,36 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AdPanelHeaderPanelComponent", + module_name = "AdPanelHeaderPanelComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramUIPreferences", + "//submodules/StickerResources", + "//submodules/PhotoResources", + "//submodules/TelegramStringFormatting", + "//submodules/AnimatedCountLabelNode", + "//submodules/AnimatedNavigationStripeNode", + "//submodules/ContextUI", + "//submodules/RadialStatusNode", + "//submodules/TextFormat", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TranslateUI", + + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/AdPanelHeaderPanelComponent.swift b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/AdPanelHeaderPanelComponent.swift new file mode 100644 index 00000000..69991d5d --- /dev/null +++ b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/AdPanelHeaderPanelComponent.swift @@ -0,0 +1,127 @@ +import Foundation +import UIKit +import Display +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import AccountContext +import TelegramCore +import SwiftSignalKit +import Postbox +import PresentationDataUtils +import ContextUI +import AsyncDisplayKit + +public final class AdPanelHeaderPanelComponent: Component { + public struct Info: Equatable { + public let message: EngineMessage + + public init(message: EngineMessage) { + self.message = message + } + } + + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + public let info: Info + public let action: (EngineMessage) -> Void + public let contextAction: (EngineMessage, ASDisplayNode, ContextGesture?) -> Void + public let close: () -> Void + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + info: Info, + action: @escaping (EngineMessage) -> Void, + contextAction: @escaping (EngineMessage, ASDisplayNode, ContextGesture?) -> Void, + close: @escaping () -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.info = info + self.action = action + self.contextAction = contextAction + self.close = close + } + + public static func ==(lhs: AdPanelHeaderPanelComponent, rhs: AdPanelHeaderPanelComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.info != rhs.info { + return false + } + return true + } + + public final class View: UIView { + private var panel: ChatAdPanelNode? + + private var component: AdPanelHeaderPanelComponent? + private weak var state: EmptyComponentState? + + public var message: EngineMessage? { + return self.component?.info.message + } + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func update(component: AdPanelHeaderPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let panel: ChatAdPanelNode + if let current = self.panel { + panel = current + } else { + panel = ChatAdPanelNode( + context: component.context, + action: component.action, + contextAction: component.contextAction, + close: component.close + ) + self.panel = panel + self.addSubview(panel.view) + } + + let height = panel.updateLayout( + width: availableSize.width, + theme: component.theme, + strings: component.strings, + info: component.info, + transition: transition.containedViewLayoutTransition + ) + let size = CGSize(width: availableSize.width, height: height) + let panelFrame = CGRect(origin: CGPoint(), size: size) + transition.setFrame(view: panel.view, frame: panelFrame) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/ChatAdPanelNode.swift b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/ChatAdPanelNode.swift new file mode 100644 index 00000000..b6e7d92a --- /dev/null +++ b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/ChatAdPanelNode.swift @@ -0,0 +1,523 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import StickerResources +import PhotoResources +import TelegramStringFormatting +import AnimatedCountLabelNode +import AnimatedNavigationStripeNode +import ContextUI +import RadialStatusNode +import TextFormat +import TextNodeWithEntities +import TranslateUI + +private enum PinnedMessageAnimation { + case slideToTop + case slideToBottom +} + +final class ChatAdPanelNode: ASDisplayNode { + private let context: AccountContext + private let action: (EngineMessage) -> Void + private let contextAction: (EngineMessage, ASDisplayNode, ContextGesture?) -> Void + private let close: () -> Void + + private(set) var message: EngineMessage? + + private let tapButton: HighlightTrackingButtonNode + + private let contextContainer: ContextControllerSourceNode + private let clippingContainer: ASDisplayNode + private let contentContainer: ASDisplayNode + private let contentTextContainer: ASDisplayNode + private let adNode: TextNode + private let titleNode: TextNode + private let textNode: TextNodeWithEntities + + private let removeButtonNode: HighlightTrackingButtonNode + private let removeBackgroundNode: ASImageNode + private let removeTextNode: ImmediateTextNode + + private let closeButton: HighlightableButtonNode + + private let imageNode: TransformImageNode + private let imageNodeContainer: ASDisplayNode + + private var currentLayout: (CGFloat, CGFloat, CGFloat)? + private var previousMediaReference: AnyMediaReference? + + private let fetchDisposable = MetaDisposable() + + init( + context: AccountContext, + action: @escaping (EngineMessage) -> Void, + contextAction: @escaping (EngineMessage, ASDisplayNode, ContextGesture?) -> Void, + close: @escaping () -> Void + ) { + self.context = context + self.action = action + self.contextAction = contextAction + self.close = close + + self.tapButton = HighlightTrackingButtonNode() + + self.contextContainer = ContextControllerSourceNode() + + self.clippingContainer = ASDisplayNode() + self.clippingContainer.clipsToBounds = true + + self.contentContainer = ASDisplayNode() + self.contentTextContainer = ASDisplayNode() + + self.adNode = TextNode() + self.adNode.displaysAsynchronously = false + self.adNode.isUserInteractionEnabled = false + + self.removeButtonNode = HighlightTrackingButtonNode() + self.removeBackgroundNode = ASImageNode() + + self.removeTextNode = ImmediateTextNode() + self.removeTextNode.displaysAsynchronously = false + self.removeTextNode.isUserInteractionEnabled = false + + self.titleNode = TextNode() + self.titleNode.displaysAsynchronously = false + self.titleNode.isUserInteractionEnabled = false + + self.textNode = TextNodeWithEntities() + self.textNode.textNode.displaysAsynchronously = false + self.textNode.textNode.isUserInteractionEnabled = false + + self.imageNode = TransformImageNode() + self.imageNode.contentAnimations = [.subsequentUpdates] + + self.imageNodeContainer = ASDisplayNode() + + self.closeButton = HighlightableButtonNode() + self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) + self.closeButton.displaysAsynchronously = false + + super.init() + + self.addSubnode(self.contextContainer) + + self.contextContainer.addSubnode(self.clippingContainer) + self.clippingContainer.addSubnode(self.contentContainer) + self.contentTextContainer.addSubnode(self.titleNode) + + self.contentTextContainer.addSubnode(self.adNode) + + self.contentTextContainer.addSubnode(self.textNode.textNode) + self.contentContainer.addSubnode(self.contentTextContainer) + + self.imageNodeContainer.addSubnode(self.imageNode) + self.contentContainer.addSubnode(self.imageNodeContainer) + + self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside]) + self.tapButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.adNode.layer.removeAnimation(forKey: "opacity") + strongSelf.adNode.alpha = 0.4 + strongSelf.titleNode.layer.removeAnimation(forKey: "opacity") + strongSelf.titleNode.alpha = 0.4 + strongSelf.textNode.textNode.layer.removeAnimation(forKey: "opacity") + strongSelf.textNode.textNode.alpha = 0.4 + strongSelf.imageNode.layer.removeAnimation(forKey: "opacity") + strongSelf.imageNode.alpha = 0.4 + strongSelf.removeTextNode.layer.removeAnimation(forKey: "opacity") + strongSelf.removeTextNode.alpha = 0.4 + strongSelf.removeBackgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.removeBackgroundNode.alpha = 0.4 + } else { + strongSelf.adNode.alpha = 1.0 + strongSelf.adNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.titleNode.alpha = 1.0 + strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.textNode.textNode.alpha = 1.0 + strongSelf.textNode.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.imageNode.alpha = 1.0 + strongSelf.imageNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.removeTextNode.alpha = 1.0 + strongSelf.removeTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.removeBackgroundNode.alpha = 1.0 + strongSelf.removeBackgroundNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.contextContainer.addSubnode(self.tapButton) + + self.contextContainer.addSubnode(self.removeBackgroundNode) + self.contextContainer.addSubnode(self.removeTextNode) + self.contextContainer.addSubnode(self.removeButtonNode) + + self.removeButtonNode.addTarget(self, action: #selector(self.removePressed), forControlEvents: [.touchUpInside]) + self.removeButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.removeTextNode.layer.removeAnimation(forKey: "opacity") + strongSelf.removeTextNode.alpha = 0.4 + strongSelf.removeBackgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.removeBackgroundNode.alpha = 0.4 + } else { + strongSelf.removeTextNode.alpha = 1.0 + strongSelf.removeTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.removeBackgroundNode.alpha = 1.0 + strongSelf.removeBackgroundNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + + self.contextContainer.activated = { [weak self] gesture, _ in + guard let self, let message = self.message else { + return + } + self.contextAction(message, self.contextContainer, gesture) + } + + self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside]) + self.addSubnode(self.closeButton) + } + + deinit { + self.fetchDisposable.dispose() + } + + private var theme: PresentationTheme? + + @objc private func closePressed() { + /*if self.context.isPremium, let adAttribute = self.message?.adAttribute { + self.controllerInteraction?.removeAd(adAttribute.opaqueId) + } else { + self.controllerInteraction?.openNoAdsDemo() + }*/ + self.close() + } + + func updateLayout(width: CGFloat, theme: PresentationTheme, strings: PresentationStrings, info: AdPanelHeaderPanelComponent.Info, transition: ContainedViewLayoutTransition) -> CGFloat { + let leftInset: CGFloat = 0.0 + let rightInset: CGFloat = 0.0 + + self.message = info.message + + if self.theme !== theme { + self.theme = theme + self.removeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 15.0, color: theme.chat.inputPanel.panelControlColor.withMultipliedAlpha(0.1)) + self.removeTextNode.attributedText = NSAttributedString(string: strings.Chat_BotAd_WhatIsThis, font: Font.regular(11.0), textColor: theme.chat.inputPanel.panelControlColor) + self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(theme.chat.inputPanel.panelControlColor.cgColor) + context.setLineWidth(1.33) + context.setLineCap(.round) + context.move(to: CGPoint(x: 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0)) + context.strokePath() + }), for: []) + } + + self.contextContainer.isGestureEnabled = false + + let panelHeight: CGFloat + var hasCloseButton = true + let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }) + panelHeight = self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, animation: nil, message: info.message, theme: theme, strings: strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: self.context.account.peerId, firstTime: false, isReplyThread: false, translateToLanguage: nil) + hasCloseButton = info.message.media.isEmpty + + self.contextContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) + + self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) + + self.clippingContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) + self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) + + let contentRightInset: CGFloat = 14.0 + rightInset + let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) + self.closeButton.frame = CGRect(origin: CGPoint(x: width - contentRightInset - closeButtonSize.width, y: floorToScreenPixels((panelHeight - closeButtonSize.height) / 2.0)), size: closeButtonSize) + + self.closeButton.isHidden = !hasCloseButton + + self.currentLayout = (width, leftInset, rightInset) + + return panelHeight + } + + private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, animation: PinnedMessageAnimation?, message: EngineMessage, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool, translateToLanguage: String?) -> CGFloat { + var animationTransition: ContainedViewLayoutTransition = .immediate + + if let animation = animation { + animationTransition = .animated(duration: 0.2, curve: .easeInOut) + + if let copyView = self.textNode.textNode.view.snapshotView(afterScreenUpdates: false) { + let offset: CGFloat + switch animation { + case .slideToTop: + offset = -10.0 + case .slideToBottom: + offset = 10.0 + } + + copyView.frame = self.textNode.textNode.frame + self.textNode.textNode.view.superview?.addSubview(copyView) + copyView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.2, removeOnCompletion: false, additive: true) + copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyView] _ in + copyView?.removeFromSuperview() + }) + self.textNode.textNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -offset), to: CGPoint(), duration: 0.2, additive: true) + self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + + let makeAdLayout = TextNode.asyncLayout(self.adNode) + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeTextLayout = TextNodeWithEntities.asyncLayout(self.textNode) + let imageNodeLayout = self.imageNode.asyncLayout() + + let previousMediaReference = self.previousMediaReference + let context = self.context + + let contentLeftInset: CGFloat = leftInset + 18.0 + let contentRightInset: CGFloat = rightInset + 9.0 + + var textRightInset: CGFloat = 0.0 + + var updatedMediaReference: AnyMediaReference? + var imageDimensions: CGSize? + + if !message._asMessage().containsSecretMedia { + for media in message.media { + if let image = media as? TelegramMediaImage { + updatedMediaReference = .message(message: MessageReference(message._asMessage()), media: image) + if let representation = largestRepresentationForPhoto(image) { + imageDimensions = representation.dimensions.cgSize + } + break + } else if let file = media as? TelegramMediaFile { + updatedMediaReference = .message(message: MessageReference(message._asMessage()), media: file) + if !file.isInstantVideo && !file.isSticker, let representation = largestImageRepresentation(file.previewRepresentations) { + imageDimensions = representation.dimensions.cgSize + } else if file.isAnimated, let dimensions = file.dimensions { + imageDimensions = dimensions.cgSize + } + break + } else if let paidContent = media as? TelegramMediaPaidContent, let firstMedia = paidContent.extendedMedia.first { + switch firstMedia { + case let .preview(dimensions, immediateThumbnailData, _): + let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + if let dimensions { + imageDimensions = dimensions.cgSize + } + updatedMediaReference = .standalone(media: thumbnailMedia) + case let .full(fullMedia): + updatedMediaReference = .message(message: MessageReference(message._asMessage()), media: fullMedia) + if let image = fullMedia as? TelegramMediaImage { + if let representation = largestRepresentationForPhoto(image) { + imageDimensions = representation.dimensions.cgSize + } + break + } else if let file = fullMedia as? TelegramMediaFile { + if let dimensions = file.dimensions { + imageDimensions = dimensions.cgSize + } + break + } + } + } + } + } + + let imageBoundingSize = CGSize(width: 48.0, height: 48.0) + var applyImage: (() -> Void)? + if let imageDimensions { + applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 10.0), imageSize: imageDimensions.aspectFilled(imageBoundingSize), boundingSize: imageBoundingSize, intrinsicInsets: UIEdgeInsets())) + textRightInset += imageBoundingSize.width + 18.0 + } else { + textRightInset = 27.0 + } + + var mediaUpdated = false + if let updatedMediaReference = updatedMediaReference, let previousMediaReference = previousMediaReference { + mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media) + } else if (updatedMediaReference != nil) != (previousMediaReference != nil) { + mediaUpdated = true + } + + var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? + var updatedFetchMediaSignal: Signal? + if mediaUpdated { + if let updatedMediaReference = updatedMediaReference, imageDimensions != nil { + if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) { + if imageReference.media.representations.isEmpty { + updateImageSignal = chatSecretPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, ignoreFullSize: true, synchronousLoad: true) + } else { + updateImageSignal = chatMessagePhotoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, blurred: false) + } + } else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) { + if fileReference.media.isAnimatedSticker { + let dimensions = fileReference.media.dimensions ?? PixelDimensions(width: 512, height: 512) + updateImageSignal = chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: fileReference.media, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))) + updatedFetchMediaSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(fileReference.media.resource)) + } else if fileReference.media.isVideo || fileReference.media.isAnimated { + updateImageSignal = chatMessageVideoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), fileReference: fileReference, blurred: false) + } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { + updateImageSignal = chatWebpageSnippetFile(account: context.account, userLocation: .peer(message.id.peerId), mediaReference: fileReference.abstract, representation: iconImageRepresentation) + } + } + } else { + updateImageSignal = .single({ _ in return nil }) + } + } + + let (adLayout, adApply) = makeAdLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Chat_BotAd_Title, font: Font.semibold(14.0), textColor: theme.chat.inputPanel.panelControlColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width, height: .greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: .zero)) + + let titleConstrainedSize = CGSize(width: width - contentLeftInset - contentRightInset - textRightInset - adLayout.size.width - 90.0, height: CGFloat.greatestFiniteMagnitude) + let textConstrainedSize = CGSize(width: width - contentLeftInset - contentRightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude) + + var titleText: String = "" + if let author = message.author { + titleText = author.compactDisplayTitle + } + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleText, font: Font.semibold(14.0), textColor: theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: titleConstrainedSize, alignment: .natural, cutout: nil, insets: .zero)) + + let (textString, _, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId) + + let messageText: NSAttributedString + let textFont = Font.regular(14.0) + if isText { + var text = message.text + var messageEntities = message._asMessage().textEntitiesAttribute?.entities ?? [] + + if let translateToLanguage = translateToLanguage, !text.isEmpty { + for attribute in message.attributes { + if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { + text = attribute.text + messageEntities = attribute.entities + break + } + } + } + + let entities = messageEntities.filter { entity in + switch entity.type { + case .CustomEmoji: + return true + default: + return false + } + } + let textColor = theme.chat.inputPanel.primaryTextColor + if entities.count > 0 { + messageText = stringWithAppliedEntities(trimToLineCount(text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage()) + } else { + messageText = NSAttributedString(string: foldLineBreaks(text), font: textFont, textColor: textColor) + } + } else { + messageText = NSAttributedString(string: foldLineBreaks(textString.string), font: textFont, textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor) + } + + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: .zero)) + + var panelHeight: CGFloat = 0.0 + if let _ = imageDimensions { + panelHeight = 9.0 + imageBoundingSize.height + 9.0 + } + + var textHeight: CGFloat + var titleOnSeparateLine = false + if textLayout.numberOfLines == 1 || contentLeftInset + adLayout.size.width + 2.0 + titleLayout.size.width > width - contentRightInset - textRightInset { + textHeight = adLayout.size.height + titleLayout.size.height + textLayout.size.height + 15.0 + titleOnSeparateLine = true + } else { + textHeight = titleLayout.size.height + textLayout.size.height + 15.0 + } + + panelHeight = max(panelHeight, textHeight) + + Queue.mainQueue().async { + let _ = adApply() + let _ = titleApply() + + let textArguments = TextNodeWithEntities.Arguments( + context: self.context, + cache: self.context.animationCache, + renderer: self.context.animationRenderer, + placeholderColor: theme.list.mediaPlaceholderColor, + attemptSynchronous: false + ) + let _ = textApply(textArguments) + + self.previousMediaReference = updatedMediaReference + + let textContainerFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: 0.0), size: CGSize(width: width, height: panelHeight)) + animationTransition.updateFrameAdditive(node: self.contentTextContainer, frame: textContainerFrame) + + let removeTextSize = self.removeTextNode.updateLayout(CGSize(width: width, height: .greatestFiniteMagnitude)) + + if titleOnSeparateLine { + self.adNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 9.0), size: adLayout.size) + self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 26.0), size: titleLayout.size) + self.textNode.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 43.0), size: textLayout.size) + + self.removeTextNode.frame = CGRect(origin: CGPoint(x: contentLeftInset + adLayout.size.width + 8.0, y: 11.0 - UIScreenPixel), size: removeTextSize) + self.removeBackgroundNode.frame = self.removeTextNode.frame.insetBy(dx: -5.0, dy: -1.0) + self.removeButtonNode.frame = self.removeBackgroundNode.frame + } else { + self.adNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 9.0), size: adLayout.size) + self.titleNode.frame = CGRect(origin: CGPoint(x: adLayout.size.width + 2.0, y: 9.0), size: titleLayout.size) + self.textNode.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 26.0), size: textLayout.size) + + self.removeTextNode.frame = CGRect(origin: CGPoint(x: contentLeftInset + adLayout.size.width + 2.0 + titleLayout.size.width + 8.0, y: 11.0 - UIScreenPixel), size: removeTextSize) + self.removeBackgroundNode.frame = self.removeTextNode.frame.insetBy(dx: -5.0, dy: -1.0) + self.removeButtonNode.frame = self.removeBackgroundNode.frame + } + + self.textNode.visibilityRect = CGRect.infinite + + self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: width - contentRightInset - imageBoundingSize.width, y: 9.0), size: imageBoundingSize) + self.imageNode.frame = CGRect(origin: CGPoint(), size: imageBoundingSize) + + if let applyImage = applyImage { + applyImage() + + animationTransition.updateSublayerTransformScale(node: self.imageNodeContainer, scale: 1.0) + animationTransition.updateAlpha(node: self.imageNodeContainer, alpha: 1.0, beginWithCurrentState: true) + } else { + animationTransition.updateSublayerTransformScale(node: self.imageNodeContainer, scale: 0.1) + animationTransition.updateAlpha(node: self.imageNodeContainer, alpha: 0.0, beginWithCurrentState: true) + } + + if let updateImageSignal = updateImageSignal { + self.imageNode.setSignal(updateImageSignal) + } + if let updatedFetchMediaSignal = updatedFetchMediaSignal { + self.fetchDisposable.set(updatedFetchMediaSignal.startStrict()) + } + } + + return panelHeight + } + + @objc func tapped() { + guard let message = self.message else { + return + } + self.action(message) + } + + @objc func removePressed() { + guard let message = self.message else { + return + } + self.contextAction(message, self.contextContainer, nil) + } +} diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift index b6137927..54d828be 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift @@ -1211,8 +1211,7 @@ private final class AdminUserActionsSheetComponent: Component { return } if !isEnabled { - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: environment.strings.GroupPermission_PermissionDisabledByDefault, actions: [ + self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: environment.strings.GroupPermission_PermissionDisabledByDefault, actions: [ TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: { }) ]), in: .window(.root)) diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift index a6dbc7f4..8d4ee376 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift @@ -496,7 +496,7 @@ private final class RecentActionsSettingsSheetComponent: Component { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent/BUILD b/submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent/BUILD new file mode 100644 index 00000000..d6049dc5 --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AlertCheckComponent", + module_name = "AlertCheckComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TextFormat", + "//submodules/Markdown", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/CheckComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent/Sources/AlertCheckComponent.swift b/submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent/Sources/AlertCheckComponent.swift new file mode 100644 index 00000000..a9c1d04b --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent/Sources/AlertCheckComponent.swift @@ -0,0 +1,186 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import SwiftSignalKit +import TelegramPresentationData +import AlertComponent +import PlainButtonComponent +import MultilineTextComponent +import CheckComponent +import TextFormat +import Markdown + +public final class AlertCheckComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + public class ExternalState { + public fileprivate(set) var value: Bool + fileprivate var valuePromise = Promise() + public var valueSignal: Signal + + public init() { + self.value = false + self.valueSignal = self.valuePromise.get() + } + } + + let title: String + let initialValue: Bool + let externalState: ExternalState + let linkAction: (() -> Void)? + + public init( + title: String, + initialValue: Bool, + externalState: ExternalState, + linkAction: (() -> Void)? = nil + ) { + self.title = title + self.initialValue = initialValue + self.externalState = externalState + self.linkAction = linkAction + } + + public static func ==(lhs: AlertCheckComponent, rhs: AlertCheckComponent) -> Bool { + return true + } + + public final class View: UIView { + private let button = ComponentView() + + private var component: AlertCheckComponent? + private weak var state: EmptyComponentState? + + private var isUpdating = false + + private var valuePromise = ValuePromise(false) + + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + func findTextView(view: UIView?) -> ImmediateTextView? { + if let view { + if let view = view as? ImmediateTextView { + return view + } + for view in view.subviews { + if let result = findTextView(view: view) { + return result + } + } + } + return nil + } + let result = super.hitTest(point, with: event) + if let textView = findTextView(view: result) { + if let (_, attributes) = textView.attributesAtPoint(self.convert(point, to: textView)) { + if attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] != nil { + return textView + } + } + } + return result + } + + func update(component: AlertCheckComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + if self.component == nil { + component.externalState.value = component.initialValue + component.externalState.valuePromise.set(self.valuePromise.get()) + } + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let checkTheme = CheckComponent.Theme( + backgroundColor: environment.theme.list.itemCheckColors.fillColor, + strokeColor: environment.theme.list.itemCheckColors.foregroundColor, + borderColor: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.15), + overlayBorder: false, + hasInset: false, + hasShadow: false + ) + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + let textColor = environment.theme.actionSheet.primaryTextColor + let linkColor = environment.theme.actionSheet.controlAccentColor + let markdownAttributes = MarkdownAttributes( + body: MarkdownAttributeSet(font: textFont, textColor: textColor), + bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), + link: MarkdownAttributeSet(font: textFont, textColor: linkColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + + let buttonSize = self.button.update( + transition: transition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(HStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent( + theme: checkTheme, + size: CGSize(width: 18.0, height: 18.0), + selected: component.externalState.value + ))), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( + text: .markdown(text: component.title, attributes: markdownAttributes), + maximumNumberOfLines: 2, + highlightColor: linkColor.withAlphaComponent(0.1), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { attributes, _ in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + component.linkAction?() + } + } + ))) + ], spacing: 10.0)), + effectAlignment: .center, + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.externalState.value = !component.externalState.value + self.valuePromise.set(component.externalState.value) + + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + }, + animateAlpha: false, + animateScale: false + )), + environment: { + }, + containerSize: CGSize(width: availableSize.width + 20.0, height: 1000.0) + ) + let buttonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - buttonSize.width) / 2.0), y: 7.0), size: buttonSize) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + transition.setFrame(view: buttonView, frame: buttonFrame) + } + + return CGSize(width: availableSize.width, height: buttonSize.height + 7.0) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent/BUILD b/submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent/BUILD new file mode 100644 index 00000000..86dafdff --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AlertInputFieldComponent", + module_name = "AlertInputFieldComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/AccountContext", + "//submodules/ComponentFlow", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/TextFormat", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent/Sources/AlertInputFieldComponent.swift b/submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent/Sources/AlertInputFieldComponent.swift new file mode 100644 index 00000000..34140adf --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent/Sources/AlertInputFieldComponent.swift @@ -0,0 +1,353 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import AlertComponent +import MultilineTextComponent +import AccountContext +import TextFormat +import PlainButtonComponent +import BundleIconComponent + +public final class AlertInputFieldComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + public class ExternalState { + public fileprivate(set) var value: String = "" + public fileprivate(set) var animateError: () -> Void = {} + public fileprivate(set) var activateInput: () -> Void = {} + fileprivate let valuePromise = ValuePromise("") + public var valueSignal: Signal { + return self.valuePromise.get() + } + + public init() { + } + } + + let context: AccountContext + let initialValue: String? + let placeholder: String + let characterLimit: Int? + let hasClearButton: Bool + let isSecureTextEntry: Bool + let returnKeyType: UIReturnKeyType + let keyboardType: UIKeyboardType + let autocapitalizationType: UITextAutocapitalizationType + let autocorrectionType: UITextAutocorrectionType + let isInitiallyFocused: Bool + let externalState: ExternalState + let shouldChangeText: ((String) -> Bool)? + let returnKeyAction: (() -> Void)? + + public init( + context: AccountContext, + initialValue: String? = nil, + placeholder: String, + characterLimit: Int? = nil, + hasClearButton: Bool = false, + isSecureTextEntry: Bool = false, + returnKeyType: UIReturnKeyType = .done, + keyboardType: UIKeyboardType = .default, + autocapitalizationType: UITextAutocapitalizationType = .sentences, + autocorrectionType: UITextAutocorrectionType = .default, + isInitiallyFocused: Bool = false, + externalState: ExternalState, + shouldChangeText: ((String) -> Bool)? = nil, + returnKeyAction: (() -> Void)? = nil + ) { + self.context = context + self.initialValue = initialValue + self.placeholder = placeholder + self.characterLimit = characterLimit + self.hasClearButton = hasClearButton + self.isSecureTextEntry = isSecureTextEntry + self.returnKeyType = returnKeyType + self.keyboardType = keyboardType + self.autocapitalizationType = autocapitalizationType + self.autocorrectionType = autocorrectionType + self.isInitiallyFocused = isInitiallyFocused + self.externalState = externalState + self.shouldChangeText = shouldChangeText + self.returnKeyAction = returnKeyAction + } + + public static func ==(lhs: AlertInputFieldComponent, rhs: AlertInputFieldComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.initialValue != rhs.initialValue { + return false + } + if lhs.placeholder != rhs.placeholder { + return false + } + if lhs.characterLimit != rhs.characterLimit { + return false + } + if lhs.hasClearButton != rhs.hasClearButton { + return false + } + if lhs.isSecureTextEntry != rhs.isSecureTextEntry { + return false + } + if lhs.returnKeyType != rhs.returnKeyType { + return false + } + if lhs.keyboardType != rhs.keyboardType { + return false + } + if lhs.autocapitalizationType != rhs.autocapitalizationType { + return false + } + if lhs.autocorrectionType != rhs.autocorrectionType { + return false + } + if lhs.isInitiallyFocused != rhs.isInitiallyFocused { + return false + } + return true + } + + private final class TextField: UITextField { + var sideInset: CGFloat = 0.0 + + override func textRect(forBounds bounds: CGRect) -> CGRect { + return CGRect(origin: CGPoint(x: self.sideInset, y: 0.0), size: CGSize(width: bounds.width - self.sideInset * 2.0, height: bounds.height)) + } + + override func editingRect(forBounds bounds: CGRect) -> CGRect { + return CGRect(origin: CGPoint(x: self.sideInset, y: 0.0), size: CGSize(width: bounds.width - self.sideInset * 2.0, height: bounds.height)) + } + } + + public final class View: UIView, UITextFieldDelegate { + private let background = ComponentView() + private let textField = TextField() + private let placeholder = ComponentView() + private let clearButton = ComponentView() + + private var component: AlertInputFieldComponent? + private weak var state: EmptyComponentState? + + private var isUpdating = false + + var currentText: String { + return self.textField.text ?? "" + } + + private var clearOnce: Bool = false + + func activateInput() { + self.textField.becomeFirstResponder() + } + + func animateError() { + if let component = self.component, component.isInitiallyFocused { + self.clearOnce = true + } + self.textField.layer.addShakeAnimation() + + HapticFeedback().error() + } + + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + self.component?.returnKeyAction?() + return false + } + + @objc private func textDidChange() { + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + } + + public func textFieldDidBeginEditing(_ textField: UITextField) { + self.clearButton.view?.isHidden = self.currentText.isEmpty + } + + public func textFieldDidEndEditing(_ textField: UITextField) { + self.clearButton.view?.isHidden = true + } + + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + guard let component = self.component else { + return true + } + + if self.clearOnce { + self.clearOnce = false + if range.length > string.count { + textField.text = "" + return false + } + } + + let updatedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string) + if let shouldChangeText = component.shouldChangeText { + return shouldChangeText(updatedText) + } + return true + } + + public func setText(text: String) { + self.textField.text = text + if !self.isUpdating { + self.state?.updated(transition: .immediate, isLocal: true) + } + } + + func update(component: AlertInputFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + var resetText: String? + if self.component == nil { + resetText = component.initialValue + component.externalState.animateError = { [weak self] in + self?.animateError() + } + component.externalState.activateInput = { [weak self] in + self?.activateInput() + } + } + + let isFirstTime = self.component == nil + + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let topInset: CGFloat = 15.0 + + if self.textField.superview == nil { + self.addSubview(self.textField) + self.textField.delegate = self + self.textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged) + } + if self.textField.autocapitalizationType != component.autocapitalizationType { + self.textField.autocapitalizationType = component.autocapitalizationType + } + if self.textField.autocorrectionType != component.autocorrectionType { + self.textField.autocorrectionType = component.autocorrectionType + } + if self.textField.isSecureTextEntry != component.isSecureTextEntry { + self.textField.isSecureTextEntry = component.isSecureTextEntry + } + if self.textField.returnKeyType != component.returnKeyType { + self.textField.returnKeyType = component.returnKeyType + } + self.textField.keyboardAppearance = environment.theme.overallDarkAppearance ? .dark : .light + if let resetText { + self.textField.text = resetText + } + + self.textField.font = Font.regular(17.0) + self.textField.textColor = environment.theme.actionSheet.primaryTextColor + self.textField.tintColor = environment.theme.actionSheet.controlAccentColor + self.textField.sideInset = 16.0 + + let backgroundPadding: CGFloat = 14.0 + let size = CGSize(width: availableSize.width, height: 50.0) + + let backgroundSize = self.background.update( + transition: transition, + component: AnyComponent( + FilledRoundedRectangleComponent(color: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1), cornerRadius: .value(25.0), smoothCorners: false) + ), + environment: {}, + containerSize: CGSize(width: size.width + backgroundPadding * 2.0, height: size.height) + ) + let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - backgroundSize.width) / 2.0), y: topInset ), size: backgroundSize) + if let backgroundView = self.background.view { + if backgroundView.superview == nil { + self.addSubview(backgroundView) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + + let textFieldSize = CGSize(width: availableSize.width - 24.0, height: 50.0) + let textFieldFrame = CGRect(origin: CGPoint(x: -12.0, y: topInset), size: textFieldSize) + transition.setFrame(view: self.textField, frame: textFieldFrame) + + let placeholderSize = self.placeholder.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString( + string: component.placeholder, + font: Font.regular(17.0), + textColor: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.4) + ))) + ), + environment: {}, + containerSize: CGSize(width: size.width, height: 50.0) + ) + let placeholderFrame = CGRect(origin: CGPoint(x: 4.0, y: floorToScreenPixels(textFieldFrame.midY - placeholderSize.height / 2.0)), size: placeholderSize) + if let placeholderView = self.placeholder.view { + if placeholderView.superview == nil { + placeholderView.isUserInteractionEnabled = false + self.addSubview(placeholderView) + } + placeholderView.frame = placeholderFrame + placeholderView.isHidden = !self.currentText.isEmpty + } + + if component.hasClearButton { + let clearButtonSize = self.clearButton.update( + transition: transition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(BundleIconComponent( + name: "Components/Search Bar/Clear", + tintColor: environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4) + )), + effectAlignment: .center, + minSize: CGSize(width: 44.0, height: 44.0), + action: { [weak self] in + guard let self else { + return + } + self.setText(text: "") + }, + animateAlpha: false, + animateScale: true + )), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + if let clearButtonView = self.clearButton.view { + if clearButtonView.superview == nil { + self.addSubview(clearButtonView) + } + transition.setFrame(view: clearButtonView, frame: CGRect(origin: CGPoint(x: availableSize.width - clearButtonSize.width + 11.0, y: topInset + floor((size.height - clearButtonSize.height) * 0.5)), size: clearButtonSize)) + clearButtonView.isHidden = self.currentText.isEmpty || !self.textField.isFirstResponder + } + } else if let clearButtonView = self.clearButton.view, clearButtonView.superview != nil { + clearButtonView.removeFromSuperview() + } + + if isFirstTime && component.isInitiallyFocused { + self.activateInput() + } + + component.externalState.value = self.currentText + component.externalState.valuePromise.set(self.currentText) + + return CGSize(width: availableSize.width, height: size.height + topInset) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent/BUILD b/submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent/BUILD new file mode 100644 index 00000000..8a8f24ed --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent/BUILD @@ -0,0 +1,27 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AlertMultilineInputFieldComponent", + module_name = "AlertMultilineInputFieldComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/AccountContext", + "//submodules/ComponentFlow", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/TextFormat", + "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/TextFieldComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent/Sources/AlertMultilineInputFieldComponent.swift b/submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent/Sources/AlertMultilineInputFieldComponent.swift new file mode 100644 index 00000000..49f9b1e7 --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent/Sources/AlertMultilineInputFieldComponent.swift @@ -0,0 +1,361 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import AlertComponent +import TextFieldComponent +import MultilineTextComponent +import AccountContext +import TextFormat + +public final class AlertMultilineInputFieldComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + public class ExternalState { + public fileprivate(set) var value: NSAttributedString = NSAttributedString() + public fileprivate(set) var animateError: () -> Void = {} + public fileprivate(set) var activateInput: () -> Void = {} + fileprivate let valuePromise = ValuePromise(NSAttributedString()) + public var valueSignal: Signal { + return self.valuePromise.get() + } + + public var textAndEntities: (String, [MessageTextEntity]) { + let text = self.value.string + let entities = generateChatInputTextEntities(self.value) + return (text, entities) + } + + public init() { + } + } + + public enum FormatMenuAvailability: Equatable { + public enum Action: CaseIterable { + case bold + case italic + case monospace + case link + case strikethrough + case underline + case spoiler + case quote + case code + + public static var all: [Action] = [ + .bold, + .italic, + .monospace, + .link, + .strikethrough, + .underline, + .spoiler, + .quote, + .code + ] + + var textFieldValue: TextFieldComponent.FormatMenuAvailability.Action { + switch self { + case .bold: + return .bold + case .italic: + return .italic + case .monospace: + return .monospace + case .link: + return .link + case .strikethrough: + return .strikethrough + case .underline: + return .underline + case .spoiler: + return .spoiler + case .quote: + return .quote + case .code: + return .code + } + } + } + case available([Action]) + case none + + var textFieldValue: TextFieldComponent.FormatMenuAvailability { + switch self { + case let .available(actions): + return .available(actions.map { $0.textFieldValue }) + case .none: + return .none + } + } + } + + public enum EmptyLineHandling { + case allowed + case oneConsecutive + case notAllowed + + var textFieldValue: TextFieldComponent.EmptyLineHandling { + switch self { + case .allowed: + return .allowed + case .oneConsecutive: + return .oneConsecutive + case .notAllowed: + return .notAllowed + } + } + } + + let context: AccountContext + let initialValue: NSAttributedString? + let placeholder: String + let prefix: NSAttributedString? + let characterLimit: Int? + let returnKeyType: UIReturnKeyType + let keyboardType: UIKeyboardType + let autocapitalizationType: UITextAutocapitalizationType + let autocorrectionType: UITextAutocorrectionType + let formatMenuAvailability: FormatMenuAvailability + let emptyLineHandling: EmptyLineHandling + let isInitiallyFocused: Bool + let externalState: ExternalState + let present: (ViewController) -> Void + let returnKeyAction: (() -> Void)? + + public init( + context: AccountContext, + initialValue: NSAttributedString? = nil, + placeholder: String, + prefix: NSAttributedString? = nil, + characterLimit: Int? = nil, + returnKeyType: UIReturnKeyType = .default, + keyboardType: UIKeyboardType = .default, + autocapitalizationType: UITextAutocapitalizationType = .sentences, + autocorrectionType: UITextAutocorrectionType = .default, + formatMenuAvailability: FormatMenuAvailability = .none, + emptyLineHandling: EmptyLineHandling = .allowed, + isInitiallyFocused: Bool = false, + externalState: ExternalState, + present: @escaping (ViewController) -> Void = { _ in }, + returnKeyAction: (() -> Void)? = nil + ) { + self.context = context + self.initialValue = initialValue + self.placeholder = placeholder + self.prefix = prefix + self.characterLimit = characterLimit + self.returnKeyType = returnKeyType + self.keyboardType = keyboardType + self.autocapitalizationType = autocapitalizationType + self.autocorrectionType = autocorrectionType + self.formatMenuAvailability = formatMenuAvailability + self.emptyLineHandling = emptyLineHandling + self.isInitiallyFocused = isInitiallyFocused + self.externalState = externalState + self.present = present + self.returnKeyAction = returnKeyAction + } + + public static func ==(lhs: AlertMultilineInputFieldComponent, rhs: AlertMultilineInputFieldComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.initialValue != rhs.initialValue { + return false + } + if lhs.placeholder != rhs.placeholder { + return false + } + if lhs.prefix != rhs.prefix { + return false + } + if lhs.returnKeyType != rhs.returnKeyType { + return false + } + if lhs.characterLimit != rhs.characterLimit { + return false + } + if lhs.keyboardType != rhs.keyboardType { + return false + } + if lhs.autocapitalizationType != rhs.autocapitalizationType { + return false + } + if lhs.autocorrectionType != rhs.autocorrectionType { + return false + } + if lhs.formatMenuAvailability != rhs.formatMenuAvailability { + return false + } + if lhs.emptyLineHandling != rhs.emptyLineHandling { + return false + } + if lhs.isInitiallyFocused != rhs.isInitiallyFocused { + return false + } + return true + } + + public final class View: UIView { + private let background = ComponentView() + private let textField = ComponentView() + private let textFieldExternalState = TextFieldComponent.ExternalState() + private let placeholder = ComponentView() + + private var component: AlertMultilineInputFieldComponent? + private weak var state: EmptyComponentState? + + func activateInput() { + if let textFieldView = self.textField.view as? TextFieldComponent.View { + textFieldView.activateInput() + } + } + + func animateError() { + if let textFieldView = self.textField.view { + textFieldView.layer.addShakeAnimation() + } + HapticFeedback().error() + } + + func update(component: AlertMultilineInputFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + var resetText: NSAttributedString? + if self.component == nil { + resetText = component.initialValue + component.externalState.animateError = { [weak self] in + self?.animateError() + } + component.externalState.activateInput = { [weak self] in + self?.activateInput() + } + } + + let isFirstTime = self.component == nil + + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let topInset: CGFloat = 15.0 + let horizontalInset: CGFloat = 4.0 + let verticalInset: CGFloat = 11.0 - UIScreenPixel + + let textFieldSize = self.textField.update( + transition: transition, + component: AnyComponent(TextFieldComponent( + context: component.context, + theme: environment.theme, + strings: environment.strings, + externalState: self.textFieldExternalState, + fontSize: 17.0, + textColor: environment.theme.actionSheet.primaryTextColor, + accentColor: environment.theme.actionSheet.controlAccentColor, + insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), + hideKeyboard: false, + customInputView: nil, + resetText: resetText, + isOneLineWhenUnfocused: false, + characterLimit: component.characterLimit, + emptyLineHandling: component.emptyLineHandling.textFieldValue, + formatMenuAvailability: component.formatMenuAvailability.textFieldValue, + returnKeyType: component.returnKeyType, + keyboardType: component.keyboardType, + autocapitalizationType: component.autocapitalizationType, + autocorrectionType: component.autocorrectionType, + lockedFormatAction: { + }, + present: { [weak self] c in + guard let self, let component = self.component else { + return + } + component.present(c) + }, + paste: { _ in + }, + returnKeyAction: { [weak self] in + guard let self, let component = self.component else { + return + } + component.returnKeyAction?() + }, + backspaceKeyAction: { + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width + horizontalInset * 2.0, height: availableSize.height) + ) + component.externalState.value = self.textFieldExternalState.text + component.externalState.valuePromise.set(component.externalState.value) + + let backgroundPadding: CGFloat = 14.0 + let size = CGSize(width: availableSize.width, height: max(50.0, floor(textFieldSize.height + verticalInset * 2.0))) + + let backgroundSize = self.background.update( + transition: transition, + component: AnyComponent( + FilledRoundedRectangleComponent(color: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1), cornerRadius: .value(25.0), smoothCorners: false) + ), + environment: {}, + containerSize: CGSize(width: size.width + backgroundPadding * 2.0, height: size.height) + ) + let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - backgroundSize.width) / 2.0), y: topInset ), size: backgroundSize) + if let backgroundView = self.background.view { + if backgroundView.superview == nil { + self.addSubview(backgroundView) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + + let textFieldFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - textFieldSize.width) / 2.0), y: topInset + 11.0 - UIScreenPixel), size: textFieldSize) + if let textFieldView = self.textField.view { + if textFieldView.superview == nil { + self.addSubview(textFieldView) + self.textField.parentState = state + } + transition.setFrame(view: textFieldView, frame: textFieldFrame) + } + + let placeholderSize = self.placeholder.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString( + string: component.placeholder, + font: Font.regular(17.0), + textColor: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.4) + ))) + ), + environment: {}, + containerSize: CGSize(width: size.width, height: 50.0) + ) + let placeholderFrame = CGRect(origin: CGPoint(x: 4.0, y: floorToScreenPixels(textFieldFrame.midY - placeholderSize.height / 2.0)), size: placeholderSize) + if let placeholderView = self.placeholder.view { + if placeholderView.superview == nil { + placeholderView.isUserInteractionEnabled = false + self.addSubview(placeholderView) + } + placeholderView.frame = placeholderFrame + placeholderView.isHidden = self.textFieldExternalState.hasText + } + + if isFirstTime && component.isInitiallyFocused { + self.activateInput() + } + + return CGSize(width: availableSize.width, height: size.height + topInset) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertTableComponent/BUILD b/submodules/TelegramUI/Components/AlertComponent/AlertTableComponent/BUILD new file mode 100644 index 00000000..9c1d5ce5 --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertTableComponent/BUILD @@ -0,0 +1,23 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AlertTableComponent", + module_name = "AlertTableComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/Gifts/TableComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertTableComponent/Sources/AlertTableComponent.swift b/submodules/TelegramUI/Components/AlertComponent/AlertTableComponent/Sources/AlertTableComponent.swift new file mode 100644 index 00000000..aabc6e85 --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertTableComponent/Sources/AlertTableComponent.swift @@ -0,0 +1,70 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import TelegramPresentationData +import AlertComponent +import TableComponent + +public final class AlertTableComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + let items: [TableComponent.Item] + + public init( + items: [TableComponent.Item] + ) { + self.items = items + } + + public static func ==(lhs: AlertTableComponent, rhs: AlertTableComponent) -> Bool { + if lhs.items != rhs.items { + return false + } + return true + } + + public final class View: UIView { + private let table = ComponentView() + + private var component: AlertTableComponent? + private weak var state: EmptyComponentState? + + func update(component: AlertTableComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let tableSize = self.table.update( + transition: transition, + component: AnyComponent( + TableComponent( + theme: environment.theme, + items: component.items, + semiTransparent: true + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width + 20.0, height: availableSize.height) + ) + let tableFrame = CGRect(origin: CGPoint(x: -10.0, y: 5.0), size: tableSize) + if let tableView = self.table.view { + if tableView.superview == nil { + self.addSubview(tableView) + } + transition.setFrame(view: tableView, frame: tableFrame) + } + return CGSize(width: availableSize.width, height: tableSize.height + 10.0) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent/BUILD b/submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent/BUILD new file mode 100644 index 00000000..c6734ce7 --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent/BUILD @@ -0,0 +1,23 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AlertTransferHeaderComponent", + module_name = "AlertTransferHeaderComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/AlertComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent/Sources/AlertTransferHeaderComponent.swift b/submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent/Sources/AlertTransferHeaderComponent.swift new file mode 100644 index 00000000..b0ddb6e2 --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent/Sources/AlertTransferHeaderComponent.swift @@ -0,0 +1,126 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import TelegramPresentationData +import AlertComponent +import BundleIconComponent + +public final class AlertTransferHeaderComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + public enum IconType { + case transfer + case take + } + + let fromComponent: AnyComponentWithIdentity + let toComponent: AnyComponentWithIdentity + let type: IconType + + public init( + fromComponent: AnyComponentWithIdentity, + toComponent: AnyComponentWithIdentity, + type: IconType + ) { + self.fromComponent = fromComponent + self.toComponent = toComponent + self.type = type + } + + public static func ==(lhs: AlertTransferHeaderComponent, rhs: AlertTransferHeaderComponent) -> Bool { + if lhs.fromComponent != rhs.fromComponent { + return false + } + if lhs.toComponent != rhs.toComponent { + return false + } + if lhs.type != rhs.type { + return false + } + return true + } + + public final class View: UIView { + private let from = ComponentView() + private let to = ComponentView() + private let arrow = ComponentView() + + private var component: AlertTransferHeaderComponent? + private weak var state: EmptyComponentState? + + func update(component: AlertTransferHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let size: CGSize + let iconName: String + switch component.type { + case .transfer: + iconName = "Peer Info/AlertArrow" + size = CGSize(width: 148.0, height: 60.0) + case .take: + iconName = "Media Editor/CutoutUndo" + size = CGSize(width: 154.0, height: 60.0) + } + let sideInset = floorToScreenPixels((availableSize.width - size.width) / 2.0) + + let fromSize = self.from.update( + transition: transition, + component: component.fromComponent.component, + environment: {}, + containerSize: CGSize(width: 60.0, height: 60.0) + ) + let fromFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: fromSize) + if let fromView = self.from.view { + if fromView.superview == nil { + self.addSubview(fromView) + } + transition.setFrame(view: fromView, frame: fromFrame) + } + + let arrowSize = self.arrow.update( + transition: transition, + component: AnyComponent( + BundleIconComponent(name: iconName, tintColor: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.2)) + ), + environment: {}, + containerSize: availableSize + ) + let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - arrowSize.width) / 2.0), y: floorToScreenPixels((size.height - arrowSize.height) / 2.0)), size: arrowSize) + if let arrowView = self.arrow.view { + if arrowView.superview == nil { + self.addSubview(arrowView) + } + transition.setFrame(view: arrowView, frame: arrowFrame) + } + + let toSize = self.to.update( + transition: transition, + component: component.toComponent.component, + environment: {}, + containerSize: CGSize(width: 60.0, height: 60.0) + ) + let toFrame = CGRect(origin: CGPoint(x: availableSize.width - toSize.width - sideInset, y: 0.0), size: toSize) + if let toView = self.to.view { + if toView.superview == nil { + self.addSubview(toView) + } + transition.setFrame(view: toView, frame: toFrame) + } + + return CGSize(width: availableSize.width, height: size.height + 11.0) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/AlertComponent/BUILD b/submodules/TelegramUI/Components/AlertComponent/BUILD index 076bdbcc..f84c8e13 100644 --- a/submodules/TelegramUI/Components/AlertComponent/BUILD +++ b/submodules/TelegramUI/Components/AlertComponent/BUILD @@ -12,9 +12,19 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/TelegramPresentationData", "//submodules/ComponentFlow", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/Markdown", + "//submodules/TextFormat", "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/MultilineTextWithEntitiesComponent", + "//submodules/Components/ActivityIndicatorComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/AlertComponent/Sources/AlertActionComponent.swift b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertActionComponent.swift new file mode 100644 index 00000000..97c171af --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertActionComponent.swift @@ -0,0 +1,228 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import MultilineTextComponent +import GlassBackgroundComponent +import ActivityIndicatorComponent + +private let titleFont = Font.medium(17.0) +private let boldTitleFont = Font.semibold(17.0) + +final class AlertActionComponent: Component { + typealias EnvironmentType = AlertComponentEnvironment + + static let actionHeight: CGFloat = 48.0 + + struct Theme: Equatable { + enum Font { + case regular + case bold + } + + let background: UIColor + let foreground: UIColor + let secondary: UIColor + let font: Font + } + + let theme: Theme + let title: String + let isHighlighted: Bool + let isEnabled: Signal + let progress: Signal + + init( + theme: Theme, + title: String, + isHighlighted: Bool, + isEnabled: Signal, + progress: Signal + ) { + self.theme = theme + self.title = title + self.isHighlighted = isHighlighted + self.isEnabled = isEnabled + self.progress = progress + } + + static func ==(lhs: AlertActionComponent, rhs: AlertActionComponent) -> Bool { + if lhs.theme != rhs.theme { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.isHighlighted != rhs.isHighlighted { + return false + } + return true + } + + final class View: UIView { + private let backgroundView = UIView() + private let title = ComponentView() + private var activity: ComponentView? + + private var component: AlertActionComponent? + private weak var state: EmptyComponentState? + + private var isEnabledDisposable: Disposable? + private var isEnabled = true + + private var progressDisposable: Disposable? + private var hasProgress = false + + private var isUpdating = false + + override init(frame: CGRect) { + super.init(frame: frame) + + self.backgroundView.clipsToBounds = true + self.addSubview(self.backgroundView) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + deinit { + self.isEnabledDisposable?.dispose() + self.progressDisposable?.dispose() + } + + func update(component: AlertActionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + if self.component == nil { + self.isEnabledDisposable = (component.isEnabled + |> deliverOnMainQueue).start(next: { [weak self] isEnabled in + guard let self else { + return + } + self.isEnabled = isEnabled + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.25)) + } + }) + + self.progressDisposable = (component.progress + |> deliverOnMainQueue).start(next: { [weak self] hasProgress in + guard let self else { + return + } + self.hasProgress = hasProgress + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.25)) + } + }) + } + self.component = component + self.state = state + + let attributedString = NSMutableAttributedString(string: component.title, font: component.theme.font == .bold ? boldTitleFont : titleFont, textColor: .white, paragraphAlignment: .center) + if let range = attributedString.string.range(of: "$") { + attributedString.addAttribute(.attachment, value: UIImage(bundleImageName: "Item List/PremiumIcon")!, range: NSRange(range, in: attributedString.string)) + attributedString.addAttribute(.foregroundColor, value: UIColor.white, range: NSRange(range, in: attributedString.string)) + attributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: attributedString.string)) + } + + let titlePadding: CGFloat = 16.0 + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(attributedString), + horizontalAlignment: .center, + maximumNumberOfLines: 1, + tintColor: component.theme.foreground + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - titlePadding * 2.0, height: availableSize.height) + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + titleView.bounds = CGRect(origin: .zero, size: titleSize) + transition.setAlpha(view: titleView, alpha: self.hasProgress ? 0.0 : 1.0) + } + + if self.hasProgress { + let activity: ComponentView + if let current = self.activity { + activity = current + } else { + activity = ComponentView() + self.activity = activity + } + let activitySize = CGSize(width: 18.0, height: 18.0) + let _ = activity.update( + transition: transition, + component: AnyComponent(ActivityIndicatorComponent(color: component.theme.secondary)), + environment: {}, + containerSize: activitySize + ) + if let activityView = activity.view { + activityView.bounds = CGRect(origin: .zero, size: activitySize) + } + } else if let activity = self.activity { + self.activity = nil + if let activityView = activity.view { + transition.setAlpha(view: activityView, alpha: 0.0, completion: { _ in + activityView.removeFromSuperview() + }) + } + } + + let buttonAlpha: CGFloat + if self.isEnabled { + buttonAlpha = component.isHighlighted ? 0.35 : 1.0 + } else { + buttonAlpha = 0.2 + } + + transition.setBackgroundColor(view: self.backgroundView, color: component.theme.background) + transition.setAlpha(view: self.backgroundView, alpha: buttonAlpha) + self.backgroundView.layer.cornerRadius = availableSize.height * 0.5 + + return CGSize(width: titleSize.width + titlePadding * 2.0, height: availableSize.height) + } + + func applySize(size: CGSize, transition: ComponentTransition) { + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size)) + + if let titleView = self.title.view { + let titleSize = titleView.bounds.size + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) + transition.setFrame(view: titleView, frame: titleFrame) + } + + if let activityView = self.activity?.view { + var activityTransition = transition + if activityView.superview == nil { + self.addSubview(activityView) + transition.animateAlpha(view: activityView, from: 0.0, to: 1.0) + activityTransition = .immediate + } + let activitySize = activityView.bounds.size + let activityFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - activitySize.width) / 2.0), y: floorToScreenPixels((size.height - activitySize.height) / 2.0)), size: activitySize) + activityTransition.setPosition(view: activityView, position: activityFrame.center) + activityView.transform = CGAffineTransformMakeScale(0.7, 0.7) + } + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/AlertComponent/Sources/AlertComponent.swift b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertComponent.swift index d7b9b066..bd4550c4 100644 --- a/submodules/TelegramUI/Components/AlertComponent/Sources/AlertComponent.swift +++ b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertComponent.swift @@ -1,395 +1,940 @@ import Foundation import UIKit -import Display -import TelegramPresentationData -import ComponentFlow -import ComponentDisplayAdapters import AsyncDisplayKit +import Display +import ComponentFlow +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import MultilineTextComponent +import ViewControllerComponent +import ComponentDisplayAdapters +import GlassBackgroundComponent -private let alertWidth: CGFloat = 270.0 - -public enum ComponentAlertActionType { - case genericAction - case defaultAction - case destructiveAction - case defaultDestructiveAction -} - -public struct ComponentAlertAction { - public let type: ComponentAlertActionType - public let title: String - public let action: () -> Void +public final class AlertComponentEnvironment: Equatable { + public let theme: PresentationTheme + public let strings: PresentationStrings - public init(type: ComponentAlertActionType, title: String, action: @escaping () -> Void) { - self.type = type - self.title = title - self.action = action - } -} - -public final class ComponentAlertContentActionNode: HighlightableButtonNode { - private var theme: AlertControllerTheme - public var action: ComponentAlertAction { - didSet { - self.updateTitle() - } - } - - private let backgroundNode: ASDisplayNode - - public var highlightedUpdated: (Bool) -> Void = { _ in } - - public init(theme: AlertControllerTheme, action: ComponentAlertAction) { + public init( + theme: PresentationTheme, + strings: PresentationStrings + ) { self.theme = theme - self.action = action - - self.backgroundNode = ASDisplayNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.alpha = 0.0 - - super.init() - - self.titleNode.maximumNumberOfLines = 2 - - self.highligthedChanged = { [weak self] value in - if let strongSelf = self { - strongSelf.setHighlighted(value, animated: true) - } + self.strings = strings + } + + public static func ==(lhs: AlertComponentEnvironment, rhs: AlertComponentEnvironment) -> Bool { + if lhs.theme !== rhs.theme { + return false } - - self.updateTheme(theme) - } - - public override func didLoad() { - super.didLoad() - - self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) - - self.pointerInteraction = PointerInteraction(node: self, style: .hover, willEnter: { [weak self] in - if let strongSelf = self { - strongSelf.setHighlighted(true, animated: false) - } - }, willExit: { [weak self] in - if let strongSelf = self { - strongSelf.setHighlighted(false, animated: false) - } - }) - } - - public func performAction() { - if self.actionEnabled { - self.action.action() + if lhs.strings !== rhs.strings { + return false } - } - - public func setHighlighted(_ highlighted: Bool, animated: Bool) { - self.highlightedUpdated(highlighted) - if highlighted { - if self.backgroundNode.supernode == nil { - self.insertSubnode(self.backgroundNode, at: 0) - } - self.backgroundNode.alpha = 1.0 - } else { - if animated { - UIView.animate(withDuration: 0.3, animations: { - self.backgroundNode.alpha = 0.0 - }) - } else { - self.backgroundNode.alpha = 0.0 - } - } - } - public var actionEnabled: Bool = true { - didSet { - self.isUserInteractionEnabled = self.actionEnabled - self.updateTitle() - } - } - - public func updateTheme(_ theme: AlertControllerTheme) { - self.theme = theme - self.backgroundNode.backgroundColor = theme.highlightedItemColor - self.updateTitle() - } - - private func updateTitle() { - var font = Font.regular(theme.baseFontSize) - var color: UIColor - switch self.action.type { - case .defaultAction, .genericAction: - color = self.actionEnabled ? self.theme.accentColor : self.theme.disabledColor - case .destructiveAction, .defaultDestructiveAction: - color = self.actionEnabled ? self.theme.destructiveColor : self.theme.disabledColor - } - switch self.action.type { - case .defaultAction, .defaultDestructiveAction: - font = Font.semibold(theme.baseFontSize) - case .destructiveAction, .genericAction: - break - } - self.setAttributedTitle(NSAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center), for: []) - self.accessibilityLabel = self.action.title - self.accessibilityTraits = [.button] - } - - @objc func pressed() { - self.action.action() - } - - override public func layout() { - super.layout() - - self.backgroundNode.frame = self.bounds + return true } } -public enum ComponentAlertContentActionLayout { - case horizontal - case vertical -} - -public final class ComponentAlertContentNode: AlertContentNode { - private var theme: AlertControllerTheme - private let actionLayout: ComponentAlertContentActionLayout +private final class AlertScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment - private let content: AnyComponent - private let contentView = ComponentView() + let configuration: AlertScreen.Configuration + let content: Signal<[AnyComponentWithIdentity], NoError> + let actions: Signal<[AlertScreen.Action], NoError> + let ready: Promise - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [ComponentAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - private let _dismissOnOutsideTap: Bool - override public var dismissOnOutsideTap: Bool { - return self._dismissOnOutsideTap - } - - private var highlightedItemIndex: Int? = nil - - public init(theme: AlertControllerTheme, content: AnyComponent, actions: [ComponentAlertAction], actionLayout: ComponentAlertContentActionLayout, dismissOnOutsideTap: Bool) { - self.theme = theme - self.actionLayout = actionLayout - self._dismissOnOutsideTap = dismissOnOutsideTap + init( + configuration: AlertScreen.Configuration, + content: Signal<[AnyComponentWithIdentity], NoError>, + actions: Signal<[AlertScreen.Action], NoError>, + ready: Promise + ) { + self.configuration = configuration self.content = content + self.actions = actions + self.ready = ready + } + + static func ==(lhs: AlertScreenComponent, rhs: AlertScreenComponent) -> Bool { + return true + } + + enum KeyCommand { + case up + case down + case left + case right + case escape + case enter + } + + final class View: UIView, UIGestureRecognizerDelegate { + private let dimView = UIView() + private let containerView = GlassBackgroundContainerView() + private let backgroundView = GlassBackgroundView() - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isUserInteractionEnabled = false - self.actionNodesSeparator.backgroundColor = theme.separatorColor + private var disposable: Disposable? + private var content: [AnyComponentWithIdentity]? + private var actions: [AlertScreen.Action]? - self.actionNodes = actions.map { action -> ComponentAlertContentActionNode in - return ComponentAlertContentActionNode(theme: theme, action: action) - } + private var contentItems: [AnyHashable: ComponentView] = [:] + private var actionItems: [AnyHashable: ComponentView] = [:] - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - separatorNode.backgroundColor = theme.separatorColor - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.actionNodesSeparator) - - var i = 0 - for actionNode in self.actionNodes { - self.addSubnode(actionNode) + private var highlightedAction: AnyHashable? + private let hapticFeedback = HapticFeedback() + + private enum ActionLayout { + case horizontal + case vertical + case verticalReversed - let index = i - actionNode.highlightedUpdated = { [weak self] highlighted in - if highlighted { - self?.highlightedItemIndex = index + var isVertical: Bool { + switch self { + case .vertical, .verticalReversed: + return true + default: + return false } } - i += 1 + } + private var effectiveActionLayout: ActionLayout = .horizontal + + fileprivate var dismissedByTapOutside = false + + private var isUpdating: Bool = false + private var component: AlertScreenComponent? + private var environment: EnvironmentType? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.dimView.alpha = 0.0 + self.dimView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.2) + + self.addSubview(self.dimView) + self.addSubview(self.containerView) + self.containerView.contentView.addSubview(self.backgroundView) + + self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapped))) + + let tapRecognizer = ActionSelectionGestureRecognizer(target: self, action: #selector(self.actionTapped(_:))) + tapRecognizer.delegate = self + self.backgroundView.addGestureRecognizer(tapRecognizer) } - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) + required init?(coder: NSCoder) { + preconditionFailure() } - } - - func setHighlightedItemIndex(_ index: Int?, update: Bool = false) { - self.highlightedItemIndex = index - if update { - var i = 0 - for actionNode in self.actionNodes { - if i == index { - actionNode.setHighlighted(true, animated: false) - } else { - actionNode.setHighlighted(false, animated: false) + deinit { + self.disposable?.dispose() + } + + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer is ActionSelectionGestureRecognizer { + let location = gestureRecognizer.location(in: self.backgroundView) + for (_, action) in self.actionItems { + if let actionView = action.view, actionView.frame.contains(location) { + return true + } } - i += 1 + return false + } else { + return super.gestureRecognizerShouldBegin(gestureRecognizer) } } - } - - override public func decreaseHighlightedIndex() { - let currentHighlightedIndex = self.highlightedItemIndex ?? 0 - self.setHighlightedItemIndex(max(0, currentHighlightedIndex - 1), update: true) - } - - override public func increaseHighlightedIndex() { - let currentHighlightedIndex = self.highlightedItemIndex ?? -1 - - self.setHighlightedItemIndex(min(self.actionNodes.count - 1, currentHighlightedIndex + 1), update: true) - } - - override public func performHighlightedAction() { - guard let highlightedItemIndex = self.highlightedItemIndex else { - return + @objc private func actionTapped(_ gestureRecognizer: ActionSelectionGestureRecognizer) { + let location = gestureRecognizer.location(in: self.backgroundView) + switch gestureRecognizer.state { + case .began, .changed: + var highlightedActionId: AnyHashable? + for (actionId, action) in self.actionItems { + if let actionView = action.view, actionView.frame.contains(location) { + highlightedActionId = actionId + break + } + } + if self.highlightedAction != highlightedActionId { + self.highlightedAction = highlightedActionId + self.state?.updated(transition: .easeInOut(duration: 0.2)) + + if case .changed = gestureRecognizer.state, highlightedActionId != nil { + self.hapticFeedback.tap() + } + } + case .ended: + if let _ = self.highlightedAction { + self.performHighlightedAction() + self.highlightedAction = nil + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + case .cancelled: + self.highlightedAction = nil + self.state?.updated(transition: .easeInOut(duration: 0.2)) + default: + break + } } - var i = 0 - for itemNode in self.actionNodes { - if i == highlightedItemIndex { - itemNode.performAction() + @objc private func dimTapped() { + guard let component = self.component, component.configuration.dismissOnOutsideTap else { return } - i += 1 - } - } - - override public func updateTheme(_ theme: AlertControllerTheme) { - self.theme = theme - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor + self.dismissedByTapOutside = true + self.requestDismiss() } - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) + func animateIn() { + let alphaTransition = ComponentTransition(animation: .curve(duration: 0.2, curve: .linear)) + let scaleTransition = ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)) + alphaTransition.setAlpha(view: self.dimView, alpha: 1.0) + + scaleTransition.animateScale(view: self.backgroundView, from: 1.15, to: 1.0) + alphaTransition.animateAlpha(view: self.containerView, from: 0.0, to: 1.0) } - } - - override public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - self.validLayout = size - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - var size = size - size.width = min(size.width, alertWidth) - - let contentSize = self.contentView.update( - transition: ComponentTransition(transition), - component: self.content, - environment: {}, - containerSize: CGSize(width: size.width - insets.left - insets.right, height: 10000.0) - ) - - let actionButtonHeight: CGFloat = 44.0 - - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = self.actionLayout - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical + func animateOut(completion: @escaping () -> Void) { + let transition = ComponentTransition(animation: .curve(duration: 0.2, curve: .linear)) + transition.setAlpha(view: self.dimView, alpha: 0.0, completion: { _ in + completion() + }) + var initialAlpha: CGFloat = 1.0 + if let presentationLayer = self.containerView.layer.presentation() { + initialAlpha = CGFloat(presentationLayer.opacity) } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + self.containerView.layer.animateAlpha(from: initialAlpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if result === self.containerView.contentView { + return self.dimView + } + return result + } + + func requestDismiss() { + guard let controller = self.environment?.controller() as? AlertScreen else { + return + } + controller.dismiss(completion: nil) + } + + func handleKeyCommand(_ command: KeyCommand) { + switch command { + case .up: + guard self.effectiveActionLayout.isVertical else { + return + } + self.updateActionHighlight(previous: false) + case .down: + guard self.effectiveActionLayout.isVertical else { + return + } + self.updateActionHighlight(previous: true) + case .left: + guard !self.effectiveActionLayout.isVertical else { + return + } + self.updateActionHighlight(previous: true) + case .right: + guard !self.effectiveActionLayout.isVertical else { + return + } + self.updateActionHighlight(previous: false) + case .escape: + self.requestDismiss() + case .enter: + self.performHighlightedAction() } } - let resultSize: CGSize - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let contentWidth = alertWidth - insets.left - insets.right - - let contentFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - contentSize.width) / 2.0), y: insets.top), size: contentSize) - if let contentComponentView = self.contentView.view { - if contentComponentView.superview == nil { - self.view.insertSubview(contentComponentView, belowSubview: self.actionNodesSeparator.view) - transition.updateFrame(view: contentComponentView, frame: contentFrame) + func updateActionHighlight(previous: Bool) { + guard let actions = self.actions else { + return } - } - - resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: contentSize.height + actionsHeight + insets.top + insets.bottom) - - self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + guard let highlightedAction = self.highlightedAction else { + if let action = actions.first(where: { $0.type == .default }) { + self.highlightedAction = action.id + } else if let action = actions.first(where: { $0.type == .defaultDestructive }) { + self.highlightedAction = action.id + } else if case .verticalReversed = self.effectiveActionLayout, let action = actions.last { + self.highlightedAction = action.id + } else if let action = actions.first { + self.highlightedAction = action.id + } + self.state?.updated(transition: .easeInOut(duration: 0.2)) + return + } + + let sequence = previous ? actions.reversed() : actions + var selectNext = false + var newHighlightedAction: AnyHashable? + + for action in sequence { + let id = AnyHashable(action.id) + if selectNext { + newHighlightedAction = id + break + } else if id == highlightedAction { + selectNext = true } } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width + guard let newHighlightedAction else { + return } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 + self.highlightedAction = newHighlightedAction + self.state?.updated(transition: .easeInOut(duration: 0.2)) } - return resultSize + func performHighlightedAction() { + guard let actions = self.actions else { + return + } + guard let highlightedAction = self.highlightedAction else { + return + } + guard let action = actions.first(where: { AnyHashable($0.id) == highlightedAction }) else { + return + } + action.action() + if action.autoDismiss { + self.requestDismiss() + } + } + + func update(component: AlertScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + self.environment = environment + self.state = state + + if self.component == nil { + self.disposable = (combineLatest( + queue: Queue.mainQueue(), + component.content, + component.actions + ) |> deliverOnMainQueue).start(next: { [weak self] content, actions in + guard let self else { + return + } + self.content = content + self.actions = actions + + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.25)) + } + }) + } + + self.component = component + + var alertHeight: CGFloat = 0.0 + let alertWidth: CGFloat = 300.0 + let contentTopInset: CGFloat = 22.0 + let contentBottomInset: CGFloat = 21.0 + let contentSideInset: CGFloat = 30.0 + let contentSpacing: CGFloat = 8.0 + let actionSideInset: CGFloat = 16.0 + let actionSpacing: CGFloat = 8.0 + let fullWidthActionSize = CGSize(width: alertWidth - actionSideInset * 2.0, height: AlertActionComponent.actionHeight) + let halfWidthActionSize = CGSize(width: (alertWidth - actionSideInset * 2.0 - actionSpacing) / 2.0, height: AlertActionComponent.actionHeight) + + let alertEnvironment = AlertComponentEnvironment(theme: environment.theme, strings: environment.strings) + + var contentOriginY: CGFloat = 0.0 + var validContentIds: Set = Set() + if let content = self.content { + for content in content { + if contentOriginY.isZero { + contentOriginY += contentTopInset + } else { + contentOriginY += contentSpacing + } + validContentIds.insert(content.id) + + let item: ComponentView + var itemTransition = transition + if let current = self.contentItems[content.id] { + item = current + } else { + item = ComponentView() + if !transition.animation.isImmediate { + itemTransition = .immediate + } + self.contentItems[content.id] = item + } + + let itemSize = item.update( + transition: itemTransition, + component: content.component, + environment: { alertEnvironment }, + containerSize: CGSize(width: alertWidth - contentSideInset * 2.0, height: availableSize.height) + ) + let itemFrame = CGRect(origin: CGPoint(x: contentSideInset, y: contentOriginY), size: itemSize) + if let itemView = item.view { + if itemView.superview == nil { + self.backgroundView.contentView.addSubview(itemView) + item.parentState = state + } + transition.setFrame(view: itemView, frame: itemFrame) + } + contentOriginY += itemSize.height + } + } + + if !contentOriginY.isZero { + alertHeight += contentOriginY + alertHeight += contentBottomInset + } + + if let actions = self.actions { + let genericActionTheme = AlertActionComponent.Theme( + background: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1), + foreground: environment.theme.actionSheet.primaryTextColor, + secondary: environment.theme.actionSheet.secondaryTextColor, + font: .regular + ) + let defaultActionTheme = AlertActionComponent.Theme( + background: environment.theme.actionSheet.controlAccentColor, + foreground: environment.theme.list.itemCheckColors.foregroundColor, + secondary: environment.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.85), + font: .bold + ) + let destructiveActionTheme = AlertActionComponent.Theme( + background: environment.theme.list.itemDestructiveColor, + foreground: .white, + secondary: .white.withMultipliedAlpha(0.6), + font: .regular + ) + let defaultDestructiveActionTheme = AlertActionComponent.Theme( + background: environment.theme.list.itemDestructiveColor, + foreground: .white, + secondary: .white.withMultipliedAlpha(0.6), + font: .bold + ) + + var effectiveActionLayout: ActionLayout = .horizontal + if case .vertical = component.configuration.actionAlignment { + effectiveActionLayout = .vertical + } else if actions.count == 1 { + effectiveActionLayout = .vertical + } + var actionTransitions: [AnyHashable: ComponentTransition] = [:] + var validActionIds: Set = Set() + for action in actions { + validActionIds.insert(action.id) + + let item: ComponentView + var itemTransition = transition + if let current = self.actionItems[action.id] { + item = current + } else { + item = ComponentView() + if !transition.animation.isImmediate { + itemTransition = .immediate + } + self.actionItems[action.id] = item + } + actionTransitions[action.id] = itemTransition + + let actionTheme: AlertActionComponent.Theme + switch action.type { + case .generic: + actionTheme = genericActionTheme + case .default: + actionTheme = defaultActionTheme + case .destructive: + actionTheme = destructiveActionTheme + case .defaultDestructive: + actionTheme = defaultDestructiveActionTheme + } + let itemSize = item.update( + transition: itemTransition, + component: AnyComponent(AlertActionComponent( + theme: actionTheme, + title: action.title, + isHighlighted: AnyHashable(action.id) == self.highlightedAction, + isEnabled: action.isEnabled, + progress: action.progress + )), + environment: { alertEnvironment }, + containerSize: fullWidthActionSize + ) + if let itemView = item.view { + if itemView.superview == nil { + self.backgroundView.contentView.addSubview(itemView) + } + } + + if case .horizontal = effectiveActionLayout, itemSize.width > halfWidthActionSize.width { + effectiveActionLayout = .verticalReversed + } + } + self.effectiveActionLayout = effectiveActionLayout + + if !actions.isEmpty { + let actionsHeight: CGFloat + if self.effectiveActionLayout.isVertical { + actionsHeight = fullWidthActionSize.height * CGFloat(actions.count) + actionSpacing * CGFloat(actions.count - 1) + } else { + actionsHeight = fullWidthActionSize.height + } + alertHeight += actionsHeight + alertHeight += actionSideInset + } + + var actionOriginX: CGFloat = actionSideInset + var actionOriginY: CGFloat + switch self.effectiveActionLayout { + case .horizontal, .verticalReversed: + actionOriginY = alertHeight - actionSideInset - fullWidthActionSize.height + case .vertical: + actionOriginY = alertHeight - actionSideInset - fullWidthActionSize.height * CGFloat( actions.count) - actionSpacing * CGFloat(actions.count - 1) + } + for action in actions { + guard let item = self.actionItems[action.id], let itemView = item.view as? AlertActionComponent.View else { + continue + } + let itemTransition = actionTransitions[action.id] ?? transition + let itemFrame: CGRect + switch self.effectiveActionLayout { + case .horizontal: + itemFrame = CGRect(origin: CGPoint(x: actionOriginX, y: actionOriginY), size: halfWidthActionSize) + actionOriginX += halfWidthActionSize.width + actionSpacing + case .vertical: + itemFrame = CGRect(origin: CGPoint(x: actionOriginX, y: actionOriginY), size: fullWidthActionSize) + actionOriginY += fullWidthActionSize.height + actionSpacing + case .verticalReversed: + itemFrame = CGRect(origin: CGPoint(x: actionOriginX, y: actionOriginY), size: fullWidthActionSize) + actionOriginY -= fullWidthActionSize.height + actionSpacing + } + itemView.applySize(size: itemFrame.size, transition: itemTransition) + itemTransition.setFrame(view: itemView, frame: itemFrame) + } + + var removeActionIds: [AnyHashable] = [] + for (id, item) in self.actionItems { + if !validActionIds.contains(id) { + removeActionIds.append(id) + if let itemView = item.view { + if !transition.animation.isImmediate { + itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false) + itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + itemView.removeFromSuperview() + }) + } else { + itemView.removeFromSuperview() + } + } + } + } + for id in removeActionIds { + self.actionItems.removeValue(forKey: id) + } + } + + let alertSize = CGSize(width: alertWidth, height: alertHeight) + let bounds = CGRect(origin: .zero, size: availableSize) + + transition.setFrame(view: self.dimView, frame: bounds) + transition.setFrame(view: self.containerView, frame: bounds) + self.containerView.update(size: availableSize, isDark: environment.theme.overallDarkAppearance, transition: transition) + + var availableHeight = availableSize.height + if component.configuration.allowInputInset, environment.inputHeight > 0.0 { + availableHeight -= environment.inputHeight + } + + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - alertSize.width) / 2.0), y: floorToScreenPixels((availableHeight - alertSize.height) / 2.0)), size: alertSize)) + self.backgroundView.update(size: alertSize, shape: .roundedRect(cornerRadius: 35.0), isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: .white), isInteractive: true, transition: transition) + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } -public func componentAlertController(theme: AlertControllerTheme, content: AnyComponent, actions: [ComponentAlertAction], actionLayout: ComponentAlertContentActionLayout = .horizontal, dismissOnOutsideTap: Bool = true) -> AlertController { - var dismissImpl: (() -> Void)? - let controller = AlertController(theme: theme, contentNode: ComponentAlertContentNode(theme: theme, content: content, actions: actions.map { action in - return ComponentAlertAction(type: action.type, title: action.title, action: { - dismissImpl?() - action.action() - }) - }, actionLayout: actionLayout, dismissOnOutsideTap: dismissOnOutsideTap)) - dismissImpl = { [weak controller] in - controller?.dismissAnimated() +open class AlertScreen: ViewControllerComponentContainer, KeyShortcutResponder { + public enum ActionAligmnent: Equatable { + case `default` + case vertical + } + + public struct Configuration: Equatable { + let actionAlignment: ActionAligmnent + let dismissOnOutsideTap: Bool + let allowInputInset: Bool + + public init( + actionAlignment: ActionAligmnent = .default, + dismissOnOutsideTap: Bool = true, + allowInputInset: Bool = false + ) { + self.actionAlignment = actionAlignment + self.dismissOnOutsideTap = dismissOnOutsideTap + self.allowInputInset = allowInputInset + } + } + + public struct Action: Equatable { + public enum ActionType: Equatable { + case generic + case `default` + case destructive + case defaultDestructive + } + public let title: String + public let type: ActionType + public let action: () -> Void + public let autoDismiss: Bool + public let isEnabled: Signal + public let progress: Signal + + public init( + id: AnyHashable? = nil, + title: String, + type: ActionType = .generic, + action: @escaping () -> Void = {}, + autoDismiss: Bool = true, + isEnabled: Signal = .single(true), + progress: Signal = .single(false) + ) { + self.type = type + self.title = title + self.action = action + self.autoDismiss = autoDismiss + self.isEnabled = isEnabled + self.progress = progress + + if let id { + self.id = id + } else { + self.id = title + } + } + + public static func ==(lhs: Action, rhs: Action) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.type != rhs.type { + return false + } + if lhs.autoDismiss != rhs.autoDismiss { + return false + } + return true + } + + fileprivate let id: AnyHashable + } + + private var processedDidAppear: Bool = false + private var processedDidDisappear: Bool = false + + private let readyValue = Promise(true) + override public var ready: Promise { + return self.readyValue + } + + public var dismissed: ((Bool) -> Void)? + + public init( + configuration: Configuration = Configuration(), + contentSignal: Signal<[AnyComponentWithIdentity], NoError>, + actionsSignal: Signal<[Action], NoError>, + updatedPresentationData: (initial: PresentationData, signal: Signal) + ) { + let componentReady = Promise() + + super.init( + component: AlertScreenComponent( + configuration: configuration, + content: contentSignal, + actions: actionsSignal, + ready: componentReady + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + presentationMode: .default, + updatedPresentationData: updatedPresentationData + ) + self.navigationPresentation = .flatModal + + //self.readyValue.set(componentReady.get() |> timeout(1.0, queue: .mainQueue(), alternate: .single(true))) + } + + public convenience init( + configuration: Configuration = Configuration(), + content: [AnyComponentWithIdentity], + actions: [Action], + updatedPresentationData: (initial: PresentationData, signal: Signal) + ) { + self.init( + configuration: configuration, + contentSignal: .single(content), + actionsSignal: .single(actions), + updatedPresentationData: updatedPresentationData + ) + } + + public convenience init( + context: AccountContext, + configuration: Configuration = Configuration(), + content: [AnyComponentWithIdentity], + actions: [Action] + ) { + self.init( + sharedContext: context.sharedContext, + configuration: configuration, + content: content, + actions: actions, + ) + } + + public convenience init( + sharedContext: SharedAccountContext, + configuration: Configuration = Configuration(), + content: [AnyComponentWithIdentity], + actions: [Action] + ) { + let presentationData = sharedContext.currentPresentationData.with { $0 } + let updatedPresentationDataSignal = sharedContext.presentationData + self.init( + configuration: configuration, + content: content, + actions: actions, + updatedPresentationData: (initial: presentationData, signal: updatedPresentationDataSignal) + ) + } + + public convenience init( + configuration: Configuration = Configuration(), + title: String? = nil, + text: String, + textAction: @escaping ([NSAttributedString.Key: Any]) -> Void = { _ in }, + actions: [Action], + updatedPresentationData: (initial: PresentationData, signal: Signal) + ) { + var content: [AnyComponentWithIdentity] = [] + if let title { + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: title) + ) + )) + } + if !text.isEmpty { + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text), action: textAction) + ) + )) + } + + self.init( + configuration: configuration, + content: content, + actions: actions, + updatedPresentationData: updatedPresentationData + ) + } + + public convenience init( + context: AccountContext, + configuration: Configuration = Configuration(), + title: String? = nil, + text: String, + actions: [Action] + ) { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let updatedPresentationDataSignal = context.sharedContext.presentationData + self.init( + configuration: configuration, + title: title, + text: text, + actions: actions, + updatedPresentationData: (initial: presentationData, signal: updatedPresentationDataSignal) + ) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !self.processedDidAppear { + self.processedDidAppear = true + if let componentView = self.node.hostView.componentView as? AlertScreenComponent.View { + componentView.animateIn() + } + } + } + + private func superDismiss() { + super.dismiss() + } + + override open func dismiss(completion: (() -> Void)? = nil) { + if !self.processedDidDisappear { + self.processedDidDisappear = true + + if let componentView = self.node.hostView.componentView as? AlertScreenComponent.View { + let dismissedByTapOutside = componentView.dismissedByTapOutside + componentView.animateOut(completion: { [weak self] in + if let self { + self.dismissed?(dismissedByTapOutside) + self.superDismiss() + } + completion?() + }) + } else { + super.dismiss(completion: completion) + } + } + } + + public var keyShortcuts: [KeyShortcut] { + return [ + KeyShortcut( + input: UIKeyCommand.inputEscape, + modifiers: [], + action: { [weak self] in + if let componentView = self?.node.hostView.componentView as? AlertScreenComponent.View { + componentView.handleKeyCommand(.escape) + } + } + ), + KeyShortcut( + input: "W", + modifiers: [.command], + action: { [weak self] in + if let componentView = self?.node.hostView.componentView as? AlertScreenComponent.View { + componentView.handleKeyCommand(.escape) + } + } + ), + KeyShortcut( + input: "\r", + modifiers: [], + action: { [weak self] in + if let componentView = self?.node.hostView.componentView as? AlertScreenComponent.View { + componentView.handleKeyCommand(.enter) + } + } + ), + KeyShortcut( + input: UIKeyCommand.inputUpArrow, + modifiers: [], + action: { [weak self] in + if let componentView = self?.node.hostView.componentView as? AlertScreenComponent.View { + componentView.handleKeyCommand(.up) + } + } + ), + KeyShortcut( + input: UIKeyCommand.inputDownArrow, + modifiers: [], + action: { [weak self] in + if let componentView = self?.node.hostView.componentView as? AlertScreenComponent.View { + componentView.handleKeyCommand(.down) + } + } + ), + KeyShortcut( + input: UIKeyCommand.inputLeftArrow, + modifiers: [], + action: { [weak self] in + if let componentView = self?.node.hostView.componentView as? AlertScreenComponent.View { + componentView.handleKeyCommand(.left) + } + } + ), + KeyShortcut( + input: UIKeyCommand.inputRightArrow, + modifiers: [], + action: { [weak self] in + if let componentView = self?.node.hostView.componentView as? AlertScreenComponent.View { + componentView.handleKeyCommand(.right) + } + } + ) + ] + } +} + +public final class ActionSelectionGestureRecognizer: UIGestureRecognizer { + private var initialLocation: CGPoint? + private var currentLocation: CGPoint? + + public override init(target: Any?, action: Selector?) { + super.init(target: target, action: action) + + self.delaysTouchesBegan = false + self.delaysTouchesEnded = false + } + + public override func reset() { + super.reset() + + self.initialLocation = nil + } + + public override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if self.initialLocation == nil { + self.initialLocation = touches.first?.location(in: self.view) + } + self.currentLocation = self.initialLocation + + self.state = .began + } + + public override func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + self.state = .ended + } + + public override func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + self.state = .cancelled + } + + public override func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + self.currentLocation = touches.first?.location(in: self.view) + + self.state = .changed + } + + public func translation(in: UIView?) -> CGPoint { + if let initialLocation = self.initialLocation, let currentLocation = self.currentLocation { + return CGPoint(x: currentLocation.x - initialLocation.x, y: currentLocation.y - initialLocation.y) + } + return CGPoint() } - return controller } diff --git a/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift new file mode 100644 index 00000000..8e68f3a1 --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift @@ -0,0 +1,366 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import TelegramCore +import TelegramPresentationData +import MultilineTextComponent +import MultilineTextWithEntitiesComponent +import Markdown +import TextFormat +import AccountContext + +private let titleFont = Font.bold(17.0) +private let defaultTextFont = Font.regular(15.0) +private let defaultBoldTextFont = Font.semibold(15.0) +private let defaultItalicTextFont = Font.italic(15.0) +private let defaultBoldItalicTextFont = Font.with(size: 15.0, weight: .semibold, traits: [.italic]) +private let defaultFixedTextFont = Font.monospace(15.0) +private let smallTextFont = Font.regular(14.0) +private let smallBoldTextFont = Font.semibold(14.0) +private let smallItalicTextFont = Font.italic(14.0) +private let smallBoldItalicTextFont = Font.with(size: 14.0, weight: .semibold, traits: [.italic]) +private let smallFixedTextFont = Font.monospace(14.0) +private let backgroundInset: CGFloat = 8.0 + +public final class AlertTitleComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + public enum Alignment { + case `default` + case center + } + + let title: String + let alignment: Alignment + + public init( + title: String, + alignment: Alignment = .default + ) { + self.title = title + self.alignment = alignment + } + + public static func ==(lhs: AlertTitleComponent, rhs: AlertTitleComponent) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.alignment != rhs.alignment { + return false + } + return true + } + + public final class View: UIView { + private let title = ComponentView() + + private var component: AlertTitleComponent? + private weak var state: EmptyComponentState? + + func update(component: AlertTitleComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.title, + font: titleFont, + textColor: environment.theme.actionSheet.primaryTextColor + )), + horizontalAlignment: component.alignment == .center ? .center : .natural, + maximumNumberOfLines: 0 + )), + environment: {}, + containerSize: availableSize + ) + + let titleOriginX: CGFloat + switch component.alignment { + case .default: + titleOriginX = 0.0 + case .center: + titleOriginX = floorToScreenPixels((availableSize.width - titleSize.width) / 2.0) + } + let titleFrame = CGRect(origin: CGPoint(x: titleOriginX, y: 0.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + return CGSize(width: availableSize.width, height: titleSize.height) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public final class AlertTextComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + public enum Content: Equatable { + case plain(String) + case attributed(NSAttributedString) + case textWithEntities(AccountContext, String, [MessageTextEntity]) + + public static func ==(lhs: Content, rhs: Content) -> Bool { + switch lhs { + case let .plain(text): + if case .plain(text) = rhs { + return true + } else { + return false + } + case let .attributed(text): + if case .attributed(text) = rhs { + return true + } else { + return false + } + case let .textWithEntities(_, lhsText, lhsEntities): + if case let .textWithEntities(_, rhsText, rhsEntities) = rhs { + return lhsText == rhsText && lhsEntities == rhsEntities + } else { + return false + } + } + } + } + + public enum Alignment: Equatable { + case `default` + case center + } + + public enum Color: Equatable { + case primary + case secondary + case destructive + } + + public enum TextStyle: Equatable { + case `default` + case small + case bold + } + + public enum Style: Equatable { + case plain(TextStyle) + case background(TextStyle) + } + + let content: Content + let alignment: Alignment + let color: Color + let style: Style + let insets: UIEdgeInsets + let action: ([NSAttributedString.Key: Any]) -> Void + + public init( + content: Content, + alignment: Alignment = .default, + color: Color = .primary, + style: Style = .plain(.default), + insets: UIEdgeInsets = .zero, + action: @escaping ([NSAttributedString.Key: Any]) -> Void = { _ in } + ) { + self.content = content + self.alignment = alignment + self.color = color + self.style = style + self.insets = insets + self.action = action + } + + public static func ==(lhs: AlertTextComponent, rhs: AlertTextComponent) -> Bool { + if lhs.content != rhs.content { + return false + } + if lhs.alignment != rhs.alignment { + return false + } + if lhs.color != rhs.color { + return false + } + if lhs.style != rhs.style { + return false + } + if lhs.insets != rhs.insets { + return false + } + return true + } + + public final class View: UIView { + private let background = ComponentView() + private let text = ComponentView() + + private var component: AlertTextComponent? + private weak var state: EmptyComponentState? + + func update(component: AlertTextComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let textColor: UIColor + switch component.color { + case .primary: + textColor = environment.theme.actionSheet.primaryTextColor + case .secondary: + textColor = environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.35) + case .destructive: + textColor = environment.theme.actionSheet.destructiveActionTextColor + } + let linkColor = environment.theme.actionSheet.controlAccentColor + + let textFont: UIFont + let boldTextFont: UIFont + let italicTextFont: UIFont + let fixedTextFont: UIFont + switch component.style { + case let .plain(textStyle), let .background(textStyle): + switch textStyle { + case .default: + textFont = defaultTextFont + boldTextFont = defaultBoldTextFont + italicTextFont = defaultItalicTextFont + fixedTextFont = defaultFixedTextFont + case .small: + textFont = smallTextFont + boldTextFont = smallBoldTextFont + italicTextFont = smallItalicTextFont + fixedTextFont = smallFixedTextFont + case .bold: + textFont = defaultBoldTextFont + boldTextFont = defaultBoldTextFont + italicTextFont = defaultBoldItalicTextFont + fixedTextFont = defaultFixedTextFont + } + } + + var finalText: NSAttributedString + var context: AccountContext? + switch component.content { + case let .plain(text): + let markdownAttributes = MarkdownAttributes( + body: MarkdownAttributeSet(font: textFont, textColor: textColor), + bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), + link: MarkdownAttributeSet(font: textFont, textColor: linkColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + finalText = parseMarkdownIntoAttributedString(text, attributes: markdownAttributes) + case let .attributed(attributedText): + finalText = attributedText + case let .textWithEntities(accountContext, text, entities): + context = accountContext + finalText = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: linkColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: italicTextFont, boldItalicFont: italicTextFont, fixedFont: fixedTextFont, blockQuoteFont: textFont, message: nil) + } + + var hasCenterAlignment = component.alignment == .center + switch component.style { + case .background: + hasCenterAlignment = true + default: + break + } + + let textConstrainedSize = CGSize(width: availableSize.width, height: availableSize.height) + + let textSize = self.text.update( + transition: transition, + component: AnyComponent( + MultilineTextWithEntitiesComponent( + context: context, + animationCache: context?.animationCache, + animationRenderer: context?.animationRenderer, + placeholderColor: textColor.withMultipliedAlpha(0.1), + text: .plain(finalText), + horizontalAlignment: hasCenterAlignment ? .center : .natural, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + spoilerColor: textColor, + highlightColor: linkColor.withAlphaComponent(0.2), + manualVisibilityControl: true, + resetAnimationsOnVisibilityChange: true, + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { attributes, _ in + component.action(attributes) + } + ) + ), + environment: {}, + containerSize: textConstrainedSize + ) + + var textOffset = CGPoint() + if hasCenterAlignment { + textOffset.x = floorToScreenPixels((availableSize.width - textSize.width) / 2.0) + } + var size = CGSize(width: availableSize.width, height: textSize.height) + if case .background = component.style { + let backgroundSize = CGSize(width: availableSize.width + 20.0, height: textSize.height + backgroundInset * 2.0) + size = backgroundSize + textOffset = CGPoint(x: textOffset.x, y: backgroundInset) + + let _ = self.background.update( + transition: transition, + component: AnyComponent( + FilledRoundedRectangleComponent( + color: textColor.withMultipliedAlpha(0.1), + cornerRadius: .value(10.0), + smoothCorners: true + ) + ), + environment: {}, + containerSize: backgroundSize + ) + let backgroundFrame = CGRect(origin: CGPoint(x: -10.0, y: component.insets.top), size: backgroundSize) + if let backgroundView = self.background.view { + if backgroundView.superview == nil { + self.addSubview(backgroundView) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + } + + let textFrame = CGRect(origin: textOffset.offsetBy(dx: 0.0, dy: component.insets.top), size: textSize) + if let textView = self.text.view { + if textView.superview == nil { + self.addSubview(textView) + } + transition.setFrame(view: textView, frame: textFrame) + } + return CGSize(width: size.width, height: size.height + component.insets.top + component.insets.bottom) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/AnimatedTextComponent/BUILD b/submodules/TelegramUI/Components/AnimatedTextComponent/BUILD index f6ee40df..782ad3be 100644 --- a/submodules/TelegramUI/Components/AnimatedTextComponent/BUILD +++ b/submodules/TelegramUI/Components/AnimatedTextComponent/BUILD @@ -15,6 +15,7 @@ swift_library( "//submodules/ComponentFlow", "//submodules/TelegramPresentationData", "//submodules/Components/BundleIconComponent", + "//submodules/Components/MultilineTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift index cc44a9c2..0d6438d4 100644 --- a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift +++ b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift @@ -4,6 +4,7 @@ import Display import ComponentFlow import TelegramPresentationData import BundleIconComponent +import MultilineTextComponent extension ComponentTransition { func animateBlur(layer: CALayer, from: CGFloat, to: CGFloat, delay: Double = 0.0, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { @@ -46,6 +47,7 @@ public final class AnimatedTextComponent: Component { public let items: [Item] public let noDelay: Bool public let animateScale: Bool + public let animateSlide: Bool public let preferredDirectionIsDown: Bool public let blur: Bool @@ -55,6 +57,7 @@ public final class AnimatedTextComponent: Component { items: [Item], noDelay: Bool = false, animateScale: Bool = true, + animateSlide: Bool = true, preferredDirectionIsDown: Bool = false, blur: Bool = false ) { @@ -63,6 +66,7 @@ public final class AnimatedTextComponent: Component { self.items = items self.noDelay = noDelay self.animateScale = animateScale + self.animateSlide = animateSlide self.preferredDirectionIsDown = preferredDirectionIsDown self.blur = blur } @@ -83,6 +87,9 @@ public final class AnimatedTextComponent: Component { if lhs.animateScale != rhs.animateScale { return false } + if lhs.animateSlide != rhs.animateSlide { + return false + } if lhs.preferredDirectionIsDown != rhs.preferredDirectionIsDown { return false } @@ -101,6 +108,8 @@ public final class AnimatedTextComponent: Component { public final class View: UIView { private var characters: [CharacterKey: ComponentView] = [:] + private var spaceSize: CGSize? + private var component: AnimatedTextComponent? private weak var state: EmptyComponentState? @@ -113,6 +122,15 @@ public final class AnimatedTextComponent: Component { } func update(component: AnimatedTextComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let spaceSize: CGSize + if let current = self.spaceSize, self.component?.font == component.font { + spaceSize = current + } else { + let spaceSizeValue = NSAttributedString(string: " ", font: component.font, textColor: .black).boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil).size + spaceSize = CGSize(width: ceil(spaceSizeValue.width), height: ceil(spaceSizeValue.height)) + self.spaceSize = spaceSize + } + self.component = component self.state = state @@ -220,7 +238,7 @@ public final class AnimatedTextComponent: Component { itemText = [.icon(iconName, tint, offset)] } var index = 0 - for character in itemText { + characterLoop: for character in itemText { let characterKey = CharacterKey(itemId: item.id, index: index, value: character.value) index += 1 @@ -236,13 +254,23 @@ public final class AnimatedTextComponent: Component { let characterComponent: AnyComponent var characterOffset: CGPoint = .zero + var addTrailingSpace = false switch character { case let .text(text): - characterComponent = AnyComponent(Text( - text: String(text), - font: component.font, - color: component.color - )) + if text == " " { + size.height = max(size.height, ceil(spaceSize.height)) + size.width += max(0.0, ceil(spaceSize.width)) + + continue characterLoop + } else { + if text.hasSuffix(" ") { + addTrailingSpace = true + } + + characterComponent = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: text, font: component.font, textColor: component.color)) + )) + } case let .icon(iconName, tint, offset): characterComponent = AnyComponent(BundleIconComponent( name: iconName, @@ -263,6 +291,7 @@ public final class AnimatedTextComponent: Component { if characterComponentView.superview == nil { characterComponentView.layer.rasterizationScale = UIScreenScale self.addSubview(characterComponentView) + characterComponentView.layer.anchorPoint = CGPoint() animateIn = true } @@ -282,8 +311,9 @@ public final class AnimatedTextComponent: Component { } characterComponentView.bounds = CGRect(origin: CGPoint(), size: characterFrame.size) - let deltaPosition = CGPoint(x: characterFrame.midX - characterComponentView.frame.midX, y: characterFrame.midY - characterComponentView.frame.midY) - characterComponentView.center = characterFrame.center + + let deltaPosition = CGPoint(x: characterFrame.minX - characterComponentView.frame.minX, y: characterFrame.minY - characterComponentView.frame.minY) + characterComponentView.center = characterFrame.origin characterComponentView.layer.animatePosition(from: CGPoint(x: -deltaPosition.x, y: -deltaPosition.y), to: CGPoint(), duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring, additive: true) } } @@ -305,13 +335,20 @@ public final class AnimatedTextComponent: Component { if component.blur { ComponentTransition.easeInOut(duration: 0.2).animateBlur(layer: characterComponentView.layer, from: transitionBlurRadius, to: 0.0, delay: delayNorm * delayWidth) } - characterComponentView.layer.animatePosition(from: CGPoint(x: 0.0, y: characterSize.height * offsetNorm), to: CGPoint(), duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + if component.animateSlide { + characterComponentView.layer.animatePosition(from: CGPoint(x: 0.0, y: characterSize.height * offsetNorm), to: CGPoint(), duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } characterComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18, delay: delayNorm * delayWidth) } } size.height = max(size.height, characterSize.height) - size.width += max(0.0, characterSize.width - UIScreenPixel * 2.0) + size.width += max(0.0, characterSize.width - UIScreenPixel) + + if addTrailingSpace { + size.height = max(size.height, ceil(spaceSize.height)) + size.width += max(0.0, ceil(spaceSize.width)) + } } } @@ -342,7 +379,9 @@ public final class AnimatedTextComponent: Component { } else { targetY = characterComponentView.center.y - characterComponentView.bounds.height * offsetNorm } - outScaleTransition.setPosition(view: characterComponentView, position: CGPoint(x: characterComponentView.center.x, y: targetY), delay: delayNorm * delayWidth) + if component.animateSlide { + outScaleTransition.setPosition(view: characterComponentView, position: CGPoint(x: characterComponentView.center.x, y: targetY), delay: delayNorm * delayWidth) + } outAlphaTransition.setAlpha(view: characterComponentView, alpha: 0.0, delay: delayNorm * delayWidth, completion: { [weak characterComponentView] _ in characterComponentView?.removeFromSuperview() }) diff --git a/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift b/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift index 4b7fac1b..5d798af8 100644 --- a/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift +++ b/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift @@ -363,7 +363,7 @@ public final class AsyncListComponent: Component { init() { self.contentContainer = UIView() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.view.addSubview(self.contentContainer) diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift index 59b5b88a..3a3a979c 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift @@ -212,7 +212,7 @@ private func attachmentFileControllerEntries(presentationData: PresentationData, if case .audio = mode { if let savedMusic, savedMusic.count > 0 { - entries.append(.savedHeader(presentationData.theme, "SAVED MUSIC".uppercased())) + entries.append(.savedHeader(presentationData.theme, presentationData.strings.MediaEditor_Audio_SavedMusic.uppercased())) var savedMusic = savedMusic var showMore = false if savedMusic.count > 4 && !state.savedMusicExpanded { @@ -225,7 +225,7 @@ private func attachmentFileControllerEntries(presentationData: PresentationData, i += 1 } if showMore { - entries.append(.showMore(presentationData.theme, "Show More")) + entries.append(.showMore(presentationData.theme, presentationData.strings.MediaEditor_Audio_ShowMore)) } } } @@ -459,16 +459,17 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre let updatedTheme = presentationData.theme.withModalBlocksBackground() presentationData = presentationData.withUpdated(theme: updatedTheme) - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let closeButton = GlassBarButtonComponent( size: barButtonSize, - backgroundColor: presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: presentationData.theme.overallDarkAppearance, state: .generic, + animateScale: false, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: presentationData.theme.chat.inputPanel.panelControlColor ) )), action: { _ in @@ -489,13 +490,14 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre let searchButton = GlassBarButtonComponent( size: barButtonSize, - backgroundColor: presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: presentationData.theme.overallDarkAppearance, state: .generic, + animateScale: false, component: AnyComponentWithIdentity(id: "search", component: AnyComponent( BundleIconComponent( name: "Navigation/Search", - tintColor: presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: presentationData.theme.chat.inputPanel.panelControlColor ) )), action: { _ in @@ -509,7 +511,7 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre } ) let searchButtonComponent = state.searching ? nil : AnyComponentWithIdentity(id: "search", component: AnyComponent(searchButton)) - let searchButtonNode = existingSearchButton.modify { current in + let searchButtonNode: BarComponentHostNode? = !state.searching ? existingSearchButton.modify { current in let buttonNode: BarComponentHostNode if let current { buttonNode = current @@ -518,7 +520,7 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre buttonNode = BarComponentHostNode(component: searchButtonComponent, size: barButtonSize) } return buttonNode - } + } : nil let previousRecentDocuments = previousRecentDocuments.swap(recentDocuments) let crossfade = previousRecentDocuments == nil && recentDocuments != nil @@ -542,7 +544,7 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre case .recent: title = presentationData.strings.Attachment_File case .audio: - title = "Audio" + title = presentationData.strings.MediaEditor_Audio_Title } let controllerState = ItemListControllerState( diff --git a/submodules/TelegramUI/Components/AvatarComponent/BUILD b/submodules/TelegramUI/Components/AvatarComponent/BUILD new file mode 100644 index 00000000..f52981e8 --- /dev/null +++ b/submodules/TelegramUI/Components/AvatarComponent/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AvatarComponent", + module_name = "AvatarComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/ComponentFlow", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/AvatarNode", + "//submodules/AccountContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/AvatarComponent/Sources/AvatarComponent.swift b/submodules/TelegramUI/Components/AvatarComponent/Sources/AvatarComponent.swift new file mode 100644 index 00000000..a9d18e4e --- /dev/null +++ b/submodules/TelegramUI/Components/AvatarComponent/Sources/AvatarComponent.swift @@ -0,0 +1,116 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramCore +import TelegramPresentationData +import AvatarNode +import AccountContext + +public final class AvatarComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let peer: EnginePeer + let icon: AnyComponent? + + public init( + context: AccountContext, + theme: PresentationTheme, + peer: EnginePeer, + icon: AnyComponent? = nil + ) { + self.context = context + self.theme = theme + self.peer = peer + self.icon = icon + } + + public static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.peer != rhs.peer { + return false + } + if lhs.icon != rhs.icon { + return false + } + return true + } + + public final class View: UIView { + private let avatarNode: AvatarNode + private var icon: ComponentView? + + private var component: AvatarComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 42.0)) + + super.init(frame: frame) + + self.addSubnode(self.avatarNode) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + if self.component == nil { + self.avatarNode.font = avatarPlaceholderFont(size: ceil(42.0 * availableSize.width / 100.0)) + } + + self.component = component + self.state = state + + var cutoutRect: CGRect? + if let icon = component.icon { + let iconView: ComponentView + if let current = self.icon { + iconView = current + } else { + iconView = ComponentView() + self.icon = iconView + } + let iconSize = iconView.update( + transition: .immediate, + component: icon, + environment: {}, + containerSize: availableSize + ) + let iconFrame = CGRect(origin: CGPoint(x: availableSize.width - iconSize.width + 2.0, y: availableSize.height - iconSize.height + 2.0), size: iconSize) + if let iconView = iconView.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + iconView.frame = iconFrame + } + cutoutRect = CGRect(origin: CGPoint(x: iconFrame.minX, y: availableSize.height - iconFrame.maxY), size: iconFrame.size).insetBy(dx: -2.0 + UIScreenPixel, dy: -2.0 + UIScreenPixel) + } + + self.avatarNode.frame = CGRect(origin: .zero, size: availableSize) + self.avatarNode.setPeer( + context: component.context, + theme: component.theme, + peer: component.peer, + synchronousLoad: true, + cutoutRect: cutoutRect + ) + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD b/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD index a8753199..c8f7959a 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD @@ -41,7 +41,7 @@ swift_library( "//submodules/TelegramUI/Components/MediaEditor", "//submodules/TelegramUI/Components/AvatarBackground", "//submodules/TelegramUI/Components/LottieComponent", - "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + "//submodules/TelegramUI/Components/PremiumAlertController", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 67727795..a43f3615 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -27,6 +27,7 @@ import MediaEditor import AvatarBackground import LottieComponent import UndoUI +import PremiumAlertController public struct AvatarKeyboardInputData: Equatable { var emoji: EmojiPagerContentComponent diff --git a/submodules/TelegramUI/Components/BackButtonComponent/Sources/BackButtonComponent.swift b/submodules/TelegramUI/Components/BackButtonComponent/Sources/BackButtonComponent.swift index d31c4d1e..01f09a66 100644 --- a/submodules/TelegramUI/Components/BackButtonComponent/Sources/BackButtonComponent.swift +++ b/submodules/TelegramUI/Components/BackButtonComponent/Sources/BackButtonComponent.swift @@ -88,7 +88,7 @@ public final class BackButtonComponent: Component { self.component = component if self.arrowView.image == nil { - self.arrowView.image = NavigationBar.backArrowImage(color: .white)?.withRenderingMode(.alwaysTemplate) + self.arrowView.image = navigationBarBackArrowImage(color: .white)?.withRenderingMode(.alwaysTemplate) } self.arrowView.tintColor = component.color diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/BackButtonView.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/BackButtonView.swift index 696d0a57..a8a355ba 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/BackButtonView.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/BackButtonView.swift @@ -29,7 +29,7 @@ final class BackButtonView: HighlightableButton { var pressAction: (() -> Void)? override init(frame: CGRect) { - self.iconView = UIImageView(image: NavigationBar.backArrowImage(color: .white)) + self.iconView = UIImageView(image: navigationBarBackArrowImage(color: .white)) self.iconView.isUserInteractionEnabled = false self.textView = TextView() diff --git a/submodules/TelegramUI/Components/CameraScreen/BUILD b/submodules/TelegramUI/Components/CameraScreen/BUILD index b169af47..5d92b5d4 100644 --- a/submodules/TelegramUI/Components/CameraScreen/BUILD +++ b/submodules/TelegramUI/Components/CameraScreen/BUILD @@ -95,6 +95,8 @@ swift_library( "//submodules/TelegramCallsUI", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode", + "//submodules/TelegramUI/Components/LiquidLens", + "//submodules/TelegramUI/Components/TabSelectionRecognizer", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index b29f30a5..8f05aa00 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -2097,7 +2097,7 @@ private final class CameraScreenComponent: CombinedComponent { if !isSticker, case .none = component.cameraState.recording, component.cameraState.isStreaming == .none && !state.isTransitioning && hasAllRequiredAccess && component.cameraState.collageProgress < 1.0 - .ulpOfOne { let availableModeControlSize: CGSize if isTablet { - availableModeControlSize = CGSize(width: panelWidth, height: 120.0) + availableModeControlSize = CGSize(width: floor(panelWidth), height: 120.0) } else { availableModeControlSize = availableSize } @@ -2131,7 +2131,6 @@ private final class CameraScreenComponent: CombinedComponent { modeControlPosition = CGPoint(x: availableSize.width / 2.0, y: availableSize.height - environment.safeInsets.bottom + modeControl.size.height / 2.0 + controlsBottomInset + 16.0) } context.add(modeControl - .clipsToBounds(true) .position(modeControlPosition) .appear(.default(alpha: true)) .disappear(.default(alpha: true)) diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift index cfb581ad..5936b26a 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift @@ -5,6 +5,8 @@ import ComponentFlow import MultilineTextComponent import TelegramPresentationData import GlassBackgroundComponent +import LiquidLens +import TabSelectionRecognizer extension CameraMode { func title(strings: PresentationStrings) -> String { @@ -70,28 +72,17 @@ final class ModeComponent: Component { final class View: UIView, ComponentTaggedView { private var component: ModeComponent? + private var state: EmptyComponentState? final class ItemView: HighlightTrackingButton { - var pressed: () -> Void = { - - } - init() { super.init(frame: .zero) - - self.isExclusiveTouch = true - - self.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) } required init(coder: NSCoder) { preconditionFailure() } - @objc func buttonPressed() { - self.pressed() - } - func update(isTablet: Bool, value: String, selected: Bool, tintColor: UIColor) -> CGSize { let accentColor: UIColor let normalColor: UIColor @@ -113,9 +104,15 @@ final class ModeComponent: Component { } private var backgroundView = UIView() - private var glassContainerView = GlassBackgroundContainerView() - private var selectionView = GlassBackgroundView() - private var itemViews: [Int32: ItemView] = [:] + private var backgroundContainer = GlassBackgroundContainerView() + + private var liquidLensView: LiquidLensView? + + private var itemViews: [AnyHashable: ItemView] = [:] + private var selectedItemViews: [AnyHashable: ItemView] = [:] + + private var tabSelectionRecognizer: TabSelectionRecognizer? + private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)? public func matches(tag: Any) -> Bool { if let component = self.component, let componentTag = component.tag { @@ -132,12 +129,11 @@ final class ModeComponent: Component { self.backgroundView.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.11) self.backgroundView.layer.cornerRadius = 24.0 - + self.layer.allowsGroupOpacity = true self.addSubview(self.backgroundView) - self.backgroundView.addSubview(self.glassContainerView) - self.glassContainerView.contentView.addSubview(self.selectionView) + self.backgroundView.addSubview(self.backgroundContainer) } required init?(coder aDecoder: NSCoder) { @@ -162,14 +158,82 @@ final class ModeComponent: Component { override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return self.backgroundView.frame.contains(point) } - - func update(component: ModeComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { - self.component = component - let isTablet = component.isTablet - let updatedMode = component.updatedMode + private func item(at point: CGPoint) -> AnyHashable? { + var closestItem: (AnyHashable, CGFloat)? + for (id, itemView) in self.itemViews { + if itemView.frame.contains(point) { + return id + } else { + let distance = abs(point.x - itemView.center.x) + if let closestItemValue = closestItem { + if closestItemValue.1 > distance { + closestItem = (id, distance) + } + } else { + closestItem = (id, distance) + } + } + } + return closestItem?.0 + } + + @objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) { + guard let component = self.component, let liquidLensView = self.liquidLensView else { + return + } + let location = recognizer.location(in: liquidLensView.contentView) + switch recognizer.state { + case .began: + if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] { + let startX = itemView.frame.minX - 4.0 + self.selectionGestureState = (startX, startX, itemId) + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) + } + case .changed: + if var selectionGestureState = self.selectionGestureState { + selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x + if let itemId = self.item(at: location) { + selectionGestureState.itemId = itemId + } + self.selectionGestureState = selectionGestureState + self.state?.updated(transition: .immediate, isLocal: true) + } + case .ended, .cancelled: + if let selectionGestureState = self.selectionGestureState { + self.selectionGestureState = nil + if case .ended = recognizer.state { + guard let item = component.availableModes.first(where: { AnyHashable($0.rawValue) == selectionGestureState.itemId }) else { + return + } + component.updatedMode(item) + } + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) + } + default: + break + } + } + + func update(component: ModeComponent, availableSize: CGSize, state: EmptyComponentState, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let isTablet = component.isTablet + + let liquidLensView: LiquidLensView + if let current = self.liquidLensView { + liquidLensView = current + } else { + liquidLensView = LiquidLensView(kind: isTablet ? .noContainer : .externalContainer) + self.liquidLensView = liquidLensView + self.backgroundContainer.contentView.addSubview(liquidLensView) + + let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:))) + self.tabSelectionRecognizer = tabSelectionRecognizer + liquidLensView.addGestureRecognizer(tabSelectionRecognizer) + } - self.glassContainerView.isHidden = component.isTablet self.backgroundView.backgroundColor = component.isTablet ? .clear : UIColor(rgb: 0xffffff, alpha: 0.11) let inset: CGFloat = 23.0 @@ -177,28 +241,36 @@ final class ModeComponent: Component { var i = 0 var itemFrame = CGRect(origin: isTablet ? .zero : CGPoint(x: inset, y: 0.0), size: buttonSize) - var selectedCenter = itemFrame.minX var selectedFrame = itemFrame - var validKeys: Set = Set() + var validKeys: Set = Set() for mode in component.availableModes.reversed() { let id = mode.rawValue validKeys.insert(id) let itemView: ItemView - if let current = self.itemViews[id] { + let selectedItemView: ItemView + if let current = self.itemViews[id], let currentSelected = self.selectedItemViews[id] { itemView = current + selectedItemView = currentSelected } else { itemView = ItemView() - self.backgroundView.addSubview(itemView) + itemView.isUserInteractionEnabled = false self.itemViews[id] = itemView - } - itemView.pressed = { - updatedMode(mode) + liquidLensView.contentView.addSubview(itemView) + + selectedItemView = ItemView() + selectedItemView.isUserInteractionEnabled = false + self.selectedItemViews[id] = selectedItemView + liquidLensView.selectedContentView.addSubview(selectedItemView) } - let itemSize = itemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: mode == component.currentMode, tintColor: component.tintColor) + let itemSize = itemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: false, tintColor: component.tintColor) itemView.bounds = CGRect(origin: .zero, size: itemSize) + + let _ = selectedItemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: true, tintColor: component.tintColor) + selectedItemView.bounds = CGRect(origin: .zero, size: itemSize) + itemFrame = CGRect(origin: itemFrame.origin, size: itemSize) if mode == component.currentMode { @@ -207,21 +279,17 @@ final class ModeComponent: Component { if isTablet { itemView.center = CGPoint(x: availableSize.width / 2.0, y: itemFrame.midY) - if mode == component.currentMode { - selectedCenter = itemFrame.midY - } + selectedItemView.center = itemView.center itemFrame = itemFrame.offsetBy(dx: 0.0, dy: tabletButtonSize.height + spacing) } else { itemView.center = CGPoint(x: itemFrame.midX, y: itemFrame.midY) - if mode == component.currentMode { - selectedCenter = itemFrame.midX - } + selectedItemView.center = itemView.center itemFrame = itemFrame.offsetBy(dx: itemFrame.width + spacing, dy: 0.0) } i += 1 } - var removeKeys: [Int32] = [] + var removeKeys: [AnyHashable] = [] for (id, itemView) in self.itemViews { if !validKeys.contains(id) { removeKeys.append(id) @@ -237,10 +305,12 @@ final class ModeComponent: Component { let totalSize: CGSize let size: CGSize + var cornerRadius: CGFloat? if isTablet { totalSize = CGSize(width: availableSize.width, height: tabletButtonSize.height * CGFloat(component.availableModes.count) + spacing * CGFloat(component.availableModes.count - 1)) size = CGSize(width: availableSize.width, height: availableSize.height) - transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height / 2.0 - selectedCenter), size: totalSize)) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: totalSize)) + cornerRadius = 20.0 } else { size = CGSize(width: availableSize.width, height: buttonSize.height) totalSize = CGSize(width: itemFrame.minX - spacing + inset, height: buttonSize.height) @@ -248,12 +318,26 @@ final class ModeComponent: Component { } let containerFrame = CGRect(origin: .zero, size: self.backgroundView.frame.size) - transition.setFrame(view: self.glassContainerView, frame: containerFrame) + transition.setFrame(view: self.backgroundContainer, frame: containerFrame) - let selectionFrame = selectedFrame.insetBy(dx: -20.0, dy: 3.0) - self.glassContainerView.update(size: containerFrame.size, isDark: true, transition: .immediate) - self.selectionView.update(size: selectionFrame.size, cornerRadius: selectionFrame.height * 0.5, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0xffffff, alpha: 0.16)), transition: transition) - transition.setFrame(view: self.selectionView, frame: selectionFrame) + let selectionFrame = selectedFrame.insetBy(dx: -23.0, dy: 3.0) + var lensSelection: (origin: CGPoint, size: CGSize) + if let selectionGestureState = self.selectionGestureState, !isTablet { + lensSelection = (CGPoint(x: selectionGestureState.currentX, y: 0.0), selectionFrame.size) + } else { + lensSelection = (CGPoint(x: selectionFrame.minX, y: selectionFrame.minY), selectionFrame.size) + } + + if isTablet { + lensSelection.size.width = size.width + } else { + lensSelection.size.height = containerFrame.size.height + lensSelection.origin.y = 0.0 + } + + transition.setFrame(view: liquidLensView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: containerFrame.size)) + liquidLensView.update(size: containerFrame.size, cornerRadius: cornerRadius, selectionOrigin: CGPoint(x: max(0.0, min(lensSelection.origin.x, containerFrame.size.width - lensSelection.size.width)), y: lensSelection.origin.y), selectionSize: lensSelection.size, inset: 3.0, isDark: true, isLifted: self.selectionGestureState != nil && !isTablet, isCollapsed: false, transition: transition) + self.backgroundContainer.update(size: containerFrame.size, isDark: true, transition: .immediate) return size } @@ -264,7 +348,7 @@ final class ModeComponent: Component { } func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) + return view.update(component: self, availableSize: availableSize, state: state, transition: transition) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/BUILD b/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/BUILD new file mode 100644 index 00000000..c60929ee --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/BUILD @@ -0,0 +1,27 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatAgeRestrictionAlertController", + module_name = "ChatAgeRestrictionAlertController", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/Sources/ChatAgeRestrictionAlertController.swift b/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/Sources/ChatAgeRestrictionAlertController.swift new file mode 100644 index 00000000..cf87df83 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/Sources/ChatAgeRestrictionAlertController.swift @@ -0,0 +1,64 @@ +import Foundation +import UIKit +import SwiftSignalKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import AccountContext +import ComponentFlow +import AlertComponent +import AlertCheckComponent + +public func chatAgeRestrictionAlertController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)?, + parentController: ViewController, + completion: @escaping (Bool) -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + let checkState = AlertCheckComponent.ExternalState() + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.SensitiveContent_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.SensitiveContent_Text)) + ) + )) + content.append(AnyComponentWithIdentity( + id: "check", + component: AnyComponent( + AlertCheckComponent(title: strings.SensitiveContent_ShowAlways, initialValue: false, externalState: checkState) + ) + )) + + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) + } + + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + content: content, + actions: [ + .init(title: strings.SensitiveContent_ViewAnyway, type: .default, action: { + completion(checkState.value) + }), + .init(title: strings.Common_Cancel) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + return alertController +} diff --git a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift index 671acfb7..4687fad8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift @@ -75,12 +75,8 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { strongSelf.contextAction?(strongSelf.containerNode, gesture) } - self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 37.0, height: 37.0)).offsetBy(dx: 10.0, dy: 1.0) - self.avatarNode.frame = self.containerNode.bounds - - #if DEBUG - //self.hasUnseenStories = true - #endif + self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 44.0, height: 44.0)) + self.avatarNode.frame = self.containerNode.bounds.insetBy(dx: 3.0, dy: 3.0) } deinit { @@ -265,7 +261,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { } override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - return CGSize(width: 37.0, height: 37.0) + return CGSize(width: 44.0, height: 44.0) } public func onLayout() { diff --git a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift index 19a4ebbc..ed37649c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift @@ -118,7 +118,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode { self.textNode = TextNode() self.titleNode = TextNode() - super.init(layerBacked: false, dynamicBounce: true, rotated: true) + super.init(layerBacked: false, rotated: true) self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift index 3d6f717b..80662ed3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -177,51 +177,8 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, Bool, LayoutMetrics)? public override init() { - /*self.button = HighlightableButton() - self.buttonBackgroundView = GlassBackgroundView() - self.buttonBackgroundView.isUserInteractionEnabled = false - self.buttonTitle = ImmediateTextNode() - self.buttonTitle.isUserInteractionEnabled = false - self.buttonTintTitle = ImmediateTextNode() - self.buttonBackgroundView.contentView.addSubview(self.buttonTitle.view) - self.buttonBackgroundView.maskContentView.addSubview(self.buttonTintTitle.view) - self.buttonBackgroundView.contentView.addSubview(self.button) - - self.helpButton = HighlightableButton() - self.helpButtonBackgroundView = GlassBackgroundView() - self.helpButtonBackgroundView.isUserInteractionEnabled = false - self.helpButtonIconView = GlassBackgroundView.ContentImageView() - self.helpButtonBackgroundView.contentView.addSubview(self.helpButtonIconView) - self.helpButtonBackgroundView.contentView.addSubview(self.helpButton) - self.helpButtonBackgroundView.isHidden = true - - self.giftButton = HighlightableButton() - self.giftButtonBackgroundView = GlassBackgroundView() - self.giftButtonBackgroundView.isUserInteractionEnabled = false - self.giftButtonIconView = GlassBackgroundView.ContentImageView() - self.giftButtonBackgroundView.contentView.addSubview(self.giftButtonIconView) - self.giftButtonBackgroundView.contentView.addSubview(self.giftButton) - self.giftButtonBackgroundView.isHidden = true - - self.suggestedPostButton = HighlightableButton() - self.suggestedPostButtonBackgroundView = GlassBackgroundView() - self.suggestedPostButtonBackgroundView.isUserInteractionEnabled = false - self.suggestedPostButtonIconView = GlassBackgroundView.ContentImageView() - self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButtonIconView) - self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButton) - self.suggestedPostButtonBackgroundView.isHidden = true*/ - super.init() - /*self.view.addSubview(self.buttonBackgroundView) - self.view.addSubview(self.helpButtonBackgroundView) - self.view.addSubview(self.giftButtonBackgroundView) - self.view.addSubview(self.suggestedPostButtonBackgroundView) - self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) - self.helpButton.addTarget(self, action: #selector(self.helpPressed), for: .touchUpInside) - self.giftButton.addTarget(self, action: #selector(self.giftPressed), for: .touchUpInside) - self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), for: .touchUpInside)*/ - self.view.addSubview(self.panelContainer) } @@ -495,7 +452,8 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { self?.buttonPressed() } )], - background: centerAction.isAccent ? .activeTint : .panel + background: centerAction.isAccent ? .activeTint : .panel, + keepWide: true ) } diff --git a/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift b/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift index aa000a8a..f2009782 100644 --- a/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift +++ b/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift @@ -56,53 +56,53 @@ public enum ChatHistoryEntry: Identifiable, Comparable { case UnreadEntry(MessageIndex, ChatPresentationData) case ReplyCountEntry(MessageIndex, Bool, Int, ChatPresentationData) case ChatInfoEntry(ChatInfoData, ChatPresentationData) - case SearchEntry(PresentationTheme, PresentationStrings) public var stableId: UInt64 { switch self { - case let .MessageEntry(message, _, _, _, _, attributes): - let type: UInt64 - switch attributes.contentTypeHint { - case .generic: - type = 2 - case .largeEmoji: - type = 3 - case .animatedEmoji: - type = 4 - } - return UInt64(message.stableId) | ((type << 40)) - case let .MessageGroupEntry(groupInfo, _, _): - return UInt64(bitPattern: groupInfo) | ((UInt64(2) << 40)) - case .UnreadEntry: - return UInt64(4) << 40 - case .ReplyCountEntry: - return UInt64(5) << 40 - case .ChatInfoEntry: - return UInt64(6) << 40 - case .SearchEntry: + case let .MessageEntry(message, _, _, _, _, attributes): + let type: UInt64 + switch attributes.contentTypeHint { + case .generic: + type = 2 + case .largeEmoji: + type = 3 + case .animatedEmoji: + type = 4 + } + return UInt64(message.stableId) | ((type << 40)) + case let .MessageGroupEntry(groupInfo, _, _): + return UInt64(bitPattern: groupInfo) | ((UInt64(2) << 40)) + case .UnreadEntry: + return UInt64(4) << 40 + case .ReplyCountEntry: + return UInt64(5) << 40 + case let .ChatInfoEntry(infoData, _): + switch infoData { + case .newThreadInfo: return UInt64(7) << 40 + default: + return UInt64(6) << 40 + } } } public var index: MessageIndex { switch self { - case let .MessageEntry(message, _, _, _, _, _): - return message.index - case let .MessageGroupEntry(_, messages, _): - return messages[messages.count - 1].0.index - case let .UnreadEntry(index, _): - return index - case let .ReplyCountEntry(index, _, _, _): - return index - case let .ChatInfoEntry(infoData, _): - switch infoData { - case .newThreadInfo: - return MessageIndex.absoluteUpperBound() - default: - return MessageIndex.absoluteLowerBound() - } - case .SearchEntry: + case let .MessageEntry(message, _, _, _, _, _): + return message.index + case let .MessageGroupEntry(_, messages, _): + return messages[messages.count - 1].0.index + case let .UnreadEntry(index, _): + return index + case let .ReplyCountEntry(index, _, _, _): + return index + case let .ChatInfoEntry(infoData, _): + switch infoData { + case .newThreadInfo: + return MessageIndex.absoluteUpperBound() + default: return MessageIndex.absoluteLowerBound() + } } } @@ -123,8 +123,6 @@ public enum ChatHistoryEntry: Identifiable, Comparable { default: return MessageIndex.absoluteLowerBound() } - case .SearchEntry: - return MessageIndex.absoluteLowerBound() } } @@ -297,12 +295,6 @@ public enum ChatHistoryEntry: Identifiable, Comparable { } else { return false } - case let .SearchEntry(lhsTheme, lhsStrings): - if case let .SearchEntry(rhsTheme, rhsStrings) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings { - return true - } else { - return false - } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/Sources/ChatHistorySearchContainerNode.swift b/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/Sources/ChatHistorySearchContainerNode.swift index e45a3a02..417e776d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/Sources/ChatHistorySearchContainerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/Sources/ChatHistorySearchContainerNode.swift @@ -158,7 +158,7 @@ public final class ChatHistorySearchContainerNode: SearchDisplayControllerConten self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.dateTimeFormat, self.presentationData.listsFontSize)) self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) + self.dimNode.backgroundColor = .clear self.listNode = ListView() self.listNode.accessibilityPageScrolledString = { row, count in return presentationData.strings.VoiceOver_ScrollStatus(row, count).string diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index ba8e6418..c84ec35a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -901,8 +901,6 @@ public final class ChatInlineSearchResultsListComponent: Component { }, openStarsTopup: { _ in }, - dismissNotice: { _ in - }, editPeer: { _ in }, openWebApp: { _ in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index 80adb8f2..cb3801d0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -440,6 +440,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { if node.iconNode == nil { let iconNode = ASImageNode() iconNode.contentMode = .center + iconNode.isUserInteractionEnabled = false node.iconNode = iconNode node.addSubnode(iconNode) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD index b5507029..30fadb76 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD @@ -32,6 +32,7 @@ swift_library( "//submodules/WallpaperBackgroundNode", "//submodules/LocalMediaResources", "//submodules/AppBundle", + "//submodules/TelegramStringFormatting", "//submodules/ChatPresentationInterfaceState", "//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/TelegramUI/Components/ChatControllerInteraction", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 001202a7..9a835b50 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -47,6 +47,7 @@ import ManagedDiceAnimationNode import MessageHaptics import ChatMessageTransitionNode import ChatMessageSuggestedPostInfoNode +import TelegramStringFormatting private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) @@ -99,6 +100,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyFeedback: HapticFeedback? + private let labelNode: TextNodeWithEntities + private var labelBackgroundNode: WallpaperBubbleBackgroundNode? + private let labelBackgroundMaskNode: ASImageNode + private var cachedMaskLabelBackgroundImage: (CGPoint, UIImage, [CGRect])? + private var selectionNode: ChatMessageSelectionNode? private var deliveryFailedNode: ChatMessageDeliveryFailedNode? private var shareButtonNode: ChatMessageShareButton? @@ -161,6 +167,12 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { self.textNode.textNode.displaysAsynchronously = false self.textNode.textNode.isUserInteractionEnabled = false + self.labelNode = TextNodeWithEntities() + self.labelNode.textNode.isUserInteractionEnabled = false + self.labelNode.textNode.displaysAsynchronously = false + + self.labelBackgroundMaskNode = ASImageNode() + super.init(rotated: rotated) self.containerNode.shouldBegin = { [weak self] location in @@ -469,9 +481,24 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } else if let telegramDice = self.telegramDice, let diceNode = self.animationNode as? ManagedDiceAnimationNode { if let value = telegramDice.value { + let wasRolling = diceNode.isRolling diceNode.setState(value == 0 ? .rolling : .value(value, true)) + + if wasRolling && !diceNode.isRolling { + Queue.mainQueue().after(3.0, { + self.labelNode.textNode.alpha = 1.0 + self.labelBackgroundNode?.alpha = 1.0 + self.labelNode.textNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25) + self.labelBackgroundNode?.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25) + self.labelNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + self.labelBackgroundNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + }) + } } else { diceNode.setState(.rolling) + + self.labelNode.textNode.alpha = 0.0 + self.labelBackgroundNode?.alpha = 0.0 } } else if self.telegramFile == nil && self.telegramDice == nil { let (emoji, fitz) = item.message.text.basicEmoji @@ -816,6 +843,9 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode) let reactionButtonsLayout = ChatMessageReactionButtonsNode.asyncLayout(self.reactionButtonsNode) + let makeLabelLayout = TextNodeWithEntities.asyncLayout(self.labelNode) + let cachedMaskLabelBackgroundImage = self.cachedMaskLabelBackgroundImage + let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode) let viaBotLayout = TextNode.asyncLayout(self.viaBotNode) @@ -850,6 +880,44 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let avatarInset: CGFloat var hasAvatar = false + let labelAttributedText = universalServiceMessageString(presentationData: (item.presentationData.theme.theme, item.presentationData.theme.wallpaper), strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId, forChatList: false, forForumOverview: false, forAdditionalServiceMessage: true) + + let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + var labelRects = labelLayout.linesRects() + if labelRects.count > 1 { + let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width }) + for i in 0 ..< sortedIndices.count { + let index = sortedIndices[i] + for j in -1 ... 1 { + if j != 0 && index + j >= 0 && index + j < sortedIndices.count { + if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 { + labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width) + labelRects[index].size.width = labelRects[index + j].size.width + } + } + } + } + } + for i in 0 ..< labelRects.count { + labelRects[i] = labelRects[i].insetBy(dx: -7.0, dy: floor((labelRects[i].height - 22.0) / 2.0)) + labelRects[i].size.height = 22.0 + labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0) + } + + let backgroundMaskImage: (CGPoint, UIImage)? + var backgroundMaskUpdated = false + if labelLayout.size.height > 0.0 { + if let (currentOffset, currentImage, currentRects) = cachedMaskLabelBackgroundImage, currentRects == labelRects { + backgroundMaskImage = (currentOffset, currentImage) + } else { + backgroundMaskImage = LinkHighlightingNode.generateImage(color: .black, inset: 0.0, innerRadius: 11.0, outerRadius: 11.0, rects: labelRects, useModernPathCalculation: false) + backgroundMaskUpdated = true + } + } else { + backgroundMaskImage = nil + } + switch item.chatLocation { case let .peer(peerId): if peerId != item.context.account.peerId { @@ -1061,6 +1129,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var viewCount: Int? = nil var dateReplies = 0 var starsCount: Int64? + var tonAmount: Int64? var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message) if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { dateReactionsAndPeers = ([], []) @@ -1079,6 +1148,10 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } + if let stakeTonAmount = telegramDice?.tonAmount { + tonAmount = stakeTonAmount + } + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, associatedData: item.associatedData) var isReplyThread = false @@ -1107,6 +1180,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { messageEffect: messageEffect, replyCount: dateReplies, starsCount: starsCount, + tonAmount: tonAmount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, canViewReactionList: canViewMessageReactionList(message: item.message), @@ -1211,6 +1285,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { quote: replyQuote, todoItemId: replyTodoItemId, story: replyStory, + isSummarized: false, parentMessage: item.message, constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude), animationCache: item.controllerInteraction.presentationContext.animationCache, @@ -1501,6 +1576,10 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else { updatedImageFrame = imageFrame.offsetBy(dx: 0.0, dy: floor((contentHeight - imageSize.height) / 2.0)) contextContentFrame = updatedImageFrame + + if let telegramDice, let _ = telegramDice.tonAmount { + updatedImageFrame = updatedImageFrame.offsetBy(dx: 0.0, dy: -30.0) + } } var updatedContentFrame = updatedImageFrame if isEmoji && emojiString == nil { @@ -1508,6 +1587,51 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { contextContentFrame = updatedContentFrame } + let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - labelLayout.size.width) / 2.0), y: updatedImageFrame.maxY + 6.0), size: labelLayout.size) + strongSelf.labelNode.textNode.frame = labelFrame + if strongSelf.labelNode.textNode.supernode == nil, labelLayout.size.height > 0.0 { + strongSelf.addSubnode(strongSelf.labelNode.textNode) + } + + let _ = labelApply(TextNodeWithEntities.Arguments( + context: item.context, + cache: item.controllerInteraction.presentationContext.animationCache, + renderer: item.controllerInteraction.presentationContext.animationRenderer, + placeholderColor: item.presentationData.theme.theme.chat.message.freeform.withWallpaper.reactionInactiveBackground, + attemptSynchronous: synchronousLoads + )) + + let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0) + if let (offset, image) = backgroundMaskImage { + if strongSelf.labelBackgroundNode == nil { + if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundNode.alpha = strongSelf.labelNode.textNode.alpha + strongSelf.labelBackgroundNode = backgroundNode + strongSelf.insertSubnode(backgroundNode, at: 0) + } + } + + if backgroundMaskUpdated, let backgroundNode = strongSelf.labelBackgroundNode { + if labelRects.count == 1 { + backgroundNode.clipsToBounds = true + backgroundNode.cornerRadius = labelRects[0].height / 2.0 + backgroundNode.view.mask = nil + } else { + backgroundNode.clipsToBounds = false + backgroundNode.cornerRadius = 0.0 + backgroundNode.view.mask = strongSelf.labelBackgroundMaskNode.view + } + } + + if let backgroundNode = strongSelf.labelBackgroundNode { + backgroundNode.layer.frame = CGRect(origin: CGPoint(x: baseBackgroundFrame.minX + offset.x, y: baseBackgroundFrame.minY + offset.y), size: image.size) + } + strongSelf.labelBackgroundMaskNode.image = image + strongSelf.labelBackgroundMaskNode.frame = CGRect(origin: CGPoint(), size: image.size) + + strongSelf.cachedMaskLabelBackgroundImage = (offset, image, labelRects) + } + if let (_, textApply) = textLayoutAndApply { let placeholderColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper) let _ = textApply(TextNodeWithEntities.Arguments(context: item.context, cache: item.controllerInteraction.presentationContext.animationCache, renderer: item.controllerInteraction.presentationContext.animationRenderer, placeholderColor: placeholderColor, attemptSynchronous: synchronousLoads)) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD index cfc11990..d764dbd7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD @@ -94,6 +94,7 @@ swift_library( "//submodules/TelegramStringFormatting", "//submodules/AvatarNode", "//submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode", + "//submodules/TelegramUI/Components/PremiumAlertController", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 2d11aad1..1d378b4f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -83,6 +83,7 @@ import TelegramAnimatedStickerNode import LottieMetal import AvatarNode import ChatMessageSuggestedPostInfoNode +import PremiumAlertController private struct BubbleItemAttributes { var index: Int? @@ -689,6 +690,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var unlockButtonNode: ChatMessageUnlockMediaNode? private var mediaInfoNode: ChatMessageStarsMediaInfoNode? + private var summarizeButtonNode: ChatMessageShareButton? private var shareButtonNode: ChatMessageShareButton? private let messageAccessibilityArea: AccessibilityAreaNode @@ -1222,6 +1224,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } + if let summarizeButtonNode = strongSelf.summarizeButtonNode, summarizeButtonNode.frame.contains(point) { + return .fail + } + if let shareButtonNode = strongSelf.shareButtonNode, shareButtonNode.frame.contains(point) { return .fail } @@ -1724,6 +1730,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp()) var needsShareButton = false + var needsSummarizeButton = false if incoming, case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind { needsShareButton = true @@ -1751,6 +1758,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI needsShareButton = true } + if let _ = item.message.attributes.first(where: { $0 is SummarizationMessageAttribute }) { + needsSummarizeButton = true + } + if let peer = item.message.peers[item.message.id.peerId] { if let channel = peer as? TelegramChannel { if case .broadcast = channel.info { @@ -1789,6 +1800,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI loop: for media in item.message.media { if media is TelegramMediaAction { needsShareButton = false + needsSummarizeButton = false break loop } else if let media = media as? TelegramMediaFile, media.isInstantVideo { mayHaveSeparateCommentsButton = true @@ -1801,12 +1813,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if mayHaveSeparateCommentsButton && hasCommentButton(item: item) { } else { needsShareButton = false + needsSummarizeButton = false } } } if isPreview { needsShareButton = false + needsSummarizeButton = false } let isAd = item.content.firstMessage.adAttribute != nil if isAd { @@ -1815,13 +1829,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil { needsShareButton = false + needsSummarizeButton = false } } if let subject = item.associatedData.subject, case .messageOptions = subject { needsShareButton = false } - + var tmpWidth: CGFloat if allowFullWidth { tmpWidth = baseWidth @@ -2312,6 +2327,17 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } + let translateToLanguage = item.associatedData.translateToLanguage + var isSummarized = false + if item.controllerInteraction.summarizedMessageIds.contains(item.message.id) { + for attribute in item.message.attributes { + if let attribute = attribute as? SummarizationMessageAttribute, attribute.summaryForLang(translateToLanguage) != nil { + initialDisplayHeader = true + isSummarized = true + } + } + } + var displayHeader = false if initialDisplayHeader { if authorNameString != nil { @@ -2339,6 +2365,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI displayHeader = true } } + if isSummarized { + displayHeader = true + } } let firstNodeTopPosition: ChatMessageBubbleRelativePosition @@ -2717,7 +2746,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI hasReply = false } - if !isInstantVideo, hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) { + if isSummarized { + hasReply = true + } + + if !isInstantVideo, hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil || isSummarized) { if headerSize.height.isZero { headerSize.height += 11.0 } else { @@ -2733,6 +2766,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI quote: replyQuote, todoItemId: replyTodoItemId, story: replyStory, + isSummarized: isSummarized, parentMessage: item.message, constrainedSize: CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right - 6.0, height: CGFloat.greatestFiniteMagnitude), animationCache: item.controllerInteraction.presentationContext.animationCache, @@ -3491,6 +3525,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI unlockButtonSizeAndApply: unlockButtonSizeApply, mediaInfoOrigin: mediaInfoOrigin?.offsetBy(dx: 0.0, dy: layoutInsets.top), mediaInfoSizeAndApply: mediaInfoSizeApply, + needsSummarizeButton: needsSummarizeButton, needsShareButton: needsShareButton, shareButtonOffset: shareButtonOffset, avatarOffset: avatarOffset, @@ -3556,6 +3591,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI unlockButtonSizeAndApply: (CGSize, (Bool) -> ChatMessageUnlockMediaNode?), mediaInfoOrigin: CGPoint?, mediaInfoSizeAndApply: (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode?), + needsSummarizeButton: Bool, needsShareButton: Bool, shareButtonOffset: CGPoint?, avatarOffset: CGFloat?, @@ -4787,6 +4823,20 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI }) } + if needsSummarizeButton { + if strongSelf.summarizeButtonNode == nil { + let summarizeButtonNode = ChatMessageShareButton() + strongSelf.summarizeButtonNode = summarizeButtonNode + strongSelf.insertSubnode(summarizeButtonNode, belowSubnode: strongSelf.messageAccessibilityArea) + summarizeButtonNode.pressed = { [weak strongSelf] in + strongSelf?.toggleSummarization() + } + } + } else if let summarizeButtonNode = strongSelf.summarizeButtonNode { + strongSelf.summarizeButtonNode = nil + summarizeButtonNode.removeFromSupernode() + } + if needsShareButton { if strongSelf.shareButtonNode == nil { let shareButtonNode = ChatMessageShareButton() @@ -4960,6 +5010,30 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } strongSelf.messageAccessibilityArea.frame = backgroundFrame } + if let summarizeButtonNode = strongSelf.summarizeButtonNode { + let buttonSize = summarizeButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments, isSummarize: true) + + var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.minY + 1.0), size: buttonSize) + + if let shareButtonOffset = shareButtonOffset { + if incoming { + buttonFrame.origin.x = shareButtonOffset.x + } + buttonFrame.origin.y = buttonFrame.origin.y + shareButtonOffset.y - (buttonSize.height - 30.0) + } else if !disablesComments { + buttonFrame.origin.y = buttonFrame.origin.y - (buttonSize.height - 30.0) + } + + if isSidePanelOpen { + buttonFrame.origin.x -= buttonFrame.width * 0.5 + buttonFrame.origin.y += buttonFrame.height * 0.5 + } + + animation.animator.updatePosition(layer: summarizeButtonNode.layer, position: buttonFrame.center, completion: nil) + animation.animator.updateBounds(layer: summarizeButtonNode.layer, bounds: CGRect(origin: CGPoint(), size: buttonFrame.size), completion: nil) + animation.animator.updateAlpha(layer: summarizeButtonNode.layer, alpha: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.0 : 1.0, completion: nil) + animation.animator.updateScale(layer: summarizeButtonNode.layer, scale: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.001 : 1.0, completion: nil) + } if let shareButtonNode = strongSelf.shareButtonNode { let buttonSize = shareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments) @@ -4994,6 +5068,30 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.backgroundFrameTransition = nil }*/ strongSelf.messageAccessibilityArea.frame = backgroundFrame + if let summarizeButtonNode = strongSelf.summarizeButtonNode { + let buttonSize = summarizeButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments, isSummarize: true) + + var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.minY + 1.0), size: buttonSize) + + if let shareButtonOffset = shareButtonOffset { + if incoming { + buttonFrame.origin.x = shareButtonOffset.x + } + buttonFrame.origin.y = buttonFrame.origin.y + shareButtonOffset.y - (buttonSize.height - 30.0) + } else if !disablesComments { + buttonFrame.origin.y = buttonFrame.origin.y - (buttonSize.height - 30.0) + } + + if isSidePanelOpen { + buttonFrame.origin.x -= buttonFrame.width * 0.5 + buttonFrame.origin.y += buttonFrame.height * 0.5 + } + + animation.animator.updatePosition(layer: summarizeButtonNode.layer, position: buttonFrame.center, completion: nil) + animation.animator.updateBounds(layer: summarizeButtonNode.layer, bounds: CGRect(origin: CGPoint(), size: buttonFrame.size), completion: nil) + animation.animator.updateAlpha(layer: summarizeButtonNode.layer, alpha: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.0 : 1.0, completion: nil) + animation.animator.updateScale(layer: summarizeButtonNode.layer, scale: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.001 : 1.0, completion: nil) + } if let shareButtonNode = strongSelf.shareButtonNode { let buttonSize = shareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments) @@ -5275,6 +5373,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } else if let replyInfoNode = self.replyInfoNode, self.item?.controllerInteraction.tapMessage == nil, replyInfoNode.frame.contains(location) { if let item = self.item { + if item.controllerInteraction.summarizedMessageIds.contains(item.message.id) { + return .action(InternalBubbleTapAction.Action({ [weak self] in + guard let self else { + return + } + self.toggleSummarization() + })) + } + for attribute in item.message.attributes { if let attribute = attribute as? ReplyMessageAttribute { if let threadId = item.message.threadId, Int32(clamping: threadId) == attribute.messageId.id, let quotedReply = item.message.attributes.first(where: { $0 is QuotedReplyMessageAttribute }) as? QuotedReplyMessageAttribute { @@ -5824,6 +5931,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return boostButtonNode.view } + if let summarizeButtonNode = self.summarizeButtonNode, summarizeButtonNode.frame.contains(point) { + return summarizeButtonNode.view.hitTest(self.view.convert(point, to: summarizeButtonNode.view), with: event) + } + if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) { return shareButtonNode.view.hitTest(self.view.convert(point, to: shareButtonNode.view), with: event) } @@ -6516,6 +6627,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI container.updateAbsoluteRect(containerFrame, within: containerSize) } + if let summarizeButtonNode = self.summarizeButtonNode { + var summarizeButtonNodeFrame = summarizeButtonNode.frame + summarizeButtonNodeFrame.origin.x += rect.minX + summarizeButtonNodeFrame.origin.y += rect.minY + + summarizeButtonNode.updateAbsoluteRect(summarizeButtonNodeFrame, within: containerSize) + } + if let shareButtonNode = self.shareButtonNode { var shareButtonNodeFrame = shareButtonNode.frame shareButtonNodeFrame.origin.x += rect.minX @@ -6854,6 +6973,45 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI self.updateVisibility(isScroll: false) } + private func toggleSummarization() { + guard let item = self.item else { + return + } + + if item.controllerInteraction.summarizedMessageIds.contains(item.message.id) { + item.controllerInteraction.summarizedMessageIds.remove(item.message.id) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + } else { + item.controllerInteraction.summarizedMessageIds.insert(item.message.id) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + + let translateToLanguage = item.associatedData.translateToLanguage + var requestSummary = true + for attribute in item.message.attributes { + if let attribute = attribute as? SummarizationMessageAttribute, attribute.summaryForLang(translateToLanguage) != nil { + requestSummary = false + break + } + } + if requestSummary { + let _ = (item.context.engine.messages.summarizeMessage(messageId: item.message.id, translateToLang: translateToLanguage) + |> deliverOnMainQueue).start(error: { error in + if case .limitExceededPremium = error, let parentController = item.controllerInteraction.navigationController()?.topViewController as? ViewController { + item.controllerInteraction.summarizedMessageIds.remove(item.message.id) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let controller = premiumAlertController( + context: item.context, + parentController: parentController, + title: item.presentationData.strings.Conversation_Summary_Limit_Title, + text: item.presentationData.strings.Conversation_Summary_Limit_Text + ) + parentController.present(controller, in: .window(.root)) + } + }) + } + } + } + private func updateVisibility(isScroll: Bool) { guard let item = self.item else { return diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift index 864920d6..eb78bd5a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift @@ -12,6 +12,7 @@ import ReactionButtonListComponent import ReactionImageComponent import AnimationCache import MultiAnimationRenderer +import TelegramStringFormatting private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) { if let _ = layer.animation(forKey: "clockFrameAnimation") { @@ -197,8 +198,10 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { var messageEffect: AvailableMessageEffects.MessageEffect? var replyCount: Int var starsCount: Int64? + var tonAmount: Int64? var isPinned: Bool var hasAutoremove: Bool + var isDeleted: Bool var canViewReactionList: Bool var animationCache: AnimationCache var animationRenderer: MultiAnimationRenderer @@ -222,8 +225,10 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { messageEffect: AvailableMessageEffects.MessageEffect?, replyCount: Int, starsCount: Int64?, + tonAmount: Int64? = nil, isPinned: Bool, hasAutoremove: Bool, + isDeleted: Bool = false, canViewReactionList: Bool, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer @@ -246,8 +251,10 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { self.messageEffect = messageEffect self.replyCount = replyCount self.starsCount = starsCount + self.tonAmount = tonAmount self.isPinned = isPinned self.hasAutoremove = hasAutoremove + self.isDeleted = isDeleted self.canViewReactionList = canViewReactionList self.animationCache = animationCache self.animationRenderer = animationRenderer @@ -270,6 +277,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { private var replyCountNode: TextNode? private var starsIcon: ASImageNode? private var starsCountNode: TextNode? + private var deletedIcon: ASImageNode? private var type: ChatMessageDateAndStatusType? private var theme: ChatPresentationThemeData? @@ -417,8 +425,10 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } else if arguments.isPinned { repliesImage = graphics.incomingDateAndStatusPinnedIcon } - if (arguments.starsCount ?? 0) != 0 { + if (arguments.starsCount ?? 0) != 0 { starsImage = graphics.incomingDateAndStatusStarsIcon + } else if (arguments.tonAmount ?? 0) != 0 { + starsImage = graphics.incomingDateAndStatusTonIcon } case let .BubbleOutgoing(status): dateColor = arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor @@ -438,6 +448,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } if (arguments.starsCount ?? 0) != 0 { starsImage = graphics.outgoingDateAndStatusStarsIcon + } else if (arguments.tonAmount ?? 0) != 0 { + starsImage = graphics.outgoingDateAndStatusTonIcon } case .ImageIncoming: dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor @@ -457,6 +469,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } if (arguments.starsCount ?? 0) != 0 { starsImage = graphics.mediaStarsIcon + } else if (arguments.tonAmount ?? 0) != 0 { + starsImage = graphics.mediaTonIcon } case let .ImageOutgoing(status): dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor @@ -477,6 +491,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } if (arguments.starsCount ?? 0) != 0 { starsImage = graphics.mediaStarsIcon + } else if (arguments.tonAmount ?? 0) != 0 { + starsImage = graphics.mediaTonIcon } case .FreeIncoming: let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper) @@ -498,6 +514,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } if (arguments.starsCount ?? 0) != 0 { starsImage = graphics.freeStarsIcon + } else if (arguments.tonAmount ?? 0) != 0 { + starsImage = graphics.freeTonIcon } case let .FreeOutgoing(status): let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper) @@ -519,6 +537,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } if (arguments.starsCount ?? 0) != 0 { starsImage = graphics.freeStarsIcon + } else if (arguments.tonAmount ?? 0) != 0 { + starsImage = graphics.freeTonIcon } } @@ -586,6 +606,36 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { currentStarsIcon = nil } + // ANTIDELETE: Deleted message icon + var currentDeletedIcon = self?.deletedIcon + var deletedIconSize = CGSize() + if arguments.isDeleted { + if currentDeletedIcon == nil { + let iconNode = ASImageNode() + iconNode.isLayerBacked = true + iconNode.displayWithoutProcessing = true + iconNode.displaysAsynchronously = false + currentDeletedIcon = iconNode + } + // Use trash icon from bundle or create simple one + let deletedImage = UIImage(bundleImageName: "Chat/Message/DeletedIcon") ?? generateTintedImage(image: UIImage(systemName: "trash"), color: dateColor) + deletedIconSize = deletedImage?.size ?? CGSize(width: 10, height: 10) + // Scale down the image + if let img = deletedImage { + let scaledSize = CGSize(width: 10, height: 10) + UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0.0) + img.draw(in: CGRect(origin: .zero, size: scaledSize)) + let scaledImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + currentDeletedIcon?.image = scaledImage + deletedIconSize = scaledSize + } else { + currentDeletedIcon?.image = deletedImage + } + } else { + currentDeletedIcon = nil + } + if let outgoingStatus = outgoingStatus { switch outgoingStatus { case .Sending: @@ -717,7 +767,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { let layoutAndApply = makeReplyCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0))) reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0 - if arguments.starsCount != nil { + if arguments.starsCount != nil || arguments.tonAmount != nil { reactionInset += 3.0 } replyCountLayoutAndApply = layoutAndApply @@ -735,6 +785,11 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { countString = "\(starsCount)" } + let layoutAndApply = makeStarsCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0))) + reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0 + starsCountLayoutAndApply = layoutAndApply + } else if let tonAmount = arguments.tonAmount, tonAmount > 0 { + let countString = formatTonAmountText(tonAmount, dateTimeFormat: arguments.presentationData.dateTimeFormat) let layoutAndApply = makeStarsCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0))) reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0 starsCountLayoutAndApply = layoutAndApply @@ -744,9 +799,15 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { reactionInset += 13.0 } + // ANTIDELETE: Add deleted icon space + var deletedIconWidth: CGFloat = 0.0 + if arguments.isDeleted { + deletedIconWidth = deletedIconSize.width + 3.0 + } + leftInset += reactionInset - let layoutSize = CGSize(width: leftInset + impressionWidth + date.size.width + statusWidth + backgroundInsets.left + backgroundInsets.right, height: date.size.height + backgroundInsets.top + backgroundInsets.bottom) + let layoutSize = CGSize(width: leftInset + deletedIconWidth + impressionWidth + date.size.width + statusWidth + backgroundInsets.left + backgroundInsets.right, height: date.size.height + backgroundInsets.top + backgroundInsets.bottom) let verticalReactionsInset: CGFloat let verticalInset: CGFloat @@ -1089,7 +1150,22 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { strongSelf.impressionIcon = nil } - animation.animator.updateFrame(layer: strongSelf.dateNode.layer, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left + impressionWidth, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: date.size), completion: nil) + // ANTIDELETE: Position deleted icon + if let currentDeletedIcon = currentDeletedIcon { + let deletedIconFrame = CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left + impressionWidth, y: backgroundInsets.top + 1.0 + offset + verticalInset + floor((date.size.height - deletedIconSize.height) / 2.0)), size: deletedIconSize) + if currentDeletedIcon.supernode == nil { + strongSelf.deletedIcon = currentDeletedIcon + strongSelf.addSubnode(currentDeletedIcon) + currentDeletedIcon.frame = deletedIconFrame + } else { + animation.animator.updateFrame(layer: currentDeletedIcon.layer, frame: deletedIconFrame, completion: nil) + } + } else if let deletedIcon = strongSelf.deletedIcon { + deletedIcon.removeFromSupernode() + strongSelf.deletedIcon = nil + } + + animation.animator.updateFrame(layer: strongSelf.dateNode.layer, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left + impressionWidth + deletedIconWidth, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: date.size), completion: nil) if let clockFrameNode = clockFrameNode { let clockPosition = CGPoint(x: leftOffset + backgroundInsets.left + clockPosition.x + reactionInset, y: backgroundInsets.top + clockPosition.y + verticalInset) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 730e74f1..b19d721b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -455,7 +455,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { switch action.action { case let .giftPremium(_, _, daysValue, _, _, giftText, giftEntities): months = max(3, Int32(round(Float(daysValue) / 30.0))) - if months == 12 { + if daysValue < 30 { + title = item.presentationData.strings.Notification_PremiumGift_DaysTitle(daysValue) + } else if months == 12 { title = item.presentationData.strings.Notification_PremiumGift_YearsTitle(1) } else { title = item.presentationData.strings.Notification_PremiumGift_MonthsTitle(months) @@ -513,7 +515,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { title = item.presentationData.strings.Notification_StarsGiveaway_Title let starsString = item.presentationData.strings.Notification_StarsGiveaway_Subtitle_Stars(Int32(clamping: count)).replacingOccurrences(of: " ", with: "\u{00A0}") text = item.presentationData.strings.Notification_StarsGiveaway_Subtitle(peerName, starsString).string - case let .giftCode(_, fromGiveaway, unclaimed, channelId, monthsValue, _, _, _, _, giftText, giftEntities): + case let .giftCode(_, fromGiveaway, unclaimed, channelId, daysValue, _, _, _, _, giftText, giftEntities): + let monthsValue = max(3, Int32(round(Float(daysValue) / 30.0))) if channelId == nil { months = monthsValue if months == 12 { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftOfferBubbleContentNode/Sources/ChatMessageGiftOfferBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftOfferBubbleContentNode/Sources/ChatMessageGiftOfferBubbleContentNode.swift index f50e5b49..ea4683c8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftOfferBubbleContentNode/Sources/ChatMessageGiftOfferBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftOfferBubbleContentNode/Sources/ChatMessageGiftOfferBubbleContentNode.swift @@ -131,7 +131,7 @@ public class ChatMessageGiftOfferBubbleContentNode: ChatMessageBubbleContentNode case .stars: priceString = item.presentationData.strings.Notification_StarGiftOffer_Offer_Stars(Int32(clamping: amount.amount.value)) case .ton: - priceString = "\(amount.amount) TON" + priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: item.presentationData.dateTimeFormat) + " TON" } let peerName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift index f5bdcefb..c1ae3540 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift @@ -530,6 +530,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco quote: replyQuote, todoItemId: replyTodoItemId, story: replyStory, + isSummarized: false, parentMessage: item.message, constrainedSize: CGSize(width: max(0, availableWidth), height: CGFloat.greatestFiniteMagnitude), animationCache: item.controllerInteraction.presentationContext.animationCache, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index 550414b4..7e79f7dc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -349,7 +349,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { } private func transcribe() { - guard let arguments = self.arguments, let context = self.context, let message = self.message else { + guard let _ = self.arguments, let context = self.context, let message = self.message else { return } @@ -358,43 +358,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { } let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: arguments.context.currentAppConfiguration.with { $0 }) - - let transcriptionText = self.forcedAudioTranscriptionText ?? transcribedText(message: message) - if transcriptionText == nil && !arguments.associatedData.alwaysDisplayTranscribeButton.providedByGroupBoost { - if premiumConfiguration.audioTransciptionTrialCount > 0 { - if !arguments.associatedData.isPremium { - if self.presentAudioTranscriptionTooltip(finished: false) { - return - } - } - } else { - guard arguments.associatedData.isPremium else { - if self.hapticFeedback == nil { - self.hapticFeedback = HapticFeedback() - } - self.hapticFeedback?.impact(.medium) - - let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_voiceToText", scale: 0.065, colors: [:], title: nil, text: presentationData.strings.Message_AudioTranscription_SubscribeToPremium, customUndoText: presentationData.strings.Message_AudioTranscription_SubscribeToPremiumAction, timeout: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in - if case .undo = action { - var replaceImpl: ((ViewController) -> Void)? - let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, forceDark: false, action: { - let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil) - replaceImpl?(controller) - }, dismissed: nil) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - arguments.controllerInteraction.navigationController()?.pushViewController(controller, animated: true) - - let _ = ApplicationSpecificNotice.incrementAudioTranscriptionSuggestion(accountManager: context.sharedContext.accountManager).startStandalone() - } - return false }) - arguments.controllerInteraction.presentControllerInCurrent(tipController, nil) - return - } - } - } + // GHOSTGRAM: Premium check removed - local transcription is free! var shouldBeginTranscription = false var shouldExpandNow = false @@ -420,7 +384,8 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { self.audioTranscriptionState = .inProgress self.requestUpdateLayout(true) - if context.sharedContext.immediateExperimentalUISettings.localTranscription { + // GHOSTGRAM: Always use local transcription (free, private, on-device!) + if true { let appLocale = presentationData.strings.baseLanguageCode let signal: Signal = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: message.id)) @@ -640,8 +605,6 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { var audioWaveform: AudioWaveform? var isVoice = false var audioDuration: Int32 = 0 - var isConsumed: Bool? - var consumableContentIcon: UIImage? for attribute in arguments.message.attributes { if let attribute = attribute as? ConsumableContentMessageAttribute { @@ -652,7 +615,6 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { consumableContentIcon = PresentationResourcesChat.chatBubbleConsumableContentOutgoingIcon(arguments.presentationData.theme.theme) } } - isConsumed = attribute.consumed break } } @@ -771,24 +733,8 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { if Namespaces.Message.allNonRegular.contains(arguments.message.id.namespace) { displayTranscribe = false } else if arguments.message.id.peerId.namespace != Namespaces.Peer.SecretChat && !isViewOnceMessage && !arguments.presentationData.isPreview { - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: arguments.context.currentAppConfiguration.with { $0 }) - if arguments.associatedData.isPremium { - displayTranscribe = true - } else if premiumConfiguration.audioTransciptionTrialCount > 0 { - if arguments.incoming { - if audioDuration < premiumConfiguration.audioTransciptionTrialMaxDuration { - displayTranscribe = true - } - } - } else if arguments.associatedData.alwaysDisplayTranscribeButton.canBeDisplayed { - if audioDuration >= 60 { - displayTranscribe = true - } else if arguments.incoming && isConsumed == false && arguments.associatedData.alwaysDisplayTranscribeButton.displayForNotConsumed { - displayTranscribe = true - } - } else if arguments.associatedData.alwaysDisplayTranscribeButton.providedByGroupBoost { - displayTranscribe = true - } + // GHOSTGRAM: Always show transcribe button for voice messages + displayTranscribe = true } let transcribedText = forcedAudioTranscriptionText ?? transcribedText(message: arguments.message) @@ -1564,8 +1510,15 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { if isTranslating, !rects.isEmpty { if self.shimmeringNodes.isEmpty { + let color: UIColor + let isIncoming = arguments.message.effectivelyIncoming(arguments.context.account.peerId) + if arguments.presentationData.theme.theme.overallDarkAppearance { + color = isIncoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor.withAlphaComponent(0.1) : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor.withAlphaComponent(0.1) + } else { + color = isIncoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor.withAlphaComponent(0.1) : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor.withAlphaComponent(0.1) + } for rects in rects { - let shimmeringNode = ShimmeringLinkNode(color: arguments.message.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.secondaryTextColor.withAlphaComponent(0.1) : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor.withAlphaComponent(0.1)) + let shimmeringNode = ShimmeringLinkNode(color: color) shimmeringNode.updateRects(rects) shimmeringNode.frame = self.bounds shimmeringNode.updateLayout(self.bounds.size) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index b4605b04..b13d2699 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -404,6 +404,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { quote: replyQuote, todoItemId: replyTodoItemId, story: replyStory, + isSummarized: false, parentMessage: item.message, constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), animationCache: item.controllerInteraction.presentationContext.animationCache, @@ -1830,45 +1831,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { return } - let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) - - let transcriptionText = transcribedText(message: item.message) - if transcriptionText == nil && !item.associatedData.alwaysDisplayTranscribeButton.providedByGroupBoost { - if premiumConfiguration.audioTransciptionTrialCount > 0 { - if !item.associatedData.isPremium { - if self.presentAudioTranscriptionTooltip(finished: false) { - return - } - } - } else { - guard item.associatedData.isPremium else { - if self.hapticFeedback == nil { - self.hapticFeedback = HapticFeedback() - } - self.hapticFeedback?.impact(.medium) - - let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_voiceToText", scale: 0.065, colors: [:], title: nil, text: presentationData.strings.Message_AudioTranscription_SubscribeToPremium, customUndoText: presentationData.strings.Message_AudioTranscription_SubscribeToPremiumAction, timeout: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in - if case .undo = action { - let context = item.context - var replaceImpl: ((ViewController) -> Void)? - let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, forceDark: false, action: { - let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil) - replaceImpl?(controller) - }, dismissed: nil) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - item.controllerInteraction.navigationController()?.pushViewController(controller, animated: true) - - let _ = ApplicationSpecificNotice.incrementAudioTranscriptionSuggestion(accountManager: item.context.sharedContext.accountManager).startStandalone() - } - return false }) - item.controllerInteraction.presentControllerInCurrent(tipController, nil) - return - } - } - } + // GHOSTGRAM: Premium check removed - local transcription is free! var shouldBeginTranscription = false var shouldExpandNow = false diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index 99026ce1..e706cb2c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -645,7 +645,7 @@ public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMe let isRotated = controllerInteraction?.chatIsRotated ?? true - super.init(layerBacked: false, dynamicBounce: true, isRotated: isRotated, seeThrough: false) + super.init(layerBacked: false, isRotated: isRotated, seeThrough: false) if isRotated { self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) @@ -1012,7 +1012,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat let isRotated = controllerInteraction?.chatIsRotated ?? true - super.init(layerBacked: false, dynamicBounce: true, isRotated: isRotated, seeThrough: false) + super.init(layerBacked: false, isRotated: isRotated, seeThrough: false) if isRotated { self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift index 70311fed..4c393049 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift @@ -81,7 +81,7 @@ public class ChatReplyCountItemNode: ListViewItemNode { self.backgroundColorNode = ASDisplayNode() - super.init(layerBacked: false, dynamicBounce: true, rotated: true) + super.init(layerBacked: false, rotated: true) self.addSubnode(self.labelNode) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift index 65c004db..63c5f26b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift @@ -94,7 +94,7 @@ public class ChatUnreadItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() self.activateArea.accessibilityTraits = .staticText - super.init(layerBacked: false, dynamicBounce: true, rotated: true) + super.init(layerBacked: false, rotated: true) self.addSubnode(self.backgroundNode) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index 2a3b973f..7d0d161a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -112,7 +112,7 @@ public final class ChatMessageAccessibilityData { if let chatPeer = message.peers[item.message.id.peerId] { let authorName = message.author.flatMap(EnginePeer.init)?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) - let (_, _, messageText, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: [EngineMessage(message)], chatPeer: EngineRenderedPeer(peer: EnginePeer(chatPeer)), accountPeerId: item.context.account.peerId) + let (_, _, messageText, _, _, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, contentSettings: item.context.currentContentSettings.with { $0 }, messages: [EngineMessage(message)], chatPeer: EngineRenderedPeer(peer: EnginePeer(chatPeer)), accountPeerId: item.context.account.peerId) var text = messageText @@ -664,7 +664,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { public var effectAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = [] public required init(rotated: Bool) { - super.init(layerBacked: false, dynamicBounce: true, rotated: rotated) + super.init(layerBacked: false, rotated: rotated) if rotated { self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift index 087a18dd..0c2b0da4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift @@ -105,7 +105,7 @@ final class ChatCallNotificationItemNode: NotificationItemNode { override public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { self.validLayout = width - let panelHeight: CGFloat = 66.0 + let panelHeight: CGFloat = 64.0 guard let item = self.item else { return panelHeight @@ -113,19 +113,19 @@ final class ChatCallNotificationItemNode: NotificationItemNode { let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } - let leftInset: CGFloat = 14.0 - let rightInset: CGFloat = 14.0 - let avatarSize: CGFloat = 38.0 + let leftInset: CGFloat = 12.0 + let rightInset: CGFloat = 12.0 + let avatarSize: CGFloat = 40.0 let avatarTextSpacing: CGFloat = 10.0 let buttonSpacing: CGFloat = 14.0 - let titleTextSpacing: CGFloat = 0.0 + let titleTextSpacing: CGFloat = 1.0 let maxTextWidth: CGFloat = width - leftInset - avatarTextSpacing - rightInset - avatarSize * 2.0 - buttonSpacing - avatarTextSpacing let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: item.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: item.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: presentationData.theme.list.itemPrimaryTextColor)) )), environment: {}, containerSize: CGSize(width: maxTextWidth, height: 100.0) @@ -134,7 +134,7 @@ final class ChatCallNotificationItemNode: NotificationItemNode { let textSize = self.text.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: item.isVideo ? presentationData.strings.Notification_VideoCallIncoming : presentationData.strings.Notification_CallIncoming, font: Font.regular(13.0), textColor: presentationData.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: item.isVideo ? presentationData.strings.Notification_VideoCallIncoming : presentationData.strings.Notification_CallIncoming, font: Font.regular(15.0), textColor: presentationData.theme.list.itemPrimaryTextColor)) )), environment: {}, containerSize: CGSize(width: maxTextWidth, height: 100.0) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift index bf30319a..466f3f0e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift @@ -381,11 +381,11 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { var applyImage: (() -> Void)? if let imageDimensions = imageDimensions { let boundingSize = CGSize(width: 55.0, height: 55.0) - var radius: CGFloat = 6.0 + var radius: CGFloat = 20.0 if isRound { radius = floor(boundingSize.width / 2.0) } - applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) + applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: radius, curve: isRound ? .circular : .continuous), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) } var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? @@ -424,16 +424,16 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { let compact = self.compact ?? false let panelHeight: CGFloat = compact ? 64.0 : 74.0 - let imageSize: CGSize = compact ? CGSize(width: 44.0, height: 44.0) : CGSize(width: 54.0, height: 54.0) - let imageSpacing: CGFloat = compact ? 19.0 : 23.0 + let imageSize: CGSize = compact ? CGSize(width: 40.0, height: 40.0) : CGSize(width: 54.0, height: 54.0) + let imageSpacing: CGFloat = compact ? 22.0 : 23.0 let leftInset: CGFloat = imageSize.width + imageSpacing - var rightInset: CGFloat = 8.0 + var rightInset: CGFloat = 10.0 if !self.imageNode.isHidden { - rightInset += imageSize.width + 8.0 + rightInset += imageSize.width + 10.0 } - transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: 10.0, y: (panelHeight - imageSize.height) / 2.0), size: imageSize)) + transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: 12.0, y: (panelHeight - imageSize.height) / 2.0), size: imageSize)) var titleInset: CGFloat = 0.0 if let image = self.titleIconNode.image { @@ -465,7 +465,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { let textSpacing: CGFloat = 1.0 - let titleFrame = CGRect(origin: CGPoint(x: leftInset + titleInset, y: 1.0 + floor((panelHeight - textLayout.size.height - titleLayout.size.height - textSpacing) / 2.0)), size: titleLayout.size) + let titleFrame = CGRect(origin: CGPoint(x: leftInset + titleInset, y: floor((panelHeight - textLayout.size.height - titleLayout.size.height - textSpacing) / 2.0)), size: titleLayout.size) transition.updateFrame(node: self.titleNode, frame: titleFrame) if let image = self.titleIconNode.image { @@ -475,7 +475,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + textSpacing), size: textLayout.size) transition.updateFrame(node: self.textNode.textNode, frame: textFrame) - transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: width - 10.0 - imageSize.width, y: (panelHeight - imageSize.height) / 2.0), size: imageSize)) + transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: width - 12.0 - imageSize.width, y: (panelHeight - imageSize.height) / 2.0), size: imageSize)) if !textLayout.spoilers.isEmpty, let item = self.item { let presentationData = item.context.sharedContext.currentPresentationData.with({ $0 }) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/BUILD index ffec32b8..d3f30ddc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/BUILD @@ -25,6 +25,8 @@ swift_library( "//submodules/CheckNode", "//submodules/TelegramUIPreferences", "//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent", "//submodules/Markdown", ], visibility = [ diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift index c7ccefad..5adf4309 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift @@ -16,309 +16,10 @@ import CheckNode import Markdown import TextFormat import StarsBalanceOverlayComponent +import AlertComponent +import AlertCheckComponent -private let textFont = Font.regular(13.0) -private let boldTextFont = Font.semibold(13.0) - -private func formattedText(_ text: String, fontSize: CGFloat, color: UIColor, linkColor: UIColor, textAlignment: NSTextAlignment = .natural) -> NSAttributedString { - return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(fontSize), textColor: color), bold: MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: color), link: MarkdownAttributeSet(font: Font.regular(fontSize), textColor: linkColor), linkAttribute: { _ in return (TelegramTextAttributes.URL, "") }), textAlignment: textAlignment) -} - -private final class ChatMessagePaymentAlertContentNode: AlertContentNode, ASGestureRecognizerDelegate { - private let strings: PresentationStrings - private let title: String - private let text: String - private let optionText: String? - private let alignment: TextAlertContentActionLayout - - private let titleNode: ImmediateTextNode - private let textNode: ImmediateTextNode - - private let checkNode: InteractiveCheckNode - private let checkLabelNode: ImmediateTextNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - var dontAskAgain: Bool = false { - didSet { - self.checkNode.setSelected(self.dontAskAgain, animated: true) - - } - } - - var openTerms: () -> Void = {} - - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, title: String, text: String, optionText: String?, actions: [TextAlertAction], alignment: TextAlertContentActionLayout) { - self.strings = strings - self.title = title - self.text = text - self.optionText = optionText - self.alignment = alignment - - self.titleNode = ImmediateTextNode() - self.titleNode.displaysAsynchronously = false - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.textAlignment = .center - - self.textNode = ImmediateTextNode() - self.textNode.maximumNumberOfLines = 0 - self.textNode.displaysAsynchronously = false - self.textNode.lineSpacing = 0.1 - self.textNode.textAlignment = .center - - self.checkNode = InteractiveCheckNode(theme: CheckNodeTheme(backgroundColor: theme.accentColor, strokeColor: theme.contrastColor, borderColor: theme.controlBorderColor, overlayBorder: false, hasInset: false, hasShadow: false)) - self.checkLabelNode = ImmediateTextNode() - self.checkLabelNode.maximumNumberOfLines = 4 - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - - if let _ = optionText { - self.addSubnode(self.checkNode) - self.addSubnode(self.checkLabelNode) - } - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.checkNode.valueChanged = { [weak self] value in - if let strongSelf = self { - strongSelf.dontAskAgain = !strongSelf.dontAskAgain - } - } - - self.checkLabelNode.highlightAttributeAction = { attributes in - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { - return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) - } else { - return nil - } - } - self.checkLabelNode.tapAttributeAction = { [weak self] attributes, _ in - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { - self?.openTerms() - } - } - - self.updateTheme(theme) - } - - override func didLoad() { - super.didLoad() - - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.acceptTap(_:))) - tapGesture.delegate = self.wrappedGestureRecognizerDelegate - self.view.addGestureRecognizer(tapGesture) - } - - override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - let location = gestureRecognizer.location(in: self.checkLabelNode.view) - if self.checkLabelNode.bounds.contains(location) { - return true - } - return false - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if !self.bounds.contains(point) { - return nil - } - - if let (_, attributes) = self.checkLabelNode.attributesAtPoint(self.view.convert(point, to: self.checkLabelNode.view)) { - if attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] == nil { - return self.view - } - } - - return super.hitTest(point, with: event) - } - - @objc private func acceptTap(_ gestureRecognizer: UITapGestureRecognizer) { - self.dontAskAgain = !self.dontAskAgain - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = formattedText(self.text, fontSize: 13.0, color: theme.primaryColor, linkColor: theme.accentColor, textAlignment: .center) - - self.checkLabelNode.attributedText = parseMarkdownIntoAttributedString( - self.optionText ?? "", - attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: textFont, textColor: theme.primaryColor), - bold: MarkdownAttributeSet(font: boldTextFont, textColor: theme.primaryColor), - link: MarkdownAttributeSet(font: textFont, textColor: theme.primaryColor), - linkAttribute: { _ in - return nil - } - ) - ) - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 17.0) - - let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 4.0 - - var entriesHeight: CGFloat = 0.0 - - let textSize = self.textNode.updateLayout(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height - - if self.checkLabelNode.supernode != nil { - origin.y += 21.0 - entriesHeight += 21.0 - - let checkSize = CGSize(width: 22.0, height: 22.0) - let condensedSize = CGSize(width: size.width - 76.0, height: size.height) - - let spacing: CGFloat = 12.0 - let acceptTermsSize = self.checkLabelNode.updateLayout(condensedSize) - let acceptTermsTotalWidth = checkSize.width + spacing + acceptTermsSize.width - let acceptTermsOriginX = floorToScreenPixels((size.width - acceptTermsTotalWidth) / 2.0) - - transition.updateFrame(node: self.checkNode, frame: CGRect(origin: CGPoint(x: acceptTermsOriginX, y: origin.y - 3.0), size: checkSize)) - transition.updateFrame(node: self.checkLabelNode, frame: CGRect(origin: CGPoint(x: acceptTermsOriginX + checkSize.width + spacing, y: origin.y), size: acceptTermsSize)) - origin.y += acceptTermsSize.height - entriesHeight += acceptTermsSize.height - origin.y += 21.0 - } - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = self.alignment - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultSize = CGSize(width: contentWidth, height: titleSize.height + textSize.height + entriesHeight + actionsHeight + 3.0 + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} - -public class ChatMessagePaymentAlertController: AlertController { +public class ChatMessagePaymentAlertController: AlertScreen { private let context: AccountContext? private let presentationData: PresentationData private weak var parentNavigationController: NavigationController? @@ -327,29 +28,26 @@ public class ChatMessagePaymentAlertController: AlertController { private let animateBalanceOverlay: Bool private var didUpdateCurrency = false - public var currency: CurrencyAmount.Currency { - didSet { - self.didUpdateCurrency = true - if let layout = self.validLayout { - self.containerLayoutUpdated(layout, transition: .animated(duration: 0.25, curve: .easeInOut)) - } - } - } - + + private var initialCurrency: CurrencyAmount.Currency? + public var currency: CurrencyAmount.Currency? + private var currencyDisposable: Disposable? + private let balance = ComponentView() private var didAppear = false - - private var validLayout: ContainerViewLayout? - + public init( context: AccountContext?, presentationData: PresentationData, - contentNode: AlertContentNode, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + configuration: Configuration = AlertScreen.Configuration(), + contentSignal: Signal<[AnyComponentWithIdentity], NoError>, + actionsSignal: Signal<[AlertScreen.Action], NoError>, navigationController: NavigationController?, chatPeerId: EnginePeer.Id, showBalance: Bool = true, - currency: CurrencyAmount.Currency = .stars, + currencySignal: Signal = .single(.stars), animateBalanceOverlay: Bool = true ) { self.context = context @@ -357,31 +55,84 @@ public class ChatMessagePaymentAlertController: AlertController { self.parentNavigationController = navigationController self.chatPeerId = chatPeerId self.showBalance = showBalance - self.currency = currency self.animateBalanceOverlay = animateBalanceOverlay - super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) + var effectiveUpdatedPresentationData: (initial: PresentationData, signal: Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (initial: presentationData, signal: .single(presentationData)) + } + + super.init( + configuration: configuration, + contentSignal: contentSignal, + actionsSignal: actionsSignal, + updatedPresentationData: effectiveUpdatedPresentationData + ) - self.willDismiss = { [weak self] in + self.currencyDisposable = (currencySignal + |> distinctUntilChanged + |> deliverOnMainQueue).start(next: { [weak self] currency in guard let self else { return } - self.animateOut() - } + if self.currency == nil { + self.initialCurrency = currency + } + self.currency = currency + if let layout = self.validLayout { + self.containerLayoutUpdated(layout, transition: .animated(duration: 0.25, curve: .easeInOut)) + } + }) + } + + public convenience init( + context: AccountContext?, + presentationData: PresentationData, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + configuration: Configuration = AlertScreen.Configuration(), + content: [AnyComponentWithIdentity], + actions: [AlertScreen.Action], + navigationController: NavigationController?, + chatPeerId: EnginePeer.Id, + showBalance: Bool = true, + currency: CurrencyAmount.Currency = .stars, + animateBalanceOverlay: Bool = true + ) { + self.init( + context: context, + presentationData: presentationData, + updatedPresentationData: updatedPresentationData, + configuration: configuration, + contentSignal: .single(content), + actionsSignal: .single(actions), + navigationController: navigationController, + chatPeerId: chatPeerId, + showBalance: showBalance, + currencySignal: .single(currency), + animateBalanceOverlay: animateBalanceOverlay + ) } required public init(coder aDecoder: NSCoder) { preconditionFailure() } + override public func dismiss(completion: (() -> Void)? = nil) { + super.dismiss(completion: completion) + + self.animateOut() + } + private func animateOut() { if !self.animateBalanceOverlay { - if self.currency == .ton && self.didUpdateCurrency { + if case .ton = self.currency, let initialCurrency, initialCurrency != self.currency { self.currency = .stars + if let layout = self.validLayout { + self.containerLayoutUpdated(layout, transition: .animated(duration: 0.25, curve: .easeInOut)) + } } - Queue.mainQueue().after(0.39, { - - }) } else { if let view = self.balance.view { view.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false) @@ -389,18 +140,10 @@ public class ChatMessagePaymentAlertController: AlertController { } } } - - public override func dismissAnimated() { - super.dismissAnimated() - self.animateOut() - } - public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) - - self.validLayout = layout - + if !self.didAppear { self.didAppear = true if !layout.metrics.isTablet && layout.size.width > layout.size.height { @@ -410,7 +153,7 @@ public class ChatMessagePaymentAlertController: AlertController { } } - if let context = self.context, let _ = self.parentNavigationController, self.showBalance { + if let context = self.context, let _ = self.parentNavigationController, self.showBalance, let currency = self.currency { let insets = layout.insets(options: .statusBar) var balanceTransition = ComponentTransition(transition) if self.balance.view == nil { @@ -424,12 +167,12 @@ public class ChatMessagePaymentAlertController: AlertController { context: context, peerId: self.chatPeerId.namespace == Namespaces.Peer.CloudChannel ? self.chatPeerId : context.account.peerId, theme: self.presentationData.theme, - currency: self.currency, + currency: currency, action: { [weak self] in - guard let self, let starsContext = context.starsContext, let navigationController = self.parentNavigationController else { + guard let self, let starsContext = context.starsContext, let navigationController = self.parentNavigationController, let currency = self.currency else { return } - switch self.currency { + switch currency { case .stars: let _ = (context.engine.payments.starsTopUpOptions() |> take(1) @@ -452,7 +195,7 @@ public class ChatMessagePaymentAlertController: AlertController { } context.sharedContext.applicationBindings.openUrl(fragmentUrl) } - self.dismissAnimated() + self.dismiss(completion: nil) } ) ), @@ -486,57 +229,66 @@ public func chatMessagePaymentAlertController( hasCheck: Bool = true, navigationController: NavigationController?, completion: @escaping (Bool) -> Void -) -> AlertController { - let theme = defaultDarkColorPresentationTheme - let presentationData = updatedPresentationData?.initial ?? presentationData +) -> ViewController { let strings = presentationData.strings - var completionImpl: (() -> Void)? - var dismissImpl: (() -> Void)? - - let title = presentationData.strings.Chat_PaidMessage_Confirm_Title - let actionTitle = presentationData.strings.Chat_PaidMessage_Confirm_PayForMessage(count) - let messagesString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Messages(count) - - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: actionTitle, action: { - completionImpl?() - dismissImpl?() - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?() - })] - + let messagesString = strings.Chat_PaidMessage_Confirm_Text_Messages(count) let text: String if peers.count == 1, let peer = peers.first { - let amountString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value)) - let totalString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value * Int64(count))) + let amountString = strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value)) + let totalString = strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value * Int64(count))) if case let .channel(channel) = peer.chatOrMonoforumMainPeer, case .broadcast = channel.info { - text = presentationData.strings.Chat_PaidMessage_Confirm_SingleComment_Text(EnginePeer(channel).compactDisplayTitle, amountString, totalString, messagesString).string + text = strings.Chat_PaidMessage_Confirm_SingleComment_Text(EnginePeer(channel).compactDisplayTitle, amountString, totalString, messagesString).string } else { - text = presentationData.strings.Chat_PaidMessage_Confirm_Single_Text(peer.chatOrMonoforumMainPeer?.compactDisplayTitle ?? " ", amountString, totalString, messagesString).string + text = strings.Chat_PaidMessage_Confirm_Single_Text(peer.chatOrMonoforumMainPeer?.compactDisplayTitle ?? " ", amountString, totalString, messagesString).string } } else { let amount = totalAmount ?? amount - let usersString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Users(Int32(peers.count)) - let totalString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value * Int64(count))) - text = presentationData.strings.Chat_PaidMessage_Confirm_Multiple_Text(usersString, totalString, messagesString).string + let usersString = strings.Chat_PaidMessage_Confirm_Text_Users(Int32(peers.count)) + let totalString = strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value * Int64(count))) + text = strings.Chat_PaidMessage_Confirm_Multiple_Text(usersString, totalString, messagesString).string + } + + let checkState = AlertCheckComponent.ExternalState() + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Chat_PaidMessage_Confirm_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + if hasCheck { + content.append(AnyComponentWithIdentity( + id: "check", + component: AnyComponent( + AlertCheckComponent(title: strings.Chat_PaidMessage_Confirm_DontAskAgain, initialValue: false, externalState: checkState) + ) + )) } - let optionText = hasCheck ? presentationData.strings.Chat_PaidMessage_Confirm_DontAskAgain : nil - - let contentNode = ChatMessagePaymentAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, optionText: optionText, actions: actions, alignment: .vertical) - - completionImpl = { [weak contentNode] in - guard let contentNode else { - return - } - completion(contentNode.dontAskAgain) - } - - let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode, navigationController: navigationController, chatPeerId: context?.account.peerId ?? peers[0].peerId) - dismissImpl = { [weak controller] in - controller?.dismissAnimated() - } - return controller + let alertController = ChatMessagePaymentAlertController( + context: context, + presentationData: presentationData, + updatedPresentationData: updatedPresentationData, + configuration: AlertScreen.Configuration(actionAlignment: .vertical, allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Chat_PaidMessage_Confirm_PayForMessage(count), type: .default, action: { + completion(checkState.value) + }), + .init(title: strings.Common_Cancel) + ], + navigationController: navigationController, + chatPeerId: context?.account.peerId ?? peers[0].peerId + ) + return alertController } public func chatMessageRemovePaymentAlertController( @@ -548,47 +300,55 @@ public func chatMessageRemovePaymentAlertController( amount: StarsAmount?, navigationController: NavigationController?, completion: @escaping (Bool) -> Void -) -> AlertController { - let theme = defaultDarkColorPresentationTheme - let presentationData = updatedPresentationData?.initial ?? presentationData +) -> ViewController { let strings = presentationData.strings - var completionImpl: (() -> Void)? - var dismissImpl: (() -> Void)? - - let actions: [TextAlertAction] = [ - TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: { - dismissImpl?() - }), - TextAlertAction(type: .defaultAction, title: strings.Chat_PaidMessage_RemoveFee_Yes, action: { - completionImpl?() - dismissImpl?() - }) - ] - - let title = strings.Chat_PaidMessage_RemoveFee_Title - let text: String - if let context, chatPeer.id != context.account.peerId { + if case .user = chatPeer { + text = strings.Chat_PaidMessage_RemoveFee_Text(peer.compactDisplayTitle).string + } else if let context, chatPeer.id != context.account.peerId { text = strings.Channel_RemoveFeeAlert_Text(peer.compactDisplayTitle).string } else { text = strings.Chat_PaidMessage_RemoveFee_Text(peer.compactDisplayTitle).string } + + let checkState = AlertCheckComponent.ExternalState() - let optionText = amount.flatMap { strings.Chat_PaidMessage_RemoveFee_Refund(strings.Chat_PaidMessage_RemoveFee_Refund_Stars(Int32(clamping: $0.value))).string } - - let contentNode = ChatMessagePaymentAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, optionText: optionText, actions: actions, alignment: .horizontal) - - completionImpl = { [weak contentNode] in - guard let contentNode else { - return - } - completion(contentNode.dontAskAgain) + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Chat_PaidMessage_RemoveFee_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + if let amount { + content.append(AnyComponentWithIdentity( + id: "check", + component: AnyComponent( + AlertCheckComponent(title: strings.Chat_PaidMessage_RemoveFee_Refund(strings.Chat_PaidMessage_RemoveFee_Refund_Stars(Int32(clamping: amount.value))).string, initialValue: false, externalState: checkState) + ) + )) } - let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode, navigationController: navigationController, chatPeerId: chatPeer.id) - dismissImpl = { [weak controller] in - controller?.dismissAnimated() - } - return controller + let alertController = ChatMessagePaymentAlertController( + context: context, + presentationData: presentationData, + updatedPresentationData: updatedPresentationData, + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.Chat_PaidMessage_RemoveFee_Yes, type: .default, action: { + completion(checkState.value) + }) + ], + navigationController: navigationController, + chatPeerId: chatPeer.id + ) + return alertController } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift index d1dfa76b..11ea5ed6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift @@ -1678,8 +1678,15 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { if isTranslating, !rects.isEmpty { if self.shimmeringNodes.isEmpty { + let color: UIColor + let isIncoming = item.message.effectivelyIncoming(item.context.account.peerId) + if item.presentationData.theme.theme.overallDarkAppearance { + color = isIncoming ? item.presentationData.theme.theme.chat.message.incoming.primaryTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.primaryTextColor.withAlphaComponent(0.1) + } else { + color = isIncoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor.withAlphaComponent(0.1) + } for rects in rects { - let shimmeringNode = ShimmeringLinkNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.secondaryTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor.withAlphaComponent(0.1)) + let shimmeringNode = ShimmeringLinkNode(color: color) shimmeringNode.updateRects(rects) shimmeringNode.frame = self.bounds shimmeringNode.updateLayout(self.bounds.size) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index 9ebeccca..cde7b258 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -85,6 +85,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { public let quote: (quote: EngineMessageReplyQuote, isQuote: Bool)? public let todoItemId: Int32? public let story: StoryId? + public let isSummarized: Bool public let parentMessage: Message public let constrainedSize: CGSize public let animationCache: AnimationCache? @@ -101,6 +102,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { quote: (quote: EngineMessageReplyQuote, isQuote: Bool)?, todoItemId: Int32?, story: StoryId?, + isSummarized: Bool, parentMessage: Message, constrainedSize: CGSize, animationCache: AnimationCache?, @@ -116,6 +118,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { self.quote = quote self.todoItemId = todoItemId self.story = story + self.isSummarized = isSummarized self.parentMessage = parentMessage self.constrainedSize = constrainedSize self.animationCache = animationCache @@ -133,6 +136,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } private let backgroundView: MessageInlineBlockBackgroundView + private var starsView: StarsView? private var quoteIconView: UIImageView? private let contentNode: ASDisplayNode private var titleNode: TextNode? @@ -206,7 +210,6 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { var secondaryColor: UIColor? var tertiaryColor: UIColor? - var authorNameColor: UIColor? var dashSecondaryColor: UIColor? var dashTertiaryColor: UIColor? @@ -239,6 +242,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { break } + if arguments.isSummarized { + authorNameColor = nil + } + switch arguments.type { case let .bubble(incoming): titleColor = incoming ? (authorNameColor ?? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor) : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor @@ -437,7 +444,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } var textLeftInset: CGFloat = 0.0 - let messageText: NSAttributedString + var messageText: NSAttributedString var todoItemCompleted: Bool? if let todoItemId = arguments.todoItemId, let todo = arguments.message?.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo, let todoItem = todo.items.first(where: { $0.id == todoItemId }) { messageText = stringWithAppliedEntities(todoItem.text, entities: todoItem.entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: nil) @@ -607,6 +614,11 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } adjustedConstrainedTextSize.width -= textLeftInset + if arguments.isSummarized { + titleString = NSAttributedString(string: arguments.presentationData.strings.Conversation_Summary_Title, font: titleFont, textColor: titleColor) + messageText = NSAttributedString(string: arguments.presentationData.strings.Conversation_Summary_Text, font: textFont, textColor: titleColor) + } + let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: maxTitleNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: contrainedTextSize.width - additionalTitleWidth, height: contrainedTextSize.height), alignment: .natural, cutout: nil, insets: textInsets)) if isExpiredStory || isStory { contrainedTextSize.width -= 26.0 @@ -687,6 +699,11 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { node = ChatMessageReplyInfoNode() } + var animation = animation + if node.titleNode == nil { + animation = .None + } + node.previousMediaReference = updatedMediaReference //node.textNode?.textNode.displaysAsynchronously = !arguments.presentationData.isPreview @@ -925,6 +942,22 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { giftEmojiLayer.removeFromSuperlayer() } + if arguments.isSummarized { + let starsView: StarsView + if let current = node.starsView { + starsView = current + } else { + starsView = StarsView() + node.starsView = starsView + node.contentNode.view.insertSubview(starsView, at: 1) + } + starsView.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size) + starsView.update(size: backgroundFrame.size, color: mainColor) + } else if let starsView = node.starsView { + node.starsView = nil + starsView.removeFromSuperview() + } + node.contentNode.frame = CGRect(origin: CGPoint(), size: size) return node @@ -1065,3 +1098,96 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { return nil } } + +private final class StarsView: UIView { + private let staticEmitterLayer = CAEmitterLayer() + + private var currentColor: UIColor? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.clipsToBounds = true + + self.layer.addSublayer(self.staticEmitterLayer) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + private func setupEmitter(size: CGSize) { + guard let currentColor = self.currentColor else { + return + } + let color = currentColor + + self.staticEmitterLayer.emitterShape = .rectangle + self.staticEmitterLayer.emitterSize = size + self.staticEmitterLayer.emitterMode = .surface + self.layer.addSublayer(self.staticEmitterLayer) + + let staticEmitter = CAEmitterCell() + staticEmitter.name = "emitter" + staticEmitter.contents = UIImage(bundleImageName: "Premium/Stars/Particle")?.cgImage + staticEmitter.birthRate = 20.0 + staticEmitter.lifetime = 3.2 + staticEmitter.velocity = 18.0 + staticEmitter.velocityRange = 3 + staticEmitter.scale = 0.1 + staticEmitter.scaleRange = 0.08 + staticEmitter.emissionRange = .pi * 2.0 + staticEmitter.setValue(3.0, forKey: "mass") + staticEmitter.setValue(2.0, forKey: "massRange") + + let staticColors: [Any] = [ + color.withAlphaComponent(0.0).cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor, + color.withAlphaComponent(0.0).cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor + ] + let staticColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife") + staticColorBehavior.setValue(staticColors, forKey: "colors") + staticEmitter.setValue([staticColorBehavior], forKey: "emitterBehaviors") + + let attractor = CAEmitterCell.createEmitterBehavior(type: "simpleAttractor") + attractor.setValue("attractor", forKey: "name") + attractor.setValue(20, forKey: "falloff") + attractor.setValue(35, forKey: "radius") + self.staticEmitterLayer.setValue([attractor], forKey: "emitterBehaviors") + self.staticEmitterLayer.setValue(4.0, forKeyPath: "emitterBehaviors.attractor.stiffness") + self.staticEmitterLayer.setValue(false, forKeyPath: "emitterBehaviors.attractor.enabled") + + self.staticEmitterLayer.emitterCells = [staticEmitter] + } + + func update(size: CGSize, color: UIColor) { + if self.staticEmitterLayer.emitterCells == nil { + self.currentColor = color + self.setupEmitter(size: size) + } else if self.currentColor != color { + self.currentColor = color + + let staticColors: [Any] = [ + UIColor.white.withAlphaComponent(0.0).cgColor, + UIColor.white.withAlphaComponent(0.35).cgColor, + color.cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor + ] + let staticColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife") + staticColorBehavior.setValue(staticColors, forKey: "colors") + + for cell in self.staticEmitterLayer.emitterCells ?? [] { + cell.setValue([staticColorBehavior], forKey: "emitterBehaviors") + } + } + + let emitterPosition = CGPoint(x: size.width * 0.5, y: size.height * 0.5) + self.staticEmitterLayer.frame = CGRect(origin: .zero, size: size) + self.staticEmitterLayer.emitterPosition = emitterPosition + self.staticEmitterLayer.setValue(emitterPosition, forKeyPath: "emitterBehaviors.attractor.position") + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift index 61fa256f..bf7b210c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift @@ -121,7 +121,6 @@ private final class GlassButtonView: UIView { override init(frame: CGRect) { self.backgroundView = GlassBackgroundView() - self.backgroundView.isUserInteractionEnabled = false self.iconView = GlassBackgroundView.ContentImageView() self.backgroundView.contentView.addSubview(self.iconView) @@ -460,11 +459,13 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) - var leftInset = leftInset - leftInset += 16.0 + var leftInset = leftInset + 8.0 + var rightInset = rightInset + 8.0 - var rightInset = rightInset - rightInset += 16.0 + if bottomInset <= 32.0 { + leftInset += 18.0 + rightInset += 18.0 + } let panelHeight = defaultHeight(metrics: metrics) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD index 6d03e682..70a8cb94 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/WallpaperBackgroundNode", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/ContextUI", + "//submodules/Components/HierarchyTrackingLayer", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift index 502bd18b..7e38d33e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift @@ -10,6 +10,7 @@ import Postbox import WallpaperBackgroundNode import ChatMessageItemCommon import ContextUI +import HierarchyTrackingLayer public class ChatMessageShareButton: ASDisplayNode { private let referenceNode: ContextReferenceContentNode @@ -21,15 +22,18 @@ public class ChatMessageShareButton: ASDisplayNode { private let topButton: HighlightTrackingButtonNode private let topIconNode: ASImageNode private var topIconOffset = CGPoint() - + private var bottomButton: HighlightTrackingButtonNode? private var bottomIconNode: ASImageNode? + private var starsView: StarsView? + private var separatorNode: ASDisplayNode? private var theme: PresentationTheme? private var isReplies: Bool = false private var hasMore: Bool = false + private var isExpand: Bool = false private var textNode: ImmediateTextNode? @@ -103,7 +107,7 @@ public class ChatMessageShareButton: ASDisplayNode { self.morePressed?() } - public func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize { + public func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false, isSummarize: Bool = false) -> CGSize { var isReplies = false var isNavigate = false var replyCount = 0 @@ -134,15 +138,27 @@ public class ChatMessageShareButton: ASDisplayNode { hasMore = true } - if self.theme !== presentationData.theme.theme || self.isReplies != isReplies || self.hasMore != hasMore { + var isExpand = false + if controllerInteraction.summarizedMessageIds.contains(message.id) { + isExpand = true + } + + if self.theme !== presentationData.theme.theme || self.isReplies != isReplies || self.hasMore != hasMore || self.isExpand != isExpand { self.theme = presentationData.theme.theme self.isReplies = isReplies self.hasMore = hasMore + self.isExpand = isExpand var updatedIconImage: UIImage? var updatedBottomIconImage: UIImage? var updatedIconOffset = CGPoint() - if let _ = message.adAttribute { + if isSummarize { + if isExpand { + updatedIconImage = PresentationResourcesChat.chatFreeExpandButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + } else { + updatedIconImage = PresentationResourcesChat.chatFreeCollapseButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + } + } else if let _ = message.adAttribute { updatedIconImage = PresentationResourcesChat.chatFreeCloseButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) updatedIconOffset = CGPoint(x: UIScreenPixel, y: UIScreenPixel) @@ -167,6 +183,17 @@ public class ChatMessageShareButton: ASDisplayNode { updatedIconImage = PresentationResourcesChat.chatFreeShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) } + if isSummarize { + if self.topIconNode.image != nil, let snapshotView = self.topIconNode.view.snapshotContentTree() { + self.view.addSubview(snapshotView) + + snapshotView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + self.topIconNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25) + } + } + self.topIconNode.image = updatedIconImage self.topIconOffset = updatedIconOffset @@ -309,6 +336,22 @@ public class ChatMessageShareButton: ASDisplayNode { self.backgroundBlurView?.view.isHidden = false } + if isSummarize { + let starsView: StarsView + if let current = self.starsView { + starsView = current + } else { + starsView = StarsView() + self.starsView = starsView + self.view.insertSubview(starsView, belowSubview: self.topIconNode.view) + } + starsView.frame = CGRect(origin: .zero, size: size) + starsView.update(size: size, color: .white) + } else if let starsView = self.starsView { + self.starsView = nil + starsView.removeFromSuperview() + } + return size } @@ -322,3 +365,70 @@ public class ChatMessageShareButton: ASDisplayNode { } } } + +private final class StarsView: UIView { + private let hierarchyTrackingLayer: HierarchyTrackingLayer + private let topStar = SimpleLayer() + private let bottomStar = SimpleLayer() + + override init(frame: CGRect) { + self.hierarchyTrackingLayer = HierarchyTrackingLayer() + + super.init(frame: frame) + + self.clipsToBounds = true + self.layer.addSublayer(self.hierarchyTrackingLayer) + + self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in + guard let self else { + return + } + self.updateAnimations() + } + + self.layer.addSublayer(self.topStar) + self.layer.addSublayer(self.bottomStar) + + let image = UIImage(bundleImageName: "Settings/Storage/ParticleStar") + self.topStar.contents = image?.cgImage + self.bottomStar.contents = image?.cgImage + + self.topStar.bounds = CGRect(origin: .zero, size: CGSize(width: 10.0, height: 10.0)) + self.bottomStar.bounds = CGRect(origin: .zero, size: CGSize(width: 10.0, height: 10.0)) + + self.topStar.opacity = 0.5 + self.bottomStar.opacity = 0.5 + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func updateAnimations() { + let topAnimation = CAKeyframeAnimation(keyPath: "transform.scale") + topAnimation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.55 as NSNumber] + topAnimation.keyTimes = [0.0 as NSNumber, 0.1 as NSNumber, 1.0 as NSNumber] + topAnimation.duration = 0.9 + topAnimation.autoreverses = true + topAnimation.repeatCount = Float.infinity + topAnimation.beginTime = CACurrentMediaTime() + self.topStar.add(topAnimation, forKey: "blink") + + let bottomAnimation = CAKeyframeAnimation(keyPath: "transform.scale") + bottomAnimation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.55 as NSNumber] + bottomAnimation.keyTimes = [0.0 as NSNumber, 0.1 as NSNumber, 1.0 as NSNumber] + bottomAnimation.duration = 0.9 + bottomAnimation.autoreverses = true + bottomAnimation.repeatCount = Float.infinity + bottomAnimation.beginTime = CACurrentMediaTime() + 0.9 + self.bottomStar.add(bottomAnimation, forKey: "blink") + } + + func update(size: CGSize, color: UIColor) { + self.topStar.layerTintColor = color.cgColor + self.bottomStar.layerTintColor = color.cgColor + + self.topStar.position = CGPoint(x: 9.0, y: 9.0) + self.bottomStar.position = CGPoint(x: size.width - 9.0, y: size.height - 9.0) + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index 6ab149a5..5e3a781e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -777,6 +777,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { quote: replyQuote, todoItemId: replyTodoItemId, story: replyStory, + isSummarized: false, parentMessage: item.message, constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), animationCache: item.controllerInteraction.presentationContext.animationCache, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 2ed26e21..ce3349ac 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -116,6 +116,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { private var appliedExpandedBlockIds: Set? private var displayContentsUnderSpoilers: (value: Bool, location: CGPoint?) = (false, nil) + private var isSummaryApplied = false + private final class TextRevealAnimationState { let fromCount: Int let toCount: Int @@ -404,6 +406,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } + var isSummaryApplied = false var isTranslating = false if let invoice { rawText = invoice.description @@ -417,7 +420,14 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if let updatingMedia = item.attributes.updatingMedia { rawText = updatingMedia.text } else { - rawText = item.message.text + // MARK: - Ghostgram: Check for local edit first + let peerId = item.message.id.peerId.toInt64() + let messageId = item.message.id.id + if let localEdit = LocalEditManager.shared.getLocalEdit(peerId: peerId, messageId: messageId) { + rawText = localEdit + } else { + rawText = item.message.text + } } for attribute in item.message.attributes { @@ -441,15 +451,29 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { messageEntities = updatingMedia.entities?.entities ?? [] } + let translateToLanguage = item.associatedData.translateToLanguage + var isSummarized = false + if item.controllerInteraction.summarizedMessageIds.contains(item.message.id) { + isSummarized = true + } + if let subject = item.associatedData.subject, case .messageOptions = subject { - } else if let translateToLanguage = item.associatedData.translateToLanguage, !item.message.text.isEmpty && incoming { - isTranslating = true - for attribute in item.message.attributes { - if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { + } else if !item.message.text.isEmpty && incoming { + if translateToLanguage != nil || isSummarized { + isTranslating = true + } + if isTranslating { + if isSummarized, let attribute = item.message.attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute, let summary = attribute.summaryForLang(translateToLanguage) { + rawText = summary.text + messageEntities = summary.entities + isTranslating = false + isSummaryApplied = true + } else if let attribute = item.message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { rawText = attribute.text messageEntities = attribute.entities - isTranslating = false - break + if !isSummarized { + isTranslating = false + } } } } @@ -718,6 +742,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread), hasAutoremove: item.message.isSelfExpiring, + isDeleted: AntiDeleteManager.shared.isMessageDeleted(peerId: item.message.id.peerId.toInt64(), messageId: item.message.id.id) || AntiDeleteManager.shared.isMessageDeleted(text: item.message.text), canViewReactionList: canViewMessageReactionList(message: item.topMessage), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer @@ -789,6 +814,20 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.textNode.textNode.displaysAsynchronously = !item.presentationData.isPreview animation.animator.updateFrame(layer: strongSelf.containerNode.layer, frame: CGRect(origin: CGPoint(), size: boundingSize), completion: nil) + + if strongSelf.isSummaryApplied != isSummaryApplied { + strongSelf.isSummaryApplied = isSummaryApplied + itemApply?.setInvertOffsetDirection() + + if let snapshotView = strongSelf.textNode.textNode.view.snapshotContentTree() { + strongSelf.view.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + strongSelf.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } if strongSelf.appliedExpandedBlockIds != nil && strongSelf.appliedExpandedBlockIds != strongSelf.expandedBlockIds { itemApply?.setInvertOffsetDirection() } @@ -1227,7 +1266,14 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if let current = self.shimmeringNode { shimmeringNode = current } else { - shimmeringNode = ShimmeringLinkNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.secondaryTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor.withAlphaComponent(0.1)) + let color: UIColor + let isIncoming = item.message.effectivelyIncoming(item.context.account.peerId) + if item.presentationData.theme.theme.overallDarkAppearance { + color = isIncoming ? item.presentationData.theme.theme.chat.message.incoming.primaryTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.primaryTextColor.withAlphaComponent(0.1) + } else { + color = isIncoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor.withAlphaComponent(0.1) + } + shimmeringNode = ShimmeringLinkNode(color: color) shimmeringNode.updateRects(rects) shimmeringNode.frame = self.textNode.textNode.frame shimmeringNode.updateLayout(self.textNode.textNode.frame.size) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift index 850d0313..bb614b86 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift @@ -1481,8 +1481,15 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode { if isTranslating, !rects.isEmpty { if self.shimmeringNodes.isEmpty { + let color: UIColor + let isIncoming = item.message.effectivelyIncoming(item.context.account.peerId) + if item.presentationData.theme.theme.overallDarkAppearance { + color = isIncoming ? item.presentationData.theme.theme.chat.message.incoming.primaryTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.primaryTextColor.withAlphaComponent(0.1) + } else { + color = isIncoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor.withAlphaComponent(0.1) + } for rects in rects { - let shimmeringNode = ShimmeringLinkNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.secondaryTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor.withAlphaComponent(0.1)) + let shimmeringNode = ShimmeringLinkNode(color: color) shimmeringNode.updateRects(rects) shimmeringNode.frame = self.bounds shimmeringNode.updateLayout(self.bounds.size) diff --git a/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/Sources/ChatNewThreadInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/Sources/ChatNewThreadInfoItem.swift index 0cdd733c..9870951c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/Sources/ChatNewThreadInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/Sources/ChatNewThreadInfoItem.swift @@ -108,7 +108,7 @@ public final class ChatNewThreadInfoItemNode: ListViewItemNode, ASGestureRecogni self.arrowView = UIImageView() - super.init(layerBacked: false, dynamicBounce: true, rotated: true) + super.init(layerBacked: false, rotated: true) self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) diff --git a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift index 8ada9fca..88f49088 100644 --- a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift @@ -333,7 +333,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { self.placeholderNode = StickerShimmerEffectNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.imageNode) @@ -721,7 +721,7 @@ public final class ChatQrCodeScreenImpl: ViewController, ChatQrCodeScreen { } private func iconColors(theme: PresentationTheme) -> [String: UIColor] { - let accentColor = theme.rootController.navigationBar.glassBarButtonForegroundColor + let accentColor = theme.chat.inputPanel.panelControlColor var colors: [String: UIColor] = [:] colors["Sunny.Path 14.Path.Stroke 1"] = accentColor colors["Sunny.Path 15.Path.Stroke 1"] = accentColor @@ -1470,7 +1470,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index 459319a3..6c94d414 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -49,7 +49,7 @@ public final class ChatRecentActionsController: TelegramBaseController { self.titleView = CounterControllerTitleView(theme: self.presentationData.theme) - super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none, groupCallPanelSource: .none) + super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) self.automaticallyControlPresentationContextLayout = false @@ -263,7 +263,7 @@ public final class ChatRecentActionsController: TelegramBaseController { self.navigationItem.setRightBarButton(rightButton.buttonItem, animated: false) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData), transition: .immediate) self.controllerNode.updatePresentationData(self.presentationData) } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index b3e9e0fc..3a659778 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -130,7 +130,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self.panelInfoButtonNode = HighlightableButtonNode() self.listNode = ListView() - self.listNode.dynamicBounceEnabled = false self.listNode.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0) self.listNode.accessibilityPageScrolledString = { row, count in return presentationData.strings.VoiceOver_ScrollStatus(row, count).string diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift index 5c524bf3..6c6b6036 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift @@ -25,7 +25,7 @@ final class ChatRecentActionsSearchNavigationContentNode: NavigationBarContentNo self.cancel = cancel - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: false) + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), presentationTheme: theme, strings: strings, fieldStyle: .modern, displayBackground: false) let placeholderText = strings.Common_Search self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) @@ -51,10 +51,12 @@ final class ChatRecentActionsSearchNavigationContentNode: NavigationBarContentNo return 54.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { diff --git a/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/BUILD new file mode 100644 index 00000000..69f39888 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/BUILD @@ -0,0 +1,31 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatSearchNavigationContentNode", + module_name = "ChatSearchNavigationContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/ChatPresentationInterfaceState", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SearchBarNode", + "//submodules/LocalizedPeerData", + "//submodules/AccountContext", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/ActivityIndicator", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift new file mode 100644 index 00000000..a72ca229 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift @@ -0,0 +1,299 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import SearchBarNode +import LocalizedPeerData +import SwiftSignalKit +import AccountContext +import ChatPresentationInterfaceState +import ComponentFlow +import GlassBackgroundComponent +import ActivityIndicator + +private let searchBarFont = Font.regular(17.0) + +public final class ChatSearchNavigationContentNode: NavigationBarContentNode { + private let context: AccountContext + private var theme: PresentationTheme + private let strings: PresentationStrings + private let chatLocation: ChatLocation + + private let backgroundContainer: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView + private let iconView: UIImageView + private var activityIndicator: ActivityIndicator? + private let searchBar: SearchBarNode + private let close: (background: GlassBackgroundView, icon: UIImageView) + + private let interaction: ChatPanelInterfaceInteraction + + private var hasActivity: Bool = false + private var searchingActivityDisposable: Disposable? + + private var params: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)? + + public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, chatLocation: ChatLocation, interaction: ChatPanelInterfaceInteraction, presentationInterfaceState: ChatPresentationInterfaceState) { + self.context = context + self.theme = theme + self.strings = strings + self.chatLocation = chatLocation + self.interaction = interaction + + self.backgroundContainer = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + self.backgroundContainer.contentView.addSubview(self.backgroundView) + self.iconView = UIImageView() + self.backgroundView.contentView.addSubview(self.iconView) + + self.close = (GlassBackgroundView(), UIImageView()) + self.close.background.contentView.addSubview(self.close.icon) + + self.searchBar = SearchBarNode( + theme: SearchBarNodeTheme( + background: .clear, + separator: .clear, + inputFill: .clear, + primaryText: theme.chat.inputPanel.panelControlColor, + placeholder: theme.chat.inputPanel.inputPlaceholderColor, + inputIcon: theme.chat.inputPanel.inputControlColor, + inputClear: theme.chat.inputPanel.panelControlColor, + accent: theme.chat.inputPanel.panelControlAccentColor, + keyboard: theme.rootController.keyboardColor + ), + presentationTheme: theme, + strings: strings, + fieldStyle: .inlineNavigation, + forceSeparator: false, + displayBackground: false, + cancelText: nil + ) + let placeholderText: String + switch chatLocation { + case .peer, .replyThread, .customChatContents: + if chatLocation.peerId == context.account.peerId, presentationInterfaceState.hasSearchTags { + if case .standard(.embedded(false)) = presentationInterfaceState.mode { + placeholderText = strings.Common_Search + } else { + placeholderText = strings.Chat_SearchTagsPlaceholder + } + } else { + placeholderText = strings.Conversation_SearchPlaceholder + } + } + self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.chat.inputPanel.inputPlaceholderColor) + + super.init() + + self.view.addSubview(self.backgroundContainer) + self.backgroundView.contentView.addSubview(self.searchBar.view) + + self.backgroundContainer.contentView.addSubview(self.close.background) + self.close.background.contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:)))) + + self.searchBar.cancel = { [weak self] in + self?.searchBar.deactivate(clear: false) + self?.interaction.dismissMessageSearch() + } + + self.searchBar.textUpdated = { [weak self] query, _ in + self?.interaction.updateMessageSearch(query) + } + + self.searchBar.clearPrefix = { [weak self] in + self?.interaction.toggleMembersSearch(false) + } + + self.searchBar.clearTokens = { [weak self] in + self?.interaction.toggleMembersSearch(false) + } + + self.searchBar.tokensUpdated = { [weak self] tokens in + if tokens.isEmpty { + self?.interaction.toggleMembersSearch(false) + } + } + + if let statuses = interaction.statuses { + self.searchingActivityDisposable = (statuses.searching + |> deliverOnMainQueue).startStrict(next: { [weak self] value in + guard let self else { + return + } + if self.hasActivity != value { + self.hasActivity = value + if let params = self.params { + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + } + } + }) + } + } + + deinit { + self.searchingActivityDisposable?.dispose() + } + + override public var nominalHeight: CGFloat { + return 60.0 + } + + @objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.searchBar.cancel?() + } + } + + override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + self.params = (size, leftInset, rightInset) + + let transition = ComponentTransition(transition) + + let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0), size: CGSize(width: size.width - 16.0 * 2.0 - leftInset - rightInset - 44.0 - 8.0, height: 44.0)) + let closeFrame = CGRect(origin: CGPoint(x: size.width - 16.0 - rightInset - 44.0, y: backgroundFrame.minY), size: CGSize(width: 44.0, height: 44.0)) + + transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size)) + self.backgroundContainer.update(size: size, isDark: self.theme.overallDarkAppearance, transition: transition) + + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + if self.iconView.image == nil { + self.iconView.image = UIImage(bundleImageName: "Navigation/Search")?.withRenderingMode(.alwaysTemplate) + } + transition.setTintColor(view: self.iconView, color: self.theme.rootController.navigationSearchBar.inputIconColor) + + if let image = self.iconView.image { + let imageSize: CGSize + let iconFrame: CGRect + let iconFraction: CGFloat = 0.8 + imageSize = CGSize(width: image.size.width * iconFraction, height: image.size.height * iconFraction) + iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floor((backgroundFrame.height - imageSize.height) * 0.5)), size: imageSize) + transition.setPosition(view: self.iconView, position: iconFrame.center) + transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + } + + if self.hasActivity { + let activityIndicator: ActivityIndicator + if let current = self.activityIndicator { + activityIndicator = current + } else { + activityIndicator = ActivityIndicator(type: .custom(self.theme.chat.inputPanel.inputControlColor, 14.0, 14.0, false)) + self.activityIndicator = activityIndicator + self.backgroundView.contentView.addSubview(activityIndicator.view) + } + let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0)) + let indicatorFrame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((backgroundFrame.height - indicatorSize.height) * 0.5)), size: indicatorSize) + transition.setPosition(view: activityIndicator.view, position: indicatorFrame.center) + transition.setBounds(view: activityIndicator.view, bounds: CGRect(origin: CGPoint(), size: indicatorFrame.size)) + } else if let activityIndicator = self.activityIndicator { + self.activityIndicator = nil + activityIndicator.view.removeFromSuperview() + } + self.iconView.isHidden = self.hasActivity + + let searchBarFrame = CGRect(origin: CGPoint(x: 36.0, y: 0.0), size: CGSize(width: backgroundFrame.width - 36.0 - 4.0, height: 44.0)) + transition.setFrame(view: self.searchBar.view, frame: searchBarFrame) + self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition) + + if self.close.icon.image == nil { + self.close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setStrokeColor(UIColor.white.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0)) + context.move(to: CGPoint(x: size.width - 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) + } + + if let image = close.icon.image { + self.close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size)) + } + self.close.icon.tintColor = self.theme.chat.inputPanel.panelControlColor + + transition.setFrame(view: self.close.background, frame: closeFrame) + self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + return size + } + + public func activate() { + self.searchBar.activate() + } + + public func deactivate() { + self.searchBar.deactivate(clear: false) + } + + public func update(presentationInterfaceState: ChatPresentationInterfaceState) { + if let search = presentationInterfaceState.search { + self.searchBar.updateThemeAndStrings( + theme: SearchBarNodeTheme( + background: .clear, + separator: .clear, + inputFill: .clear, + primaryText: presentationInterfaceState.theme.chat.inputPanel.panelControlColor, + placeholder: presentationInterfaceState.theme.chat.inputPanel.inputPlaceholderColor, + inputIcon: presentationInterfaceState.theme.chat.inputPanel.inputControlColor, + inputClear: presentationInterfaceState.theme.chat.inputPanel.panelControlColor, + accent: presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor, + keyboard: presentationInterfaceState.theme.rootController.keyboardColor + ), + presentationTheme: presentationInterfaceState.theme, + strings: presentationInterfaceState.strings + ) + + switch search.domain { + case .everything, .tag: + self.searchBar.tokens = [] + self.searchBar.prefixString = nil + let placeholderText: String + switch self.chatLocation { + case .peer, .replyThread, .customChatContents: + if presentationInterfaceState.historyFilter != nil { + placeholderText = self.strings.Common_Search + } else if self.chatLocation.peerId == self.context.account.peerId, presentationInterfaceState.hasSearchTags { + if case .standard(.embedded(false)) = presentationInterfaceState.mode { + placeholderText = strings.Common_Search + } else { + placeholderText = self.strings.Chat_SearchTagsPlaceholder + } + } else { + placeholderText = self.strings.Conversation_SearchPlaceholder + } + } + self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: presentationInterfaceState.theme.chat.inputPanel.inputPlaceholderColor) + case .members: + self.searchBar.tokens = [] + self.searchBar.prefixString = NSAttributedString(string: strings.Conversation_SearchByName_Prefix, font: searchBarFont, textColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor) + self.searchBar.placeholderString = nil + case let .member(peer): + self.searchBar.tokens = [SearchBarToken(id: peer.id, icon: UIImage(bundleImageName: "Chat List/Search/User"), title: EnginePeer(peer).compactDisplayTitle, permanent: false)] + self.searchBar.prefixString = nil + self.searchBar.placeholderString = nil + } + + if self.searchBar.text != search.query { + self.searchBar.text = search.query + self.interaction.updateMessageSearch(search.query) + } + } + + if presentationInterfaceState.theme != self.theme { + self.theme = presentationInterfaceState.theme + if let params = self.params { + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + } + } + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift index 46b7f464..e54789b9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatFloatingTopicsPanel.swift @@ -151,8 +151,8 @@ public final class ChatFloatingTopicsPanel: Component { }, containerSize: CGSize(width: 72.0 + 8.0, height: availableSize.height) ) - let sidePanelFrame = CGRect(origin: CGPoint(), size: CGSize(width: 8.0 + 80.0, height: availableSize.height - 8.0 - environment.insets.bottom)) - let sidePanelBackgroundFrame = CGRect(origin: CGPoint(x: 8.0, y: 8.0), size: CGSize(width: 80.0, height: availableSize.height - 8.0 - 8.0 - environment.insets.bottom)) + let sidePanelFrame = CGRect(origin: CGPoint(x: 8.0, y: 0.0), size: CGSize(width: 16.0 + 80.0, height: availableSize.height - 8.0 - environment.insets.bottom)) + let sidePanelBackgroundFrame = CGRect(origin: CGPoint(x: 16.0, y: 8.0), size: CGSize(width: 80.0, height: availableSize.height - 8.0 - 8.0 - environment.insets.bottom)) currentPanelBackgroundFrame = sidePanelBackgroundFrame if let sidePanelView = sidePanel.view as? ChatSideTopicsPanel.View { if sidePanelView.superview == nil { @@ -160,7 +160,7 @@ public final class ChatFloatingTopicsPanel: Component { sidePanelView.clipsToBounds = true self.addSubview(sidePanelView) - sidePanelView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: sidePanelSize.height, height: 8.0 + 40.0)) + sidePanelView.frame = CGRect(origin: CGPoint(x: 8.0, y: 0.0), size: CGSize(width: sidePanelSize.height, height: 8.0 + 40.0)) } transition.setFrame(view: sidePanelView, frame: sidePanelFrame) } @@ -168,7 +168,7 @@ public final class ChatFloatingTopicsPanel: Component { self.sidePanel = nil if let sidePanelView = sidePanel.view as? ChatSideTopicsPanel.View { sidePanelView.clipsToBounds = true - transition.setFrame(view: sidePanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: sidePanelView.bounds.width, height: 8.0 + 40.0)), completion: { [weak sidePanelView] _ in + transition.setFrame(view: sidePanelView, frame: CGRect(origin: CGPoint(x: 8.0, y: 0.0), size: CGSize(width: sidePanelView.bounds.width, height: 8.0 + 40.0)), completion: { [weak sidePanelView] _ in sidePanelView?.removeFromSuperview() }) } @@ -212,17 +212,17 @@ public final class ChatFloatingTopicsPanel: Component { right: 0.0 )) }, - containerSize: CGSize(width: availableSize.width, height: 8.0 + 40.0) + containerSize: CGSize(width: availableSize.width - 16.0, height: 8.0 + 40.0) ) - let topPanelFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width - 8.0, height: 8.0 + 40.0)) - let topPanelBackgroundFrame = CGRect(origin: CGPoint(x: 8.0, y: 8.0), size: CGSize(width: availableSize.width - 8.0 - 8.0, height: 40.0)) + let topPanelFrame = CGRect(origin: CGPoint(x: 8.0, y: 0.0), size: CGSize(width: availableSize.width - 16.0, height: 8.0 + 40.0)) + let topPanelBackgroundFrame = CGRect(origin: CGPoint(x: 16.0, y: 8.0), size: CGSize(width: availableSize.width - 16.0 - 16.0, height: 40.0)) currentPanelBackgroundFrame = topPanelBackgroundFrame if let topPanelView = topPanel.view as? ChatSideTopicsPanel.View { if topPanelView.superview == nil { topPanelView.clipsToBounds = true topPanelView.layer.cornerRadius = 20.0 self.addSubview(topPanelView) - topPanelView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 80.0 + 8.0, height: topPanelFrame.height)) + topPanelView.frame = CGRect(origin: CGPoint(x: 8.0, y: 0.0), size: CGSize(width: 80.0 + 16.0, height: topPanelFrame.height)) } transition.setFrame(view: topPanelView, frame: topPanelFrame) } @@ -230,7 +230,7 @@ public final class ChatFloatingTopicsPanel: Component { self.topPanel = nil if let topPanelView = topPanel.view as? ChatSideTopicsPanel.View { topPanelView.clipsToBounds = true - transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 8.0 + 72.0, height: topPanelView.bounds.height)), completion: { [weak topPanelView] _ in + transition.setFrame(view: topPanelView, frame: CGRect(origin: CGPoint(x: 8.0, y: 0.0), size: CGSize(width: 16.0 + 72.0, height: topPanelView.bounds.height)), completion: { [weak topPanelView] _ in topPanelView?.removeFromSuperview() }) } diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift index d7032913..872c9df9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift @@ -464,7 +464,7 @@ public final class ChatSideTopicsPanel: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: titleText, font: Font.regular(10.0), textColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.secondaryTextColor)), + text: .plain(NSAttributedString(string: titleText, font: Font.regular(10.0), textColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.chat.inputPanel.panelControlColor)), horizontalAlignment: .center, maximumNumberOfLines: 2 )), @@ -897,7 +897,7 @@ public final class ChatSideTopicsPanel: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: titleText, font: Font.medium(14.0), textColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.secondaryTextColor)), + text: .plain(NSAttributedString(string: titleText, font: Font.medium(14.0), textColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.chat.inputPanel.panelControlColor)), horizontalAlignment: .center, maximumNumberOfLines: 2 )), @@ -1109,7 +1109,7 @@ public final class ChatSideTopicsPanel: Component { transition: .immediate, component: AnyComponent(BundleIconComponent( name: isReordering ? "Media Editor/Done" : "Chat/Title Panels/SidebarIcon", - tintColor: location == .side ? theme.rootController.navigationBar.accentTextColor : theme.rootController.navigationBar.secondaryTextColor, + tintColor: location == .side ? theme.rootController.navigationBar.accentTextColor : theme.chat.inputPanel.panelControlColor, maxSize: CGSize(width: 24.0, height: 24.0), scaleFactor: 1.0 )), @@ -1242,7 +1242,7 @@ public final class ChatSideTopicsPanel: Component { transition: .immediate, component: AnyComponent(BundleIconComponent( name: "Chat List/Tabs/IconChats", - tintColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.secondaryTextColor + tintColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.chat.inputPanel.panelControlColor )), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) @@ -1257,7 +1257,7 @@ public final class ChatSideTopicsPanel: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: titleText, font: Font.regular(10.0), textColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.secondaryTextColor)), + text: .plain(NSAttributedString(string: titleText, font: Font.regular(10.0), textColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.chat.inputPanel.panelControlColor)), maximumNumberOfLines: 2 )), environment: {}, @@ -1394,7 +1394,7 @@ public final class ChatSideTopicsPanel: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: titleText, font: Font.medium(14.0), textColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.secondaryTextColor)), + text: .plain(NSAttributedString(string: titleText, font: Font.medium(14.0), textColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.chat.inputPanel.panelControlColor)), maximumNumberOfLines: 2 )), environment: {}, diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift index a4e00c92..b758e33d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift @@ -149,7 +149,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag public let textNode: ImmediateAnimatedCountLabelNode public let expandMediaInputButton: HighlightTrackingButton - private let expandMediaInputButtonBackgroundView: GlassBackgroundView + public let expandMediaInputButtonBackgroundView: GlassBackgroundView private let expandMediaInputButtonIcon: GlassBackgroundView.ContentImageView private var effectBadgeView: EffectBadgeView? @@ -201,10 +201,9 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag self.expandMediaInputButton = HighlightTrackingButton() self.expandMediaInputButtonBackgroundView = GlassBackgroundView() - self.expandMediaInputButtonBackgroundView.isUserInteractionEnabled = false - self.expandMediaInputButton.addSubview(self.expandMediaInputButtonBackgroundView) self.expandMediaInputButtonIcon = GlassBackgroundView.ContentImageView() self.expandMediaInputButtonBackgroundView.contentView.addSubview(self.expandMediaInputButtonIcon) + self.expandMediaInputButtonBackgroundView.contentView.addSubview(self.expandMediaInputButton) self.expandMediaInputButtonIcon.image = PresentationResourcesChat.chatInputPanelExpandButtonImage(presentationInterfaceState.theme) self.expandMediaInputButtonIcon.tintColor = theme.chat.inputPanel.panelControlColor self.expandMediaInputButtonIcon.setMonochromaticEffect(tintColor: theme.chat.inputPanel.panelControlColor) @@ -242,7 +241,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag self.sendContainerNode.view.addSubview(self.sendButtonBackgroundView) self.sendContainerNode.addSubnode(self.sendButton) self.sendContainerNode.addSubnode(self.textNode) - self.view.addSubview(self.expandMediaInputButton) + self.view.addSubview(self.expandMediaInputButtonBackgroundView) self.expandMediaInputButton.highligthedChanged = { [weak self] highlighted in guard let self else { @@ -402,7 +401,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag transition.updateFrame(view: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(view: self.expandMediaInputButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size)) - self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition)) + self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: ComponentTransition(transition)) if let image = self.expandMediaInputButtonIcon.image { let expandIconFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size) self.expandMediaInputButtonIcon.center = expandIconFrame.center diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift index 88c90e22..1a0b15a8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift @@ -491,7 +491,6 @@ public final class ChatTextInputPanelComponent: Component { pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: false, replyMessage: nil, @@ -793,7 +792,6 @@ public final class ChatTextInputPanelComponent: Component { pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: false, replyMessage: nil, diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index 90366396..1f78c9fd 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -703,7 +703,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.sendActionButtons.micButtonBackgroundView.alpha = 0.0 self.sendActionButtons.micButton.alpha = 0.0 self.sendActionButtons.micButtonTintMaskView.alpha = 0.0 - self.sendActionButtons.expandMediaInputButton.alpha = 0.0 + self.sendActionButtons.expandMediaInputButtonBackgroundView.alpha = 0.0 self.mediaActionButtons = ChatTextInputActionButtonsNode(context: context, presentationInterfaceState: presentationInterfaceState, presentationContext: presentationContext, presentController: presentController) self.mediaActionButtons.sendContainerNode.alpha = 0.0 @@ -811,11 +811,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if highlighted { self.attachmentButtonIcon.layer.removeAnimation(forKey: "opacity") self.attachmentButtonIcon.alpha = 0.4 - self.attachmentButtonIcon.layer.allowsGroupOpacity = true } else { self.attachmentButtonIcon.alpha = 1.0 self.attachmentButtonIcon.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - self.attachmentButtonIcon.layer.allowsGroupOpacity = false } } } @@ -901,7 +899,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.mediaActionButtons.updateAccessibility() self.mediaActionButtons.expandMediaInputButton.addTarget(self, action: #selector(self.expandButtonPressed), for: .touchUpInside) - self.mediaActionButtons.expandMediaInputButton.alpha = 0.0 + self.mediaActionButtons.expandMediaInputButtonBackgroundView.alpha = 0.0 self.searchLayoutClearButton.highligthedChanged = { [weak self] highlighted in guard let self else { @@ -1174,6 +1172,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.touchDownGestureRecognizer = recognizer textInputNode.textView.accessibilityHint = self.textPlaceholderNode.attributedText?.string + + self.isAccessibilityContainer = true + self.accessibilityElements = [textInputNode.textView] } private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics, bottomInset: CGFloat) -> CGFloat { @@ -2437,8 +2438,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.attachmentButtonBackground.contentView.addSubview(dotAnimationView) dotAnimationView.frame = dotAnimationSize.centered(in: self.attachmentButtonBackground.contentView.bounds) - self.attachmentButtonIcon.layer.opacity = 0.0 - self.attachmentButtonIcon.layer.transform = CATransform3DMakeScale(0.001, 0.001, 1.0) + self.attachmentButtonIcon.isHidden = true dotAnimationView.playOnce(completion: { [weak self, weak dotAnimationView] in guard let self else { return @@ -2453,8 +2453,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg transition.setScale(view: dotAnimationView, scale: 0.001) } - transition.setAlpha(view: self.attachmentButtonIcon, alpha: 1.0) - transition.setScale(view: self.attachmentButtonIcon, scale: 1.0) + self.attachmentButtonIcon.isHidden = false + transition.animateAlpha(view: self.attachmentButtonIcon, from: 0.0, to: 1.0) + transition.animateScale(view: self.attachmentButtonIcon, from: 0.001, to: 1.0) }) } } @@ -2692,8 +2693,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg transition.updatePosition(layer: dotAnimationView.layer, position: self.attachmentButtonBackground.contentView.bounds.center) - self.attachmentButtonIcon.layer.opacity = 0.0 - self.attachmentButtonIcon.layer.transform = CATransform3DMakeScale(0.001, 0.001, 1.0) + self.attachmentButtonIcon.isHidden = true dotAnimationView.playOnce(completion: { [weak self, weak dotAnimationView] in guard let self else { return @@ -2708,8 +2708,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg transition.setScale(view: dotAnimationView, scale: 0.001) } - transition.setAlpha(view: self.attachmentButtonIcon, alpha: 1.0) - transition.setScale(view: self.attachmentButtonIcon, scale: 1.0) + self.attachmentButtonIcon.isHidden = false + transition.animateAlpha(view: self.attachmentButtonIcon, from: 0.0, to: 1.0) + transition.animateScale(view: self.attachmentButtonIcon, from: 0.001, to: 1.0) }) } } else { @@ -4463,15 +4464,15 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } if mediaInputIsActive && !hideExpandMediaInput { - if self.mediaActionButtons.expandMediaInputButton.alpha.isZero { - self.mediaActionButtons.expandMediaInputButton.alpha = 1.0 + if self.mediaActionButtons.expandMediaInputButtonBackgroundView.alpha.isZero { + self.mediaActionButtons.expandMediaInputButtonBackgroundView.alpha = 1.0 if alphaTransition.isAnimated { - self.mediaActionButtons.expandMediaInputButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.mediaActionButtons.expandMediaInputButtonBackgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } } else { - if !self.mediaActionButtons.expandMediaInputButton.alpha.isZero { - alphaTransition.updateAlpha(layer: self.mediaActionButtons.expandMediaInputButton.layer, alpha: 0.0) + if !self.mediaActionButtons.expandMediaInputButtonBackgroundView.alpha.isZero { + alphaTransition.updateAlpha(layer: self.mediaActionButtons.expandMediaInputButtonBackgroundView.layer, alpha: 0.0) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift index c9bab92d..3b80c51c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift @@ -170,7 +170,7 @@ public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDe self.disclaimerTextNode.textNode.isUserInteractionEnabled = false self.disclaimerTextNode.textNode.displaysAsynchronously = false - super.init(layerBacked: false, dynamicBounce: true, rotated: true) + super.init(layerBacked: false, rotated: true) self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) diff --git a/submodules/TelegramUI/Components/Chat/EditableTokenListNode/BUILD b/submodules/TelegramUI/Components/Chat/EditableTokenListNode/BUILD index cb19c19e..4b23fb12 100644 --- a/submodules/TelegramUI/Components/Chat/EditableTokenListNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/EditableTokenListNode/BUILD @@ -16,6 +16,9 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/AvatarNode", "//submodules/AccountContext", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift b/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift index a2ec801b..20782373 100644 --- a/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift +++ b/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift @@ -6,6 +6,9 @@ import TelegramCore import TelegramPresentationData import AvatarNode import AccountContext +import GlassBackgroundComponent +import ComponentFlow +import ComponentDisplayAdapters public struct EditableTokenListToken { public enum Subject { @@ -26,23 +29,6 @@ public struct EditableTokenListToken { } } -private let caretIndicatorImage = generateVerticallyStretchableFilledCircleImage(radius: 1.0, color: UIColor(rgb: 0x3350ee)) - -private func caretAnimation() -> CAAnimation { - let animation = CAKeyframeAnimation(keyPath: "opacity") - animation.values = [1.0 as NSNumber, 0.0 as NSNumber, 1.0 as NSNumber, 1.0 as NSNumber] - let firstDuration = 0.3 - let secondDuration = 0.25 - let restDuration = 0.35 - let duration = firstDuration + secondDuration + restDuration - let keyTimes: [NSNumber] = [0.0 as NSNumber, (firstDuration / duration) as NSNumber, ((firstDuration + secondDuration) / duration) as NSNumber, ((firstDuration + secondDuration + restDuration) / duration) as NSNumber] - - animation.keyTimes = keyTimes - animation.duration = duration - animation.repeatCount = Float.greatestFiniteMagnitude - return animation -} - private func generateRemoveIcon(_ color: UIColor) -> UIImage? { return generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in context.clear(CGRect(origin: .zero, size: size)) @@ -61,35 +47,10 @@ private func generateRemoveIcon(_ color: UIColor) -> UIImage? { }) } -public final class EditableTokenListNodeTheme { - public let backgroundColor: UIColor - public let separatorColor: UIColor - public let placeholderTextColor: UIColor - public let primaryTextColor: UIColor - public let tokenBackgroundColor: UIColor - public let selectedTextColor: UIColor - public let selectedBackgroundColor: UIColor - public let accentColor: UIColor - public let keyboardColor: PresentationThemeKeyboardColor - - public init(backgroundColor: UIColor, separatorColor: UIColor, placeholderTextColor: UIColor, primaryTextColor: UIColor, tokenBackgroundColor: UIColor, selectedTextColor: UIColor, selectedBackgroundColor: UIColor, accentColor: UIColor, keyboardColor: PresentationThemeKeyboardColor) { - self.backgroundColor = backgroundColor - self.separatorColor = separatorColor - self.placeholderTextColor = placeholderTextColor - self.primaryTextColor = primaryTextColor - self.tokenBackgroundColor = tokenBackgroundColor - self.selectedTextColor = selectedTextColor - self.selectedBackgroundColor = selectedBackgroundColor - self.accentColor = accentColor - self.keyboardColor = keyboardColor - } -} - private final class TokenNode: ASDisplayNode { private let context: AccountContext - private let presentationTheme: PresentationTheme + private let theme: PresentationTheme - let theme: EditableTokenListNodeTheme let token: EditableTokenListToken let avatarNode: AvatarNode let categoryAvatarNode: ASImageNode @@ -98,23 +59,9 @@ private final class TokenNode: ASDisplayNode { let backgroundNode: ASImageNode let selectedBackgroundNode: ASImageNode var isSelected: Bool = false - // didSet { - // if self.isSelected != oldValue { - // self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(14.0), textColor: self.isSelected ? self.theme.selectedTextColor : self.theme.primaryTextColor) - // self.titleNode.redrawIfPossible() - // self.backgroundNode.isHidden = self.isSelected - // self.selectedBackgroundNode.isHidden = !self.isSelected - // - // self.avatarNode.isHidden = self.isSelected - // self.categoryAvatarNode.isHidden = self.isSelected - // self.removeIconNode.isHidden = !self.isSelected - // } - // } - // } - init(context: AccountContext, presentationTheme: PresentationTheme, theme: EditableTokenListNodeTheme, token: EditableTokenListToken, isSelected: Bool) { + init(context: AccountContext, theme: PresentationTheme, token: EditableTokenListToken, isSelected: Bool) { self.context = context - self.presentationTheme = presentationTheme self.theme = theme self.token = token self.titleNode = ASTextNode() @@ -131,39 +78,39 @@ private final class TokenNode: ASDisplayNode { self.removeIconNode.alpha = 0.0 self.removeIconNode.displaysAsynchronously = false self.removeIconNode.displayWithoutProcessing = true - self.removeIconNode.image = generateRemoveIcon(theme.selectedTextColor) + self.removeIconNode.image = generateRemoveIcon(theme.list.itemCheckColors.foregroundColor) - let cornerRadius: CGFloat + let cornerDiameter: CGFloat switch token.subject { case .peer: - cornerRadius = 24.0 + cornerDiameter = 28.0 case .category: - cornerRadius = 14.0 + cornerDiameter = 14.0 } self.backgroundNode = ASImageNode() self.backgroundNode.displaysAsynchronously = false self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: cornerRadius, color: theme.tokenBackgroundColor) + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: cornerDiameter, color: theme.list.itemCheckColors.strokeColor.withAlphaComponent(0.25)) self.selectedBackgroundNode = ASImageNode() self.selectedBackgroundNode.alpha = 0.0 self.selectedBackgroundNode.displaysAsynchronously = false self.selectedBackgroundNode.displayWithoutProcessing = true - self.selectedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: cornerRadius, color: theme.selectedBackgroundColor) + self.selectedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: cornerDiameter, color: theme.list.itemCheckColors.fillColor) super.init() self.addSubnode(self.backgroundNode) self.addSubnode(self.selectedBackgroundNode) - self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(14.0), textColor: self.isSelected ? self.theme.selectedTextColor : self.theme.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(15.0), textColor: self.isSelected ? self.theme.list.itemCheckColors.foregroundColor : self.theme.list.itemPrimaryTextColor) self.addSubnode(self.titleNode) self.addSubnode(self.removeIconNode) switch token.subject { case let .peer(peer): self.addSubnode(self.avatarNode) - self.avatarNode.setPeer(context: context, theme: presentationTheme, peer: peer) + self.avatarNode.setPeer(context: context, theme: theme, peer: peer) case let .category(image): self.addSubnode(self.categoryAvatarNode) self.categoryAvatarNode.image = image @@ -182,9 +129,9 @@ private final class TokenNode: ASDisplayNode { if titleSize.width.isZero { return } - self.backgroundNode.frame = self.bounds.insetBy(dx: 2.0, dy: 2.0) - self.selectedBackgroundNode.frame = self.bounds.insetBy(dx: 2.0, dy: 2.0) - self.avatarNode.frame = CGRect(origin: CGPoint(x: 3.0, y: 3.0), size: CGSize(width: 22.0, height: 22.0)) + self.backgroundNode.frame = self.bounds + self.selectedBackgroundNode.frame = self.bounds + self.avatarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.bounds.height, height: self.bounds.height)) self.categoryAvatarNode.frame = self.avatarNode.frame self.removeIconNode.frame = self.avatarNode.frame @@ -243,91 +190,70 @@ private final class TokenNode: ASDisplayNode { } } - self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(14.0), textColor: self.isSelected ? self.theme.selectedTextColor : self.theme.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(15.0), textColor: self.isSelected ? self.theme.list.itemCheckColors.foregroundColor : self.theme.list.itemPrimaryTextColor) self.titleNode.redrawIfPossible() } } -private final class CaretIndicatorNode: ASImageNode { - override func willEnterHierarchy() { - super.willEnterHierarchy() - - if self.layer.animation(forKey: "blink") == nil { - self.layer.add(caretAnimation(), forKey: "blink") - } - } -} - public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { private let context: AccountContext - private let presentationTheme: PresentationTheme + private let theme: PresentationTheme private let placeholder: String private let shortPlaceholder: String? - private let theme: EditableTokenListNodeTheme - private let backgroundNode: NavigationBackgroundNode + private let backgroundContainer: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView private let scrollNode: ASScrollNode private let placeholderNode: ASTextNode private var tokenNodes: [TokenNode] = [] - private let separatorNode: ASDisplayNode private let textFieldScrollNode: ASScrollNode private let textFieldNode: TextFieldNode - private let caretIndicatorNode: CaretIndicatorNode private var selectedTokenId: AnyHashable? public var textUpdated: ((String) -> Void)? public var deleteToken: ((AnyHashable) -> Void)? public var textReturned: (() -> Void)? - public init(context: AccountContext, presentationTheme: PresentationTheme, theme: EditableTokenListNodeTheme, placeholder: String, shortPlaceholder: String? = nil) { + public init(context: AccountContext, theme: PresentationTheme, placeholder: String, shortPlaceholder: String? = nil) { self.context = context - self.presentationTheme = presentationTheme self.theme = theme self.placeholder = placeholder self.shortPlaceholder = shortPlaceholder - self.backgroundNode = NavigationBackgroundNode(color: theme.backgroundColor) + self.backgroundContainer = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + self.backgroundContainer.contentView.addSubview(self.backgroundView) self.scrollNode = ASScrollNode() - self.scrollNode.view.alwaysBounceVertical = true + self.scrollNode.view.alwaysBounceVertical = false + self.scrollNode.clipsToBounds = true self.placeholderNode = ASTextNode() self.placeholderNode.isUserInteractionEnabled = false self.placeholderNode.maximumNumberOfLines = 1 - self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(15.0), textColor: theme.placeholderTextColor) + self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(15.0), textColor: theme.list.itemPlaceholderTextColor) self.textFieldScrollNode = ASScrollNode() self.textFieldNode = TextFieldNode() self.textFieldNode.textField.font = Font.regular(15.0) - self.textFieldNode.textField.textColor = theme.primaryTextColor + self.textFieldNode.textField.textColor = theme.list.itemPrimaryTextColor self.textFieldNode.textField.autocorrectionType = .no self.textFieldNode.textField.returnKeyType = .done - self.textFieldNode.textField.keyboardAppearance = theme.keyboardColor.keyboardAppearance - self.textFieldNode.textField.tintColor = theme.accentColor - - self.caretIndicatorNode = CaretIndicatorNode() - self.caretIndicatorNode.isLayerBacked = true - self.caretIndicatorNode.displayWithoutProcessing = true - self.caretIndicatorNode.displaysAsynchronously = false - self.caretIndicatorNode.image = caretIndicatorImage - - self.separatorNode = ASDisplayNode() - self.separatorNode.isLayerBacked = true - self.separatorNode.backgroundColor = theme.separatorColor + self.textFieldNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance + self.textFieldNode.textField.tintColor = theme.list.itemAccentColor super.init() - self.addSubnode(self.backgroundNode) + + self.view.addSubview(self.backgroundContainer) + self.addSubnode(self.scrollNode) - self.addSubnode(self.separatorNode) self.scrollNode.addSubnode(self.placeholderNode) self.scrollNode.addSubnode(self.textFieldScrollNode) self.textFieldScrollNode.addSubnode(self.textFieldNode) - //self.scrollNode.addSubnode(self.caretIndicatorNode) - self.clipsToBounds = true self.textFieldNode.textField.delegate = self self.textFieldNode.textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) @@ -357,7 +283,7 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { placeholderSnapshot = self.placeholderNode.layer.snapshotContentTreeAsView() placeholderSnapshot?.frame = self.placeholderNode.frame } - self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(15.0), textColor: self.theme.placeholderTextColor) + self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(15.0), textColor: self.theme.list.itemPlaceholderTextColor) } for i in (0 ..< self.tokenNodes.count).reversed() { @@ -380,8 +306,7 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { } let sideInset: CGFloat = 12.0 + leftInset - let verticalInset: CGFloat = 6.0 - + let verticalInset: CGFloat = 8.0 var animationDelay = 0.0 var currentOffset = CGPoint(x: sideInset, y: verticalInset) @@ -398,7 +323,7 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { if let currentNode = currentNode { tokenNode = currentNode } else { - tokenNode = TokenNode(context: self.context, presentationTheme: self.presentationTheme, theme: self.theme, token: token, isSelected: self.selectedTokenId != nil && token.id == self.selectedTokenId!) + tokenNode = TokenNode(context: self.context, theme: self.theme, token: token, isSelected: self.selectedTokenId != nil && token.id == self.selectedTokenId!) self.tokenNodes.append(tokenNode) self.scrollNode.addSubnode(tokenNode) animateIn = true @@ -407,10 +332,10 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { let tokenSize = tokenNode.measure(CGSize(width: max(1.0, width - sideInset - sideInset), height: CGFloat.greatestFiniteMagnitude)) if tokenSize.width + currentOffset.x >= width - sideInset && !currentOffset.x.isEqual(to: sideInset) { currentOffset.x = sideInset - currentOffset.y += tokenSize.height + currentOffset.y += tokenSize.height + 6.0 } let tokenFrame = CGRect(origin: CGPoint(x: currentOffset.x, y: currentOffset.y), size: tokenSize) - currentOffset.x += ceil(tokenSize.width) + currentOffset.x += ceil(tokenSize.width) + 6.0 if animateIn { tokenNode.frame = tokenFrame @@ -451,7 +376,7 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { let placeholderSize = self.placeholderNode.measure(CGSize(width: max(1.0, width - sideInset - sideInset), height: CGFloat.greatestFiniteMagnitude)) if width - currentOffset.x < placeholderSize.width { - currentOffset.y += 28.0 + currentOffset.y += 28.0 + 6.0 currentOffset.x = sideInset } @@ -472,42 +397,45 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { } let textNodeFrame = CGRect(origin: CGPoint(x: currentOffset.x + 4.0, y: currentOffset.y + UIScreenPixel), size: CGSize(width: width - currentOffset.x - sideInset - 8.0, height: 28.0)) - let caretNodeFrame = CGRect(origin: CGPoint(x: textNodeFrame.minX, y: textNodeFrame.minY + 4.0 - UIScreenPixel), size: CGSize(width: 2.0, height: 19.0 + UIScreenPixel)) if case .immediate = transition { transition.updateFrame(node: self.textFieldScrollNode, frame: textNodeFrame) transition.updateFrame(node: self.textFieldNode, frame: CGRect(origin: CGPoint(), size: textNodeFrame.size)) - transition.updateFrame(node: self.caretIndicatorNode, frame: caretNodeFrame) } else { let previousFrame = self.textFieldScrollNode.frame self.textFieldScrollNode.frame = textNodeFrame self.textFieldScrollNode.layer.animateFrame(from: previousFrame, to: textNodeFrame, duration: 0.2 + animationDelay, timingFunction: kCAMediaTimingFunctionSpring) transition.updateFrame(node: self.textFieldNode, frame: CGRect(origin: CGPoint(), size: textNodeFrame.size)) - - let previousCaretFrame = self.caretIndicatorNode.frame - self.caretIndicatorNode.frame = caretNodeFrame - self.caretIndicatorNode.layer.animateFrame(from: previousCaretFrame, to: caretNodeFrame, duration: 0.2 + animationDelay, timingFunction: kCAMediaTimingFunctionSpring) } let previousContentHeight = self.scrollNode.view.contentSize.height - let contentHeight = currentOffset.y + 29.0 + verticalInset + let contentHeight = currentOffset.y + 28.0 + verticalInset let nodeHeight = min(contentHeight, 110.0) - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: nodeHeight))) + transition.updateCornerRadius(node: self.scrollNode, cornerRadius: min(44.0, nodeHeight) * 0.5) + self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 16.0, left: 0.0, bottom: 16.0, right: 0.0) if !abs(previousContentHeight - contentHeight).isLess(than: CGFloat.ulpOfOne) { let contentOffset = CGPoint(x: 0.0, y: max(0.0, contentHeight - nodeHeight)) - if case .immediate = transition { - self.scrollNode.view.contentOffset = contentOffset - } else { - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: contentOffset.y), size: self.scrollNode.bounds.size)) + if self.scrollNode.view.contentOffset != contentOffset { + if case .immediate = transition { + self.scrollNode.view.contentOffset = contentOffset + } else { + //transition.animateOffsetAdditive(node: self.scrollNode, offset: self.scrollNode.view.contentOffset.y - contentOffset.y) + } } } - self.scrollNode.view.contentSize = CGSize(width: width, height: contentHeight) + if self.scrollNode.view.contentSize != CGSize(width: width, height: contentHeight) { + self.scrollNode.view.contentSize = CGSize(width: width, height: contentHeight) + } - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: nodeHeight))) - self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition) + let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: nodeHeight)) + self.backgroundContainer.update(size: backgroundFrame.size, isDark: self.theme.overallDarkAppearance, transition: ComponentTransition(transition)) + transition.updateFrame(view: self.backgroundContainer, frame: backgroundFrame) + + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: min(44.0, backgroundFrame.height) * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: ComponentTransition(transition)) + transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) return nodeHeight } @@ -528,15 +456,9 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { } public func textFieldDidBeginEditing(_ textField: UITextField) { - /*if self.caretIndicatorNode.supernode == self { - self.caretIndicatorNode.removeFromSupernode() - }*/ } public func textFieldDidEndEditing(_ textField: UITextField) { - /*if self.caretIndicatorNode.supernode != self.scrollNode { - self.scrollNode.addSubnode(self.caretIndicatorNode) - }*/ } public func setText(_ text: String) { diff --git a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD index 15cf263a..09c44b10 100644 --- a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD +++ b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD @@ -21,8 +21,9 @@ swift_library( "//submodules/Components/MultilineTextComponent", "//submodules/Components/BalancedTextComponent", "//submodules/Components/ComponentDisplayAdapters", - "//submodules/TelegramUI/Components/TextFieldComponent", "//submodules/TextFormat", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift index 0df9fdc4..fb4c4a6c 100644 --- a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift @@ -13,399 +13,109 @@ import BalancedTextComponent import TextFieldComponent import ComponentDisplayAdapters import TextFormat +import ComponentFlow +import AlertComponent +import AlertMultilineInputFieldComponent -private final class FactCheckAlertContentNode: AlertContentNode { - private let context: AccountContext - private var theme: AlertControllerTheme - private var presentationTheme: PresentationTheme - private let strings: PresentationStrings - private let text: String - private let initialValue: String - - private let titleView = ComponentView() +public func factCheckAlertController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + value: String, + entities: [MessageTextEntity], + apply: @escaping (String, [MessageTextEntity]) -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + let inputState = AlertMultilineInputFieldComponent.ExternalState() - private let state = ComponentState() - - private let inputBackgroundNode = ASImageNode() - private let inputField = ComponentView() - private let inputFieldExternalState = TextFieldComponent.ExternalState() - private let inputPlaceholderView = ComponentView() - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let disposable = MetaDisposable() - - private var validLayout: CGSize? - - private let hapticFeedback = HapticFeedback() - - var present: (ViewController) -> () = { _ in } - - var complete: (() -> Void)? { - didSet { -// self.inputFieldNode.complete = self.complete + let doneIsEnabled: Signal + if !value.isEmpty { + doneIsEnabled = .single(true) + } else { + doneIsEnabled = inputState.valueSignal + |> map { value in + return !value.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } } - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled + var characterLimit: Int = 1024 + if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["factcheck_length_limit"] as? Double { + characterLimit = Int(value) } - init(context: AccountContext, theme: AlertControllerTheme, presentationTheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, value: String, entities: [MessageTextEntity]) { - self.context = context - self.theme = theme - self.presentationTheme = presentationTheme - self.strings = strings - self.text = text - self.initialValue = value - - if !value.isEmpty { - self.inputFieldExternalState.initialText = chatInputStateStringWithAppliedEntities(value, entities: entities) - } - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.inputBackgroundNode.displaysAsynchronously = false - self.inputBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: presentationTheme.actionSheet.inputHollowBackgroundColor, strokeColor: presentationTheme.actionSheet.inputBorderColor, strokeWidth: UIScreenPixel) - - self.addSubnode(self.actionNodesSeparator) - self.addSubnode(self.inputBackgroundNode) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - self.actionNodes.last?.actionEnabled = true - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - - self.state._updated = { [weak self] transition, _ in - guard let self, let _ = self.validLayout else { - return - } - self.requestLayout?(transition.containedViewLayoutTransition) - } - } + let initialValue = chatInputStateStringWithAppliedEntities(value, entities: entities) - deinit { - self.disposable.dispose() - } - - var textAndEntities: (String, [MessageTextEntity]) { - let text = self.inputFieldExternalState.text.string - let entities = generateChatInputTextEntities(self.inputFieldExternalState.text) - return (text, entities) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.theme = theme - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) - - let hadValidLayout = self.validLayout != nil - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 16.0) - let spacing: CGFloat = 5.0 - - - let titleSize = self.titleView.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: self.text, font: Font.semibold(17.0), textColor: self.theme.primaryColor)), - horizontalAlignment: .center, - maximumNumberOfLines: 0 - )), - environment: {}, - containerSize: CGSize(width: measureSize.width, height: 1000.0) + var presentImpl: ((ViewController) -> Void)? + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.FactCheck_Title) ) - let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) * 0.5), y: origin.y), size: titleSize) - if let titleComponentView = self.titleView.view { - if titleComponentView.superview == nil { - self.view.addSubview(titleComponentView) - } - titleComponentView.frame = titleFrame - } - origin.y += titleSize.height + 17.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) - - var contentWidth = max(titleSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - let inputInset: CGFloat = 16.0 - let inputWidth = resultWidth - inputInset * 2.0 - - var characterLimit: Int = 1024 - if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["factcheck_length_limit"] as? Double { - characterLimit = Int(value) - } - - let inputFieldSize = self.inputField.update( - transition: .immediate, - component: AnyComponent(TextFieldComponent( - context: self.context, - theme: self.presentationTheme, - strings: self.strings, - externalState: self.inputFieldExternalState, - fontSize: 14.0, - textColor: self.presentationTheme.actionSheet.inputTextColor, - accentColor: self.presentationTheme.actionSheet.controlAccentColor, - insets: UIEdgeInsets(top: 8.0, left: 2.0, bottom: 8.0, right: 2.0), - hideKeyboard: false, - customInputView: nil, - resetText: nil, - isOneLineWhenUnfocused: false, + )) + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertMultilineInputFieldComponent( + context: context, + initialValue: initialValue, + placeholder: strings.FactCheck_Placeholder, characterLimit: characterLimit, + formatMenuAvailability: .available([.bold, .italic]), emptyLineHandling: .oneConsecutive, - formatMenuAvailability: .available([.bold, .italic, .link]), - returnKeyType: .default, - lockedFormatAction: { - }, - present: { [weak self] c in - self?.present(c) - }, - paste: { _ in - }, - returnKeyAction: nil, - backspaceKeyAction: nil - )), - environment: {}, - containerSize: CGSize(width: inputWidth, height: 270.0) - ) - self.inputField.parentState = self.state - let inputFieldFrame = CGRect(origin: CGPoint(x: inputInset, y: origin.y), size: inputFieldSize) - if let inputFieldView = self.inputField.view as? TextFieldComponent.View { - if inputFieldView.superview == nil { - self.view.addSubview(inputFieldView) - } - transition.updateFrame(view: inputFieldView, frame: inputFieldFrame) - transition.updateFrame(node: self.inputBackgroundNode, frame: inputFieldFrame) - - if !hadValidLayout { - inputFieldView.activateInput() - } - } - - let inputPlaceholderSize = self.inputPlaceholderView.update( - transition: .immediate, - component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString( - string: self.strings.FactCheck_Placeholder, - font: Font.regular(14.0), - textColor: self.presentationTheme.actionSheet.inputPlaceholderColor - ))) - ), - environment: {}, - containerSize: CGSize(width: inputWidth, height: 240.0) - ) - let inputPlaceholderFrame = CGRect(origin: CGPoint(x: inputInset + 10.0, y: floorToScreenPixels(inputFieldFrame.midY - inputPlaceholderSize.height / 2.0)), size: inputPlaceholderSize) - if let inputPlaceholderView = self.inputPlaceholderView.view { - if inputPlaceholderView.superview == nil { - inputPlaceholderView.isUserInteractionEnabled = false - self.view.addSubview(inputPlaceholderView) - } - inputPlaceholderView.frame = inputPlaceholderFrame - inputPlaceholderView.isHidden = self.inputFieldExternalState.hasText - } - - if let lastActionNode = self.actionNodes.last { - if self.initialValue.isEmpty { - lastActionNode.actionEnabled = self.inputFieldExternalState.hasText - } else { - if self.inputFieldExternalState.hasText { - lastActionNode.action = TextAlertAction( - type: .defaultAction, - title: self.strings.Common_Done, - action: lastActionNode.action.action - ) - } else { - lastActionNode.action = TextAlertAction( - type: .defaultDestructiveAction, - title: self.strings.FactCheck_Remove, - action: lastActionNode.action.action - ) + isInitiallyFocused: true, + externalState: inputState, + present: { c in + presentImpl?(c) } - } + ) + ) + )) + + let doneIsRemove: Signal + if !value.isEmpty { + doneIsRemove = inputState.valueSignal + |> map { value in + return value.string.isEmpty } - - let resultSize = CGSize(width: resultWidth, height: titleSize.height + spacing + inputFieldSize.height + 17.0 + actionsHeight + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize + |> distinctUntilChanged + } else { + doneIsRemove = .single(false) } - func deactivateInput() { - if let inputFieldView = self.inputField.view as? TextFieldComponent.View { - inputFieldView.deactivateInput() - } + let actionsSignal: Signal<[AlertScreen.Action], NoError> = doneIsRemove + |> map { doneIsRemove in + var actions: [AlertScreen.Action] = [] + actions.append(.init(title: strings.Common_Cancel)) + + let doneTitle: String = doneIsRemove ? strings.FactCheck_Remove : strings.Common_Done + let doneType: AlertScreen.Action.ActionType = doneIsRemove ? .defaultDestructive : .default + actions.append( + .init(id: "done", title: doneTitle, type: doneType, action: { + let (text, entities) = inputState.textAndEntities + apply(text, entities) + }, isEnabled: doneIsEnabled) + ) + + return actions } - func animateError() { - if let inputFieldView = self.inputField.view as? TextFieldComponent.View { - inputFieldView.layer.addShakeAnimation() - } + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) + } - self.hapticFeedback.error() + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + contentSignal: .single(content), + actionsSignal: actionsSignal, + updatedPresentationData: effectiveUpdatedPresentationData + ) + presentImpl = { [weak alertController] c in + alertController?.present(c, in: .window(.root)) } -} - -public func factCheckAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, value: String, entities: [MessageTextEntity], apply: @escaping (String, [MessageTextEntity]) -> Void) -> AlertController { - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - - var dismissImpl: ((Bool) -> Void)? - var applyImpl: (() -> Void)? - - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: { - dismissImpl?(true) - applyImpl?() - })] - - let contentNode = FactCheckAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), presentationTheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: presentationData.strings.FactCheck_Title, value: value, entities: entities) - contentNode.complete = { - applyImpl?() - } - applyImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - let (text, entities) = contentNode.textAndEntities - apply(text, entities) - } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - } - dismissImpl = { [weak controller] animated in - contentNode.deactivateInput() - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - - contentNode.present = { [weak controller] c in - controller?.present(c, in: .window(.root)) - } - - return controller + return alertController } diff --git a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD index 0faa5484..97ddeb8a 100644 --- a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD @@ -29,6 +29,8 @@ swift_library( "//submodules/TelegramUI/Components/AnimationCache", "//submodules/TelegramUI/Components/MultiAnimationRenderer", "//submodules/TelegramUI/Components/Chat/AccessoryPanelNode", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/AlertComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift index a63706f2..21ed1416 100644 --- a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift @@ -20,6 +20,8 @@ import AnimationCache import MultiAnimationRenderer import AccessoryPanelNode import AppBundle +import ComponentFlow +import AlertComponent func textStringForForwardedMessage(_ message: Message, strings: PresentationStrings) -> (text: String, entities: [MessageTextEntity], isMedia: Bool) { for media in message.media { @@ -378,26 +380,48 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode { string = self.strings.Conversation_ForwardOptions_Text(messages, peerDisplayTitle) } - let font = Font.regular(floor(self.fontSize.baseDisplaySize * 15.0 / 17.0)) - let boldFont = Font.semibold(floor(self.fontSize.baseDisplaySize * 15.0 / 17.0)) - let body = MarkdownAttributeSet(font: font, textColor: self.theme.actionSheet.secondaryTextColor) - let bold = MarkdownAttributeSet(font: boldFont, textColor: self.theme.actionSheet.secondaryTextColor) + let font = Font.regular(15.0) + let boldFont = Font.semibold(15.0) + let body = MarkdownAttributeSet(font: font, textColor: self.theme.actionSheet.primaryTextColor) + let bold = MarkdownAttributeSet(font: boldFont, textColor: self.theme.actionSheet.primaryTextColor) + let text = addAttributesToStringWithRanges(string._tuple, body: body, argumentAttributes: [0: bold, 1: bold], textAlignment: .natural) - let title = NSAttributedString(string: self.strings.Conversation_ForwardOptions_Title(messageCount), font: Font.semibold(floor(self.fontSize.baseDisplaySize)), textColor: self.theme.actionSheet.primaryTextColor, paragraphAlignment: .center) - let text = addAttributesToStringWithRanges(string._tuple, body: body, argumentAttributes: [0: bold, 1: bold], textAlignment: .center) + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent( + title: strings.Conversation_ForwardOptions_Title(messageCount) + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .attributed(text)) + ) + )) - let alertController = richTextAlertController(context: self.context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: self.strings.Conversation_ForwardOptions_ShowOptions, action: { [weak self] in - if let strongSelf = self { - strongSelf.interfaceInteraction?.presentForwardOptions(strongSelf.view) - Queue.mainQueue().after(0.5) { - strongSelf.updateThemeAndStrings(theme: strongSelf.theme, strings: strongSelf.strings, forwardOptionsState: strongSelf.forwardOptionsState, force: true) - } - - let _ = ApplicationSpecificNotice.incrementChatForwardOptionsTip(accountManager: strongSelf.context.sharedContext.accountManager, count: 3).start() - } - }), TextAlertAction(type: .destructiveAction, title: self.strings.Conversation_ForwardOptions_CancelForwarding, action: { [weak self] in - self?.dismiss?() - })], actionLayout: .vertical) + let alertController = AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + content: content, + actions: [ + .init(title: strings.Conversation_ForwardOptions_ShowOptions, action: { [weak self] in + guard let self else { + return + } + self.interfaceInteraction?.presentForwardOptions(self.view) + Queue.mainQueue().after(0.5) { + self.updateThemeAndStrings(theme: self.theme, strings: self.strings, forwardOptionsState: self.forwardOptionsState, force: true) + } + let _ = ApplicationSpecificNotice.incrementChatForwardOptionsTip(accountManager: self.context.sharedContext.accountManager, count: 3).start() + }), + .init(title: strings.Conversation_ForwardOptions_CancelForwarding, type: .destructive, action: { [weak self] in + self?.dismiss?() + }) + ] + ) self.interfaceInteraction?.presentController(alertController, nil) } diff --git a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift index 7f38fa1e..d6e4efc8 100644 --- a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift +++ b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift @@ -100,7 +100,7 @@ public struct InteractiveEmojiConfiguration { } public static func with(appConfiguration: AppConfiguration) -> InteractiveEmojiConfiguration { - if let data = appConfiguration.data, let emojis = data["emojies_send_dice"] as? [String] { + if let data = appConfiguration.data, var emojis = data["emojies_send_dice"] as? [String] { var successParameters: [String: InteractiveEmojiSuccessParameters] = [:] if let success = data["emojies_send_dice_success"] as? [String: [String: Double]] { for (key, dict) in success { @@ -109,6 +109,11 @@ public struct InteractiveEmojiConfiguration { } } } + #if DEBUG + if !emojis.contains("๐ŸŽฒ") { + emojis.append("๐ŸŽฒ") + } + #endif return InteractiveEmojiConfiguration(emojis: emojis, successParameters: successParameters) } else { return .defaultValue @@ -126,6 +131,10 @@ public final class ManagedDiceAnimationNode: ManagedAnimationNode { private let configuration = Promise() private let emojis = Promise<[TelegramMediaFile]>() + public var isRolling: Bool { + return self.diceState == .rolling + } + public var success: (() -> Void)? public init(context: AccountContext, emoji: String) { diff --git a/submodules/TelegramUI/Components/Chat/ShimmeringLinkNode/Sources/ShimmeringLinkNode.swift b/submodules/TelegramUI/Components/Chat/ShimmeringLinkNode/Sources/ShimmeringLinkNode.swift index c70a1c79..efdc5c13 100644 --- a/submodules/TelegramUI/Components/Chat/ShimmeringLinkNode/Sources/ShimmeringLinkNode.swift +++ b/submodules/TelegramUI/Components/Chat/ShimmeringLinkNode/Sources/ShimmeringLinkNode.swift @@ -93,7 +93,7 @@ public final class ShimmeringLinkNode: ASDisplayNode { self.shimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: size), within: size) self.borderShimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: size), within: size) - self.shimmerEffectNode.update(backgroundColor: .clear, foregroundColor: self.color.withAlphaComponent(min(1.0, self.color.alpha * 1.2)), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil) - self.borderShimmerEffectNode.update(backgroundColor: .clear, foregroundColor: self.color.withAlphaComponent(min(1.0, self.color.alpha * 1.5)), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil) + self.shimmerEffectNode.update(backgroundColor: .clear, foregroundColor: self.color.withMultipliedAlpha(1.75), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil) + self.borderShimmerEffectNode.update(backgroundColor: .clear, foregroundColor: self.color.withMultipliedAlpha(2.0), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil) } } diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 68555594..894a9d0f 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -313,6 +313,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public var enableFullTranslucency: Bool = true public var chatIsRotated: Bool = true public var canReadHistory: Bool = false + public var summarizedMessageIds: Set = Set() + private var isOpeningMediaValue: Bool = false public var isOpeningMedia: Bool { diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 06933901..efac3288 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -33,6 +33,8 @@ import LegacyMessageInputPanelInputView import AttachmentTextInputPanelNode import GlassBackgroundComponent +private let keyboardCornerRadius: CGFloat = 30.0 + public final class EmptyInputView: UIView, UIInputViewAudioFeedback { public var enableInputClicksWhenVisible: Bool { return true @@ -490,7 +492,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { self.clippingView = UIView() self.clippingView.clipsToBounds = true - self.clippingView.layer.cornerRadius = 20.0 + self.clippingView.layer.cornerRadius = keyboardCornerRadius self.entityKeyboardView = ComponentHostView() @@ -1904,8 +1906,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { theme: interfaceState.theme, strings: interfaceState.strings, isContentInFocus: isVisible, - containerInsets: UIEdgeInsets(top: self.isEmojiSearchActive ? -34.0 : 0.0, left: leftInset, bottom: keyboardBottomInset, right: rightInset), - topPanelInsets: UIEdgeInsets(), + containerInsets: UIEdgeInsets(top: self.isEmojiSearchActive ? -42.0 : 0.0, left: leftInset, bottom: keyboardBottomInset, right: rightInset), + topPanelInsets: UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 5.0), emojiContent: emojiContent, stickerContent: stickerContent, maskContent: nil, @@ -2030,16 +2032,16 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { backgroundFrame.size.height += 32.0 if backgroundChromeView.image == nil { - backgroundChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 20.0 * 2.0, height: 20.0 * 2.0), isDark: interfaceState.theme.overallDarkAppearance, fillColor: .clear) + backgroundChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: keyboardCornerRadius * 2.0, height: keyboardCornerRadius * 2.0), isDark: interfaceState.theme.overallDarkAppearance, fillColor: .clear) } if backgroundTintView.image == nil { - backgroundTintView.image = generateStretchableFilledCircleImage(diameter: 20.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate) + backgroundTintView.image = generateStretchableFilledCircleImage(diameter: keyboardCornerRadius * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate) } backgroundTintView.tintColor = interfaceState.theme.chat.inputMediaPanel.backgroundColor transition.updateFrame(view: backgroundView, frame: backgroundFrame) backgroundView.updateColor(color: .clear, forceKeepBlur: true, transition: .immediate) - backgroundView.update(size: backgroundFrame.size, cornerRadius: 20.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition) + backgroundView.update(size: backgroundFrame.size, cornerRadius: keyboardCornerRadius, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition) transition.updateFrame(view: backgroundChromeView, frame: backgroundFrame.insetBy(dx: -1.0, dy: 0.0)) @@ -2666,7 +2668,6 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift index 7b623f34..201a5677 100644 --- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift +++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift @@ -1269,7 +1269,10 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { if let controller = environment.controller() { let subLayout = ContainerViewLayout( - size: availableSize, metrics: environment.metrics, deviceMetrics: environment.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: sideInset - 12.0, bottom: bottomPanelHeight, right: sideInset), + size: availableSize, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + intrinsicInsets: UIEdgeInsets(top: 0.0, left: sideInset - 12.0, bottom: bottomPanelHeight, right: sideInset), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, @@ -1513,7 +1516,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { case .someUserTooManyChannels: text = presentationData.strings.ChatListFilter_CreateLinkErrorSomeoneHasChannelLimit } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + controller.present(textAlertController(context: component.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }) } }) diff --git a/submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode/BUILD b/submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode/BUILD new file mode 100644 index 00000000..d5a45624 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode/BUILD @@ -0,0 +1,27 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatListFilterTabContainerNode", + module_name = "ChatListFilterTabContainerNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/AccountContext", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/LiquidLens", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode/Sources/ChatListFilterTabContainerNode.swift b/submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode/Sources/ChatListFilterTabContainerNode.swift new file mode 100644 index 00000000..b393b8da --- /dev/null +++ b/submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode/Sources/ChatListFilterTabContainerNode.swift @@ -0,0 +1,1127 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramCore +import TelegramPresentationData +import TextNodeWithEntities +import AccountContext +import GlassBackgroundComponent +import ComponentFlow +import ComponentDisplayAdapters + +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 context: AccountContext + private let pressed: (Bool) -> Void + private let requestedDeletion: () -> Void + + private let extractedContainerNode: ContextExtractedContentContainingNode + private let containerNode: ContextControllerSourceNode + + private let extractedBackgroundNode: ASImageNode + private let titleContainer: ASDisplayNode + private let titleNode: ImmediateTextNodeWithEntities + private let titleActiveNode: ImmediateTextNodeWithEntities + private let shortTitleContainer: ASDisplayNode + private let shortTitleNode: ImmediateTextNodeWithEntities + private let shortTitleActiveNode: ImmediateTextNodeWithEntities + private let badgeContainerNode: ASDisplayNode + private let badgeTextNode: ImmediateTextNode + private let badgeBackgroundActiveNode: ASImageNode + private let badgeBackgroundInactiveNode: ASImageNode + + private var deleteButtonNode: ItemNodeDeleteButtonNode? + private let buttonNode: HighlightTrackingButtonNode + + private var selectionFraction: CGFloat = 0.0 + private(set) var unreadCount: Int = 0 + + private var isReordering: Bool = false + private var isEditing: Bool = false + private var isDisabled: Bool = false + + private var theme: PresentationTheme? + private var currentTitle: (ChatFolderTitle, ChatFolderTitle)? + + private var pointerInteraction: PointerInteraction? + + init(context: AccountContext, pressed: @escaping (Bool) -> Void, requestedDeletion: @escaping () -> Void, contextGesture: @escaping (ContextExtractedContentContainingNode, ContextGesture, Bool) -> Void) { + self.context = context + self.pressed = pressed + self.requestedDeletion = requestedDeletion + + self.extractedContainerNode = ContextExtractedContentContainingNode() + self.containerNode = ContextControllerSourceNode() + + self.extractedBackgroundNode = ASImageNode() + self.extractedBackgroundNode.alpha = 0.0 + + let titleInset: CGFloat = 4.0 + + self.titleContainer = ASDisplayNode() + + self.titleNode = ImmediateTextNodeWithEntities() + self.titleNode.displaysAsynchronously = false + self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) + self.titleNode.resetEmojiToFirstFrameAutomatically = true + + self.titleActiveNode = ImmediateTextNodeWithEntities() + self.titleActiveNode.displaysAsynchronously = false + self.titleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) + self.titleActiveNode.alpha = 0.0 + self.titleActiveNode.resetEmojiToFirstFrameAutomatically = true + + self.shortTitleContainer = ASDisplayNode() + + self.shortTitleNode = ImmediateTextNodeWithEntities() + self.shortTitleNode.displaysAsynchronously = false + self.shortTitleNode.alpha = 0.0 + self.shortTitleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) + self.shortTitleNode.resetEmojiToFirstFrameAutomatically = true + + self.shortTitleActiveNode = ImmediateTextNodeWithEntities() + self.shortTitleActiveNode.displaysAsynchronously = false + self.shortTitleActiveNode.alpha = 0.0 + self.shortTitleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) + self.shortTitleActiveNode.alpha = 0.0 + self.shortTitleActiveNode.resetEmojiToFirstFrameAutomatically = true + + self.badgeContainerNode = ASDisplayNode() + + self.badgeTextNode = ImmediateTextNode() + self.badgeTextNode.displaysAsynchronously = false + + self.badgeBackgroundActiveNode = ASImageNode() + self.badgeBackgroundActiveNode.displaysAsynchronously = false + self.badgeBackgroundActiveNode.displayWithoutProcessing = true + + self.badgeBackgroundInactiveNode = ASImageNode() + self.badgeBackgroundInactiveNode.displaysAsynchronously = false + self.badgeBackgroundInactiveNode.displayWithoutProcessing = true + + self.buttonNode = HighlightTrackingButtonNode() + + super.init() + + self.extractedContainerNode.contentNode.addSubnode(self.extractedBackgroundNode) + self.extractedContainerNode.contentNode.addSubnode(self.titleContainer) + self.titleContainer.addSubnode(self.titleNode) + self.titleContainer.addSubnode(self.titleActiveNode) + self.extractedContainerNode.contentNode.addSubnode(self.shortTitleContainer) + self.shortTitleContainer.addSubnode(self.shortTitleNode) + self.shortTitleContainer.addSubnode(self.shortTitleActiveNode) + self.badgeContainerNode.addSubnode(self.badgeBackgroundInactiveNode) + self.badgeContainerNode.addSubnode(self.badgeBackgroundActiveNode) + self.badgeContainerNode.addSubnode(self.badgeTextNode) + self.extractedContainerNode.contentNode.addSubnode(self.badgeContainerNode) + self.extractedContainerNode.contentNode.addSubnode(self.buttonNode) + + self.containerNode.addSubnode(self.extractedContainerNode) + self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode + self.addSubnode(self.containerNode) + + self.buttonNode.isExclusiveTouch = true + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self else { + return + } + contextGesture(strongSelf.extractedContainerNode, gesture, strongSelf.isDisabled) + } + + self.extractedContainerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in + guard let strongSelf = self else { + return + } + + 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 + } + }) + } + } + + override func didLoad() { + super.didLoad() + + self.pointerInteraction = PointerInteraction(view: self.containerNode.view, customInteractionView: nil, style: .insetRectangle(-10.0, 4.0)) + } + + @objc private func buttonPressed() { + self.pressed(self.isDisabled) + } + + func updateText(strings: PresentationStrings, title: ChatFolderTitle, shortTitle: ChatFolderTitle, unreadCount: Int, unreadHasUnmuted: Bool, isNoFilter: Bool, selectionFraction: CGFloat, isEditing: Bool, isReordering: Bool, canReorderAllChats: Bool, isDisabled: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { + self.isEditing = isEditing + self.isDisabled = isDisabled + + var themeUpdated = false + if self.theme !== presentationData.theme { + self.theme = presentationData.theme + + self.badgeBackgroundActiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.list.itemCheckColors.fillColor) + self.badgeBackgroundInactiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeInactiveBackgroundColor) + + themeUpdated = true + } + + var titleUpdated = false + if self.currentTitle?.0 != title || self.currentTitle?.1 != shortTitle { + self.currentTitle = (title, shortTitle) + + titleUpdated = true + } + + var unreadCountUpdated = false + if self.unreadCount != unreadCount { + unreadCountUpdated = true + self.unreadCount = unreadCount + } + + self.buttonNode.accessibilityLabel = title.text + if unreadCount > 0 { + if self.buttonNode.accessibilityValue == nil || unreadCountUpdated { + self.buttonNode.accessibilityValue = strings.VoiceOver_Chat_UnreadMessages(Int32(unreadCount)) + } + } else { + self.buttonNode.accessibilityValue = "" + } + if selectionFraction == 1.0 { + self.buttonNode.accessibilityTraits = [.button, .selected] + } else { + self.buttonNode.accessibilityTraits = [.button] + } + + self.containerNode.isGestureEnabled = !isEditing && !isReordering + self.buttonNode.isUserInteractionEnabled = !isEditing && !isReordering + + self.selectionFraction = selectionFraction + self.unreadCount = unreadCount + + if isReordering && !isNoFilter { + 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: (isEditing || isDisabled || isReordering || unreadCount == 0) ? 0.0 : 1.0) + + let selectionAlpha: CGFloat = selectionFraction * selectionFraction + let deselectionAlpha: CGFloat = isDisabled ? 0.5 : 1.0// - selectionFraction + + transition.updateAlpha(node: self.titleNode, alpha: deselectionAlpha) + transition.updateAlpha(node: self.titleActiveNode, alpha: selectionAlpha) + transition.updateAlpha(node: self.shortTitleNode, alpha: deselectionAlpha) + transition.updateAlpha(node: self.shortTitleActiveNode, alpha: selectionAlpha) + + let titleArguments = TextNodeWithEntities.Arguments( + context: self.context, + cache: self.context.animationCache, + renderer: self.context.animationRenderer, + placeholderColor: presentationData.theme.list.mediaPlaceholderColor, + attemptSynchronous: false + ) + + self.titleNode.arguments = titleArguments + self.titleActiveNode.arguments = titleArguments + self.shortTitleNode.arguments = titleArguments + self.shortTitleActiveNode.arguments = titleArguments + + self.titleNode.visibility = title.enableAnimations + self.titleActiveNode.visibility = title.enableAnimations + self.shortTitleNode.visibility = title.enableAnimations + self.shortTitleActiveNode.visibility = title.enableAnimations + + if themeUpdated || titleUpdated { + self.titleNode.attributedText = title.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.chat.inputPanel.panelControlColor) + self.titleActiveNode.attributedText = title.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.chat.inputPanel.panelControlColor) + + self.shortTitleNode.attributedText = shortTitle.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.chat.inputPanel.panelControlColor) + self.shortTitleActiveNode.attributedText = shortTitle.attributedString(font: Font.medium(14.0), textColor: presentationData.theme.chat.inputPanel.panelControlColor) + } + + if unreadCount != 0 { + if themeUpdated || unreadCountUpdated || self.badgeTextNode.attributedText == nil { + self.badgeTextNode.attributedText = NSAttributedString(string: "\(unreadCount)", font: Font.regular(14.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) + } + + let badgeSelectionFraction: CGFloat = unreadHasUnmuted ? 1.0 : selectionFraction + let badgeSelectionAlpha: CGFloat = badgeSelectionFraction + //let badgeDeselectionAlpha: CGFloat = 1.0 - badgeSelectionFraction + + transition.updateAlpha(node: self.badgeBackgroundActiveNode, alpha: badgeSelectionAlpha * badgeSelectionAlpha) + //transition.updateAlpha(node: self.badgeBackgroundInactiveNode, alpha: badgeDeselectionAlpha) + self.badgeBackgroundInactiveNode.alpha = 1.0 + } + + if self.isReordering != isReordering { + self.isReordering = isReordering + if self.isReordering { + self.layer.addReorderingShaking() + } else { + self.layer.removeAnimation(forKey: "shaking_position") + self.layer.removeAnimation(forKey: "shaking_rotation") + } + } + } + + func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> (width: CGFloat, shortWidth: CGFloat) { + let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) + let _ = self.titleActiveNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) + let titleFrame = CGRect(origin: CGPoint(x: -self.titleNode.insets.left, y: floor((height - titleSize.height) / 2.0)), size: titleSize) + self.titleContainer.frame = titleFrame + self.titleNode.frame = CGRect(origin: CGPoint(), size: titleFrame.size) + self.titleActiveNode.frame = CGRect(origin: CGPoint(), size: titleFrame.size) + + let shortTitleSize = self.shortTitleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) + let _ = self.shortTitleActiveNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) + let shortTitleFrame = CGRect(origin: CGPoint(x: -self.shortTitleNode.insets.left, y: floor((height - shortTitleSize.height) / 2.0)), size: shortTitleSize) + self.shortTitleContainer.frame = shortTitleFrame + self.shortTitleNode.frame = CGRect(origin: CGPoint(), size: shortTitleFrame.size) + self.shortTitleActiveNode.frame = CGRect(origin: CGPoint(), size: shortTitleFrame.size) + + if let deleteButtonNode = self.deleteButtonNode { + if let theme = self.theme { + let deleteButtonSize = deleteButtonNode.update(theme: theme) + deleteButtonNode.frame = CGRect(origin: CGPoint(x: -deleteButtonSize.width + 3.0, y: 5.0), size: deleteButtonSize) + } + } + + let badgeSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) + let badgeInset: CGFloat = 4.0 + let badgeBackgroundFrame = CGRect(origin: CGPoint(x: titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + 4.0, y: floor((height - 18.0) / 2.0)), size: CGSize(width: max(18.0, badgeSize.width + badgeInset * 2.0), height: 18.0)) + self.badgeContainerNode.frame = badgeBackgroundFrame + self.badgeBackgroundActiveNode.frame = CGRect(origin: CGPoint(), size: badgeBackgroundFrame.size) + self.badgeBackgroundInactiveNode.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) + + let width: CGFloat + if self.unreadCount == 0 || self.isReordering || self.isEditing || self.isDisabled { + if !self.isReordering { + self.badgeContainerNode.alpha = 0.0 + } + width = titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + } else { + if !self.isReordering { + self.badgeContainerNode.alpha = 1.0 + } + width = badgeBackgroundFrame.maxX + } + + return (width, shortTitleSize.width - self.shortTitleNode.insets.left - self.shortTitleNode.insets.right) + } + + func updateArea(size: CGSize, sideInset: CGFloat, useShortTitle: Bool, transition: ContainedViewLayoutTransition) { + transition.updateAlpha(node: self.titleContainer, alpha: useShortTitle ? 0.0 : 1.0) + transition.updateAlpha(node: self.shortTitleContainer, 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(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) + self.extractedContainerNode.hitTestSlop = self.hitTestSlop + self.extractedContainerNode.contentNode.hitTestSlop = self.hitTestSlop + self.containerNode.hitTestSlop = self.hitTestSlop + + let extractedBackgroundHeight: CGFloat = 36.0 + let extractedBackgroundInset: CGFloat = 14.0 + self.extractedBackgroundNode.frame = CGRect(origin: CGPoint(x: -extractedBackgroundInset, y: floor((size.height - extractedBackgroundHeight) / 2.0)), size: CGSize(width: size.width + extractedBackgroundInset * 2.0, height: extractedBackgroundHeight)) + } + + func animateBadgeIn() { + 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() { + 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) + } + } + + 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 + } + } + + if self.buttonNode.isUserInteractionEnabled { + if let result = self.buttonNode.view.hitTest(self.view.convert(point, to: self.buttonNode.view), with: event) { + return result + } + } + + return super.hitTest(point, with: event) + } +} + +public enum ChatListFilterTabEntryId: Hashable { + case all + case filter(Int32) +} + +public struct ChatListFilterTabEntryUnreadCount: Equatable { + public let value: Int + public let hasUnmuted: Bool + + public init(value: Int, hasUnmuted: Bool) { + self.value = value + self.hasUnmuted = hasUnmuted + } +} + +public enum ChatListFilterTabEntry: Equatable { + case all(unreadCount: Int) + case filter(id: Int32, text: ChatFolderTitle, unread: ChatListFilterTabEntryUnreadCount) + + public var id: ChatListFilterTabEntryId { + switch self { + case .all: + return .all + case let .filter(id, _, _): + return .filter(id) + } + } + + func title(strings: PresentationStrings) -> ChatFolderTitle { + switch self { + case .all: + return ChatFolderTitle(text: strings.ChatList_Tabs_AllChats, entities: [], enableAnimations: true) + case let .filter(_, text, _): + return text + } + } + + func shortTitle(strings: PresentationStrings) -> ChatFolderTitle { + switch self { + case .all: + return ChatFolderTitle(text: strings.ChatList_Tabs_All, entities: [], enableAnimations: true) + case let .filter(_, text, _): + return text + } + } +} + +public final class ChatListFilterTabContainerNode: ASDisplayNode { + private let context: AccountContext + private let backgroundContainerView: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView + private let scrollNode: ASScrollNode + private let selectedBackgroundNode: ASImageNode + private var itemNodes: [ChatListFilterTabEntryId: ItemNode] = [:] + + public var tabSelected: ((ChatListFilterTabEntryId, Bool) -> Void)? + public var tabRequestedDeletion: ((ChatListFilterTabEntryId) -> Void)? + public var addFilter: (() -> Void)? + public var contextGesture: ((Int32?, ContextExtractedContentContainingNode, ContextGesture, Bool) -> Void)? + public var presentPremiumTip: (() -> Void)? + + private var reorderingGesture: ReorderingGestureRecognizer? + private var reorderingItem: ChatListFilterTabEntryId? + private var reorderingItemPosition: (initial: CGFloat, offset: CGFloat)? + private var reorderingAutoScrollAnimator: ConstantDisplayLinkAnimator? + private var initialReorderedItemIds: [ChatListFilterTabEntryId]? + private var reorderedItemIds: [ChatListFilterTabEntryId]? + private lazy var hapticFeedback = { HapticFeedback() }() + + private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, canReorderAllChats: Bool, filtersLimit: Int32?, transitionFraction: CGFloat, presentationData: PresentationData)? + + public var reorderedFilterIds: [Int32]? { + return self.reorderedItemIds.flatMap { + $0.compactMap { + switch $0 { + case .all: + return 0 + case let .filter(id): + return id + } + } + } + } + + public var filtersCount: Int32 { + if let (_, _, filters, _, _, _, _, _, _, _) = self.currentParams { + let filters = filters.filter { filter in + if case .all = filter { + return false + } else { + return true + } + } + return Int32(filters.count) + } else { + return 0 + } + } + + public init(context: AccountContext) { + self.context = context + + self.backgroundContainerView = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + self.backgroundContainerView.contentView.addSubview(self.backgroundView) + + self.scrollNode = ASScrollNode() + + self.selectedBackgroundNode = ASImageNode() + self.selectedBackgroundNode.displaysAsynchronously = false + self.selectedBackgroundNode.displayWithoutProcessing = true + + super.init() + + self.view.addSubview(self.backgroundContainerView) + + self.scrollNode.view.showsHorizontalScrollIndicator = false + self.scrollNode.view.showsVerticalScrollIndicator = false + self.scrollNode.view.scrollsToTop = false + self.scrollNode.view.delaysContentTouches = false + self.scrollNode.view.canCancelContentTouches = true + if #available(iOS 11.0, *) { + self.scrollNode.view.contentInsetAdjustmentBehavior = .never + } + + self.backgroundView.contentView.addSubview(self.scrollNode.view) + self.scrollNode.addSubnode(self.selectedBackgroundNode) + + let reorderingGesture = ReorderingGestureRecognizer(shouldBegin: { [weak self] point in + guard let strongSelf = self else { + return false + } + for (_, itemNode) in strongSelf.itemNodes { + if itemNode.view.convert(itemNode.bounds, to: strongSelf.view).contains(point) { + return true + } + } + return false + }, began: { [weak self] point in + guard let strongSelf = self, let _ = strongSelf.currentParams else { + return + } + strongSelf.initialReorderedItemIds = strongSelf.reorderedItemIds + 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.backgroundView.contentView.addSubview(itemNode.view) + + strongSelf.reorderingItemPosition = (itemNode.frame.minX, 0.0) + if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, canReorderAllChats, filtersLimit, transitionFraction, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, canReorderAllChats: canReorderAllChats, filtersLimit: filtersLimit, 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) + } + + if strongSelf.currentParams?.canReorderAllChats == false, let firstItem = strongSelf.reorderedItemIds?.first, case .filter = firstItem { + strongSelf.reorderedItemIds = strongSelf.initialReorderedItemIds + strongSelf.presentPremiumTip?() + } + + strongSelf.reorderingItem = nil + strongSelf.reorderingItemPosition = nil + strongSelf.reorderingAutoScrollAnimator?.invalidate() + strongSelf.reorderingAutoScrollAnimator = nil + if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, canReorderAllChats, filtersLimit, transitionFraction, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, canReorderAllChats: canReorderAllChats, filtersLimit: filtersLimit, 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 + } + + let minIndex = 0 + 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(minIndex, itemIndex - 1) + } else { + targetIndex = max(minIndex, 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, canReorderAllChats, filtersLimit, transitionFraction, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, canReorderAllChats: canReorderAllChats, filtersLimit: filtersLimit, transitionFraction: transitionFraction, presentationData: presentationData, transition: .animated(duration: 0.25, curve: .easeInOut)) + } + } + break + } + } + } + + strongSelf.reorderingItemPosition = (initial, offset) + } + if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, canReorderAllChats, filtersLimit, transitionFraction, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, canReorderAllChats: canReorderAllChats, filtersLimit: filtersLimit, transitionFraction: transitionFraction, presentationData: presentationData, transition: .immediate) + } + }) + self.reorderingGesture = reorderingGesture + self.view.addGestureRecognizer(reorderingGesture) + reorderingGesture.isEnabled = false + } + + private var previousSelectedAbsFrame: CGRect? + private var previousSelectedFrame: CGRect? + + public func cancelAnimations() { + self.selectedBackgroundNode.layer.removeAllAnimations() + self.scrollNode.layer.removeAllAnimations() + } + + public func update(size containerSize: CGSize, sideInset containerSideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, canReorderAllChats: Bool, filtersLimit: Int32?, transitionFraction: CGFloat, presentationData: PresentationData, transition proposedTransition: ContainedViewLayoutTransition) { + let isFirstTime = self.currentParams == nil + let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : proposedTransition + + let backgroundSize = CGSize(width: containerSize.width - (16.0 - containerSideInset) * 2.0, height: 44.0) + + transition.updateFrame(view: self.backgroundContainerView, frame: CGRect(origin: CGPoint(x: containerSideInset + 16.0, y: 0.0), size: backgroundSize)) + self.backgroundContainerView.update(size: backgroundSize, isDark: presentationData.theme.overallDarkAppearance, transition: ComponentTransition(transition)) + + transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: backgroundSize)) + self.backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: ComponentTransition(transition)) + + var isEditing = isEditing + if isReordering { + isEditing = false + } + + var focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter + let previousScrollBounds = self.scrollNode.bounds + let previousContentWidth = self.scrollNode.view.contentSize.width + + if self.currentParams?.presentationData.theme !== presentationData.theme { + self.selectedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 36.0, color: presentationData.theme.chatList.itemHighlightedBackgroundColor) + } + + 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: containerSize, sideInset: containerSideInset, filters: filters, selectedFilter: selectedFilter, isReordering, isEditing, canReorderAllChats, filtersLimit, transitionFraction, presentationData: presentationData) + + self.reorderingGesture?.isEnabled = isReordering + + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: backgroundSize)) + + enum BadgeAnimation { + case `in` + case out + } + + var badgeAnimations: [ChatListFilterTabEntryId: BadgeAnimation] = [:] + + 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 + } + } + } + + var folderIndex = 0 + for i in 0 ..< reorderedFilters.count { + let filter = reorderedFilters[i] + + 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(context: self.context, pressed: { [weak self] disabled in + self?.tabSelected?(filter.id, disabled) + }, requestedDeletion: { [weak self] in + self?.tabRequestedDeletion?(filter.id) + }, contextGesture: { [weak self] sourceNode, gesture, isDisabled in + guard let strongSelf = self else { + return + } + strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = false + strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = true + strongSelf.scrollNode.view.setContentOffset(strongSelf.scrollNode.view.contentOffset, animated: false) + switch filter { + case let .filter(id, _, _): + strongSelf.contextGesture?(id, sourceNode, gesture, isDisabled) + default: + strongSelf.contextGesture?(nil, sourceNode, gesture, isDisabled) + } + }) + self.itemNodes[filter.id] = itemNode + } + let unreadCount: Int + let unreadHasUnmuted: Bool + var isNoFilter = false + var isDisabled = false + switch filter { + case let .all(count): + unreadCount = count + unreadHasUnmuted = true + isNoFilter = true + case let .filter(_, _, unread): + unreadCount = unread.value + unreadHasUnmuted = unread.hasUnmuted + + if let filtersLimit = filtersLimit { + isDisabled = !canReorderAllChats && folderIndex >= filtersLimit + } + folderIndex += 1 + } + if !wasAdded && (itemNode.unreadCount != 0) != (unreadCount != 0) { + badgeAnimations[filter.id] = (unreadCount != 0) ? .in : .out + } + + let selectionFraction: CGFloat + if selectedFilter == filter.id { + selectionFraction = 1.0 - abs(transitionFraction) + } else if i != 0 && selectedFilter == reorderedFilters[i - 1].id { + selectionFraction = max(0.0, -transitionFraction) + } else if i != reorderedFilters.count - 1 && selectedFilter == reorderedFilters[i + 1].id { + selectionFraction = max(0.0, transitionFraction) + } else { + selectionFraction = 0.0 + } + + itemNode.updateText(strings: presentationData.strings, title: filter.title(strings: presentationData.strings), shortTitle: i == 0 ? filter.shortTitle(strings: presentationData.strings) : filter.title(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, selectionFraction: selectionFraction, isEditing: isEditing, isReordering: isReordering, canReorderAllChats: canReorderAllChats, isDisabled: isDisabled, presentationData: presentationData, transition: itemNodeTransition) + } + var removeKeys: [ChatListFilterTabEntryId] = [] + for (id, _) in self.itemNodes { + if !filters.contains(where: { $0.id == id }) { + removeKeys.append(id) + } + } + for id in removeKeys { + if let itemNode = self.itemNodes.removeValue(forKey: id) { + transition.updateAlpha(node: itemNode, alpha: 0.0, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + transition.updateTransformScale(node: itemNode, scale: 0.1) + } + } + + var tabSizes: [(ChatListFilterTabEntryId, CGSize, CGSize, ItemNode, Bool)] = [] + var totalRawTabSize: CGFloat = 0.0 + var selectionFrames: [CGRect] = [] + + 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, paneNodeShortWidth) = itemNode.updateLayout(height: backgroundSize.height, transition: itemNodeTransition) + let paneNodeSize = CGSize(width: paneNodeWidth, height: backgroundSize.height) + let paneNodeShortSize = CGSize(width: paneNodeShortWidth, height: backgroundSize.height) + tabSizes.append((filter.id, paneNodeSize, paneNodeShortSize, itemNode, wasAdded)) + totalRawTabSize += paneNodeSize.width + + if case .animated = transition, let badgeAnimation = badgeAnimations[filter.id] { + switch badgeAnimation { + case .in: + itemNode.animateBadgeIn() + case .out: + itemNode.animateBadgeOut() + } + } + } + + let minSpacing: CGFloat = 26.0 + + let resolvedSideInset: CGFloat = 14.0 + var leftOffset: CGFloat = resolvedSideInset + + var longTitlesWidth: CGFloat = resolvedSideInset + for i in 0 ..< tabSizes.count { + let (_, paneNodeSize, _, _, _) = tabSizes[i] + longTitlesWidth += paneNodeSize.width + if i != tabSizes.count - 1 { + longTitlesWidth += minSpacing + } + } + longTitlesWidth += resolvedSideInset + let useShortTitles = longTitlesWidth > backgroundSize.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((backgroundSize.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 + + self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: backgroundSize.height) + + var selectedFrame: CGRect? + 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))) + } + + if currentIndex != 0 && transitionFraction > 0.0 { + let currentFrame = selectionFrames[currentIndex] + let previousFrame = selectionFrames[currentIndex - 1] + selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) + } else if currentIndex != filters.count - 1 && transitionFraction < 0.0 { + let currentFrame = selectionFrames[currentIndex] + let previousFrame = selectionFrames[currentIndex + 1] + selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) + } else { + selectedFrame = selectionFrames[currentIndex] + } + } + + if let selectedFrame = selectedFrame { + let wasAdded = self.selectedBackgroundNode.isHidden + self.selectedBackgroundNode.isHidden = false + let selectedBackgroundFrame = CGRect(origin: CGPoint(x: selectedFrame.minX - 10.0, y: selectedFrame.minY - floor((36.0 - selectedFrame.height) * 0.5)), size: CGSize(width: selectedFrame.width + 10.0 * 2.0, height: 36.0)) + if wasAdded { + self.selectedBackgroundNode.frame = selectedBackgroundFrame + } else { + transition.updateFrame(node: self.selectedBackgroundNode, frame: selectedBackgroundFrame) + } + + 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))) + updatedBounds = CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size) + } + self.scrollNode.bounds = updatedBounds + } + if abs(previousScrollBounds.minX - self.scrollNode.bounds.minX) > .ulpOfOne { + 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.selectedBackgroundNode.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 InternalGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if otherGestureRecognizer is UIPanGestureRecognizer { + return true + } else { + return false + } + } +} + +private final class ReorderingGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { + private let internalDelegate = InternalGestureRecognizerDelegate() + + 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.internalDelegate + } + + override func reset() { + super.reset() + + self.initialLocation = nil + self.delayTimer?.invalidate() + self.delayTimer = nil + self.currentLocation = nil + } + + 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/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/BUILD b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/BUILD new file mode 100644 index 00000000..cd53921f --- /dev/null +++ b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/BUILD @@ -0,0 +1,33 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatListHeaderNoticeComponent", + module_name = "ChatListHeaderNoticeComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AppBundle", + "//submodules/ItemListUI", + "//submodules/Markdown", + "//submodules/TelegramUI/Components/Chat/MergedAvatarsNode", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TextFormat", + "//submodules/AvatarNode", + "//submodules/TelegramUI/Components/GlobalControlPanelsContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListHeaderNoticeComponent.swift b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListHeaderNoticeComponent.swift new file mode 100644 index 00000000..f3596727 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListHeaderNoticeComponent.swift @@ -0,0 +1,139 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramCore +import TelegramPresentationData +import AccountContext +import GlobalControlPanelsContext +import ComponentFlow +import ComponentDisplayAdapters + +public final class ChatListHeaderNoticeComponent: Component { + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + public let data: GlobalControlPanelsContext.ChatListNotice + public let activateAction: (GlobalControlPanelsContext.ChatListNotice) -> Void + public let dismissAction: (GlobalControlPanelsContext.ChatListNotice) -> Void + public let selectAction: (GlobalControlPanelsContext.ChatListNotice, Bool) -> Void + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + data: GlobalControlPanelsContext.ChatListNotice, + activateAction: @escaping (GlobalControlPanelsContext.ChatListNotice) -> Void, + dismissAction: @escaping (GlobalControlPanelsContext.ChatListNotice) -> Void, + selectAction: @escaping (GlobalControlPanelsContext.ChatListNotice, Bool) -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.data = data + self.activateAction = activateAction + self.dismissAction = dismissAction + self.selectAction = selectAction + } + + public static func ==(lhs: ChatListHeaderNoticeComponent, rhs: ChatListHeaderNoticeComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.data != rhs.data { + return false + } + return true + } + + public final class View: UIView { + private var panel: ChatListNoticeItemNode? + + private var component: ChatListHeaderNoticeComponent? + private weak var state: EmptyComponentState? + + public override init(frame: CGRect) { + super.init(frame: frame) + + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { + guard let component = self.component else { + return + } + if case .ended = recognizer.state { + component.activateAction(component.data) + } + } + + func update(component: ChatListHeaderNoticeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let itemNode: ChatListNoticeItemNode + if let current = self.panel { + itemNode = current + } else { + itemNode = ChatListNoticeItemNode() + self.panel = itemNode + self.addSubview(itemNode.view) + } + + let item = ChatListNoticeItem( + context: component.context, + theme: component.theme, + strings: component.strings, + notice: component.data, + action: { [weak self] action in + guard let self, let component = self.component else { + return + } + switch action { + case .activate: + component.activateAction(component.data) + case .hide: + component.dismissAction(component.data) + case let .buttonChoice(isPositive): + component.selectAction(component.data, isPositive) + } + } + ) + let (nodeLayout, apply) = itemNode.asyncLayout()(item, ListViewItemLayoutParams( + width: availableSize.width, + leftInset: 0.0, + rightInset: 0.0, + availableHeight: 10000.0, + isStandalone: true + ), false) + + let size = CGSize(width: availableSize.width, height: nodeLayout.contentSize.height) + let panelFrame = CGRect(origin: CGPoint(), size: size) + transition.setFrame(view: itemNode.view, frame: panelFrame) + apply() + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListNoticeItem.swift b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListNoticeItem.swift new file mode 100644 index 00000000..7c7cb283 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListNoticeItem.swift @@ -0,0 +1,548 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import AppBundle +import ItemListUI +import Markdown +import AccountContext +import MergedAvatarsNode +import TextNodeWithEntities +import TextFormat +import AvatarNode +import GlobalControlPanelsContext + +class ChatListNoticeItem: ListViewItem { + enum Action { + case activate + case hide + case buttonChoice(isPositive: Bool) + } + + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let notice: GlobalControlPanelsContext.ChatListNotice + let action: (Action) -> Void + + let selectable: Bool = true + + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, notice: GlobalControlPanelsContext.ChatListNotice, action: @escaping (Action) -> Void) { + self.context = context + self.theme = theme + self.strings = strings + self.notice = notice + self.action = action + } + + func selected(listView: ListView) { + listView.clearHighlightAnimated(true) + + self.action(.activate) + } + + 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 = ChatListNoticeItemNode() + + let (nodeLayout, apply) = node.asyncLayout()(self, params, false) + + node.insets = nodeLayout.insets + node.contentSize = nodeLayout.contentSize + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in + apply() + }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + assert(node() is ChatListNoticeItemNode) + if let nodeValue = node() as? ChatListNoticeItemNode { + + let layout = nodeValue.asyncLayout() + async { + let (nodeLayout, apply) = layout(self, params, nextItem == nil) + Queue.mainQueue().async { + completion(nodeLayout, { _ in + apply() + }) + } + } + } + } + } +} + +private let separatorHeight = 1.0 / UIScreen.main.scale + +private let titleFont = Font.semibold(15.0) +private let titleBoldFont = Font.bold(15.0) +private let titleItalicFont = Font.semiboldItalic(15.0) +private let titleBoldItalicFont = Font.semiboldItalic(15.0) + +private let textFont = Font.regular(15.0) +private let textBoldFont = Font.semibold(15.0) +private let textItalicFont = Font.italic(15.0) +private let textBoldItalicFont = Font.semiboldItalic(15.0) + +private let smallTextFont = Font.regular(14.0) + +final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode { + private let contentContainer: ASDisplayNode + private let titleNode: TextNodeWithEntities + private let textNode: TextNodeWithEntities + private let arrowNode: ASImageNode + private let separatorNode: ASDisplayNode + + private var avatarNode: AvatarNode? + private var avatarsNode: MergedAvatarsNode? + + private var closeButton: HighlightableButtonNode? + + private var okButtonText: TextNode? + private var cancelButtonText: TextNode? + private var okButton: HighlightableButtonNode? + private var cancelButton: HighlightableButtonNode? + + private var item: ChatListNoticeItem? + + override var apparentHeight: CGFloat { + didSet { + self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.bounds.width, height: self.apparentHeight)) + self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.contentContainer.bounds.height - UIScreenPixel), size: CGSize(width: self.contentContainer.bounds.width, height: UIScreenPixel)) + } + } + + required init() { + self.contentContainer = ASDisplayNode() + + self.titleNode = TextNodeWithEntities() + self.textNode = TextNodeWithEntities() + self.arrowNode = ASImageNode() + self.separatorNode = ASDisplayNode() + + super.init(layerBacked: false, rotated: false, seeThrough: false) + + self.contentContainer.clipsToBounds = true + self.clipsToBounds = true + + self.contentContainer.addSubnode(self.titleNode.textNode) + self.contentContainer.addSubnode(self.textNode.textNode) + self.contentContainer.addSubnode(self.arrowNode) + + self.addSubnode(self.contentContainer) + } + + @objc private func closePressed() { + guard let item = self.item else { + return + } + item.action(.hide) + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + let layout = self.asyncLayout() + let (_, apply) = layout(item as! ChatListNoticeItem, params, nextItem == nil) + apply() + } + + func asyncLayout() -> (_ item: ChatListNoticeItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) { + let previousItem = self.item + + let makeTitleLayout = TextNodeWithEntities.asyncLayout(self.titleNode) + let makeTextLayout = TextNodeWithEntities.asyncLayout(self.textNode) + + let makeOkButtonTextLayout = TextNode.asyncLayout(self.okButtonText) + let makeCancelButtonTextLayout = TextNode.asyncLayout(self.cancelButtonText) + + return { item, params, last in + let baseWidth = params.width - params.leftInset - params.rightInset + let _ = baseWidth + + let sideInset: CGFloat = params.leftInset + 16.0 + let rightInset: CGFloat = sideInset + 24.0 + var titleRightInset = rightInset - 4.0 + let verticalInset: CGFloat = 9.0 + var spacing: CGFloat = 0.0 + + let themeUpdated = item.theme !== previousItem?.theme + + let titleString: NSAttributedString + let textString: NSAttributedString + var avatarPeer: EnginePeer? + var avatarPeers: [EnginePeer] = [] + + var okButtonLayout: (TextNodeLayout, () -> TextNode)? + var cancelButtonLayout: (TextNodeLayout, () -> TextNode)? + var alignment: NSTextAlignment = .left + + switch item.notice { + case let .clearStorage(sizeFraction): + let sizeString = dataSizeString(Int64(sizeFraction), formatting: DataSizeStringFormatting(strings: item.strings, decimalSeparator: ".")) + let rawTitleString = item.strings.ChatList_StorageHintTitle(sizeString) + let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString.string, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)) + if let range = rawTitleString.ranges.first { + titleStringValue.addAttribute(.foregroundColor, value: item.theme.rootController.navigationBar.accentTextColor, range: range.range) + } + titleString = titleStringValue + + textString = NSAttributedString(string: item.strings.ChatList_StorageHintText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + case .setupPassword: + titleString = NSAttributedString(string: item.strings.Settings_SuggestSetupPasswordTitle, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor) + textString = NSAttributedString(string: item.strings.Settings_SuggestSetupPasswordText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + case let .premiumUpgrade(discount): + let discountString = "\(discount)%" + let rawTitleString = item.strings.ChatList_PremiumAnnualUpgradeTitle(discountString) + let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString.string, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)) + if let range = rawTitleString.ranges.first { + titleStringValue.addAttribute(.foregroundColor, value: item.theme.rootController.navigationBar.accentTextColor, range: range.range) + } + titleString = titleStringValue + + textString = NSAttributedString(string: item.strings.ChatList_PremiumAnnualUpgradeText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + case let .premiumAnnualDiscount(discount): + let discountString = "\(discount)%" + let rawTitleString = item.strings.ChatList_PremiumAnnualDiscountTitle(discountString) + let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString.string, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)) + if let range = rawTitleString.ranges.first { + titleStringValue.addAttribute(.foregroundColor, value: item.theme.rootController.navigationBar.accentTextColor, range: range.range) + } + titleString = titleStringValue + + textString = NSAttributedString(string: item.strings.ChatList_PremiumAnnualDiscountText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + titleRightInset = sideInset + case let .premiumRestore(discount): + let discountString = "\(discount)%" + let rawTitleString = item.strings.ChatList_PremiumRestoreDiscountTitle(discountString) + let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString.string, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)) + if let range = rawTitleString.ranges.first { + titleStringValue.addAttribute(.foregroundColor, value: item.theme.rootController.navigationBar.accentTextColor, range: range.range) + } + titleString = titleStringValue + + textString = NSAttributedString(string: item.strings.ChatList_PremiumRestoreDiscountText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + case .xmasPremiumGift: + titleString = parseMarkdownIntoAttributedString(item.strings.ChatList_PremiumXmasGiftTitle, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.accentTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), linkAttribute: { _ in return nil })) + textString = NSAttributedString(string: item.strings.ChatList_PremiumXmasGiftText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + case .premiumGrace: + titleString = parseMarkdownIntoAttributedString(item.strings.ChatList_PremiumGraceTitle, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.accentTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), linkAttribute: { _ in return nil })) + textString = NSAttributedString(string: item.strings.ChatList_PremiumGraceText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + case .setupBirthday: + titleString = NSAttributedString(string: item.strings.ChatList_AddBirthdayTitle, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor) + textString = NSAttributedString(string: item.strings.ChatList_AddBirthdayText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + case let .birthdayPremiumGift(peers, _): + let title: String + let text: String + if peers.count == 1, let peer = peers.first { + var peerName = peer.compactDisplayTitle + if peerName.count > 20 { + peerName = peerName.prefix(20).trimmingCharacters(in: .whitespacesAndNewlines) + "\u{2026}" + } + title = item.strings.ChatList_BirthdaySingleTitle(peerName).string + text = item.strings.ChatList_BirthdaySingleText + } else { + title = item.strings.ChatList_BirthdayMultipleTitle(Int32(peers.count)) + text = item.strings.ChatList_BirthdayMultipleText + } + titleString = parseMarkdownIntoAttributedString(title, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.accentTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), linkAttribute: { _ in return nil })) + textString = NSAttributedString(string: text, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + avatarPeers = Array(peers.prefix(3)) + case let .reviewLogin(newSessionReview, totalCount): + spacing = 2.0 + alignment = .center + + var rawTitleString = item.strings.ChatList_SessionReview_PanelTitle + if totalCount > 1 { + rawTitleString = "1/\(totalCount) \(rawTitleString)" + } + let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)) + titleString = titleStringValue + + textString = NSAttributedString(string: item.strings.ChatList_SessionReview_PanelText(newSessionReview.device, newSessionReview.location).string, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + + okButtonLayout = makeOkButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_SessionReview_PanelConfirm, font: titleFont, textColor: item.theme.list.itemAccentColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0))) + cancelButtonLayout = makeCancelButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_SessionReview_PanelReject, font: titleFont, textColor: item.theme.list.itemDestructiveColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0))) + case let .starsSubscriptionLowBalance(amount, peers): + let title: String + let text: String + let starsValue = item.strings.ChatList_SubscriptionsLowBalance_Stars(Int32(clamping: amount.value)) + if let peer = peers.first, peers.count == 1 { + title = item.strings.ChatList_SubscriptionsLowBalance_Single_Title(starsValue, peer.compactDisplayTitle).string + text = item.strings.ChatList_SubscriptionsLowBalance_Single_Text + } else { + title = item.strings.ChatList_SubscriptionsLowBalance_Multiple_Title(starsValue).string + text = item.strings.ChatList_SubscriptionsLowBalance_Multiple_Text + } + let attributedTitle = NSMutableAttributedString(string: "โญ๏ธ\(title)", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor) + if let range = attributedTitle.string.range(of: "โญ๏ธ") { + attributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedTitle.string)) + attributedTitle.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: attributedTitle.string)) + } + titleString = attributedTitle + textString = NSAttributedString(string: text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + case let .setupPhoto(accountPeer): + titleString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Title, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor) + textString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + avatarPeer = accountPeer + case .accountFreeze: + titleString = NSAttributedString(string: item.strings.ChatList_FrozenAccount_Title, font: titleFont, textColor: item.theme.list.itemDestructiveColor) + textString = NSAttributedString(string: item.strings.ChatList_FrozenAccount_Text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + case let .link(_, _, title, subtitle): + titleString = stringWithAppliedEntities(title.string, entities: title.entities, baseColor: item.theme.list.itemPrimaryTextColor, linkColor: item.theme.list.itemAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleBoldFont, italicFont: titleItalicFont, boldItalicFont: titleBoldItalicFont, fixedFont: titleFont, blockQuoteFont: titleFont, message: nil) + textString = stringWithAppliedEntities(subtitle.string, entities: subtitle.entities, baseColor: item.theme.list.itemPrimaryTextColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFont, blockQuoteFont: textFont, message: nil) + } + + var leftInset: CGFloat = sideInset + if !avatarPeers.isEmpty { + let avatarsWidth = 30.0 + CGFloat(avatarPeers.count - 1) * 16.0 + leftInset += avatarsWidth + 4.0 + } else if let _ = avatarPeer { + let avatarsWidth: CGFloat = 40.0 + leftInset += avatarsWidth + 6.0 + } + + let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - titleRightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18)) + + let textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, maximumNumberOfLines: 10, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18)) + + var contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.0.size.height + textLayout.0.size.height) + if let okButtonLayout { + contentSize.height += okButtonLayout.0.size.height + 20.0 + } + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets()) + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.item = item + + if themeUpdated { + strongSelf.arrowNode.image = PresentationResourcesItemList.disclosureArrowImage(item.theme) + } + + let _ = titleLayout.1(TextNodeWithEntities.Arguments(context: item.context, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: .white, attemptSynchronous: true)) + if case .center = alignment { + strongSelf.titleNode.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - titleLayout.0.size.width) * 0.5), y: verticalInset), size: titleLayout.0.size) + } else { + strongSelf.titleNode.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.0.size) + } + + let _ = textLayout.1(TextNodeWithEntities.Arguments(context: item.context, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: .white, attemptSynchronous: true)) + + strongSelf.titleNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 1000000.0, height: 1000000.0)) + strongSelf.textNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 1000000.0, height: 1000000.0)) + + if case .center = alignment { + strongSelf.textNode.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.0.size.width) * 0.5), y: strongSelf.titleNode.textNode.frame.maxY + spacing), size: textLayout.0.size) + } else { + strongSelf.textNode.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.textNode.frame.maxY + spacing), size: textLayout.0.size) + } + + if !avatarPeers.isEmpty { + let avatarsNode: MergedAvatarsNode + if let current = strongSelf.avatarsNode { + avatarsNode = current + } else { + avatarsNode = MergedAvatarsNode() + avatarsNode.isUserInteractionEnabled = false + strongSelf.addSubnode(avatarsNode) + strongSelf.avatarsNode = avatarsNode + } + let avatarSize = CGSize(width: 30.0, height: 30.0) + avatarsNode.update(context: item.context, peers: avatarPeers.map { $0._asPeer() }, synchronousLoad: false, imageSize: avatarSize.width, imageSpacing: 16.0, borderWidth: 2.0 - UIScreenPixel, avatarFontSize: 10.0) + let avatarsSize = CGSize(width: avatarSize.width + 16.0 * CGFloat(avatarPeers.count - 1), height: avatarSize.height) + avatarsNode.updateLayout(size: avatarsSize) + avatarsNode.frame = CGRect(origin: CGPoint(x: sideInset - 6.0, y: floor((layout.size.height - avatarsSize.height) / 2.0)), size: avatarsSize) + } else if let avatarsNode = strongSelf.avatarsNode { + avatarsNode.removeFromSupernode() + strongSelf.avatarsNode = nil + } + + if let avatarPeer { + let avatarNode: AvatarNode + if let current = strongSelf.avatarNode { + avatarNode = current + } else { + avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 13.0)) + avatarNode.isUserInteractionEnabled = false + strongSelf.addSubnode(avatarNode) + strongSelf.avatarNode = avatarNode + + avatarNode.setPeer(context: item.context, theme: item.theme, peer: avatarPeer, overrideImage: .cameraIcon) + } + let avatarSize = CGSize(width: 40.0, height: 40.0) + avatarNode.frame = CGRect(origin: CGPoint(x: sideInset - 6.0, y: floor((layout.size.height - avatarSize.height) / 2.0)), size: avatarSize) + } else if let avatarNode = strongSelf.avatarNode { + avatarNode.removeFromSupernode() + strongSelf.avatarNode = nil + } + + if let image = strongSelf.arrowNode.image { + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - image.size.width + 8.0, y: floor((layout.size.height - image.size.height) / 2.0)), size: image.size) + } + + let hasCloseButton: Bool + switch item.notice { + case .xmasPremiumGift, .setupBirthday, .birthdayPremiumGift, .premiumGrace, .starsSubscriptionLowBalance, .setupPhoto, .link: + hasCloseButton = true + default: + hasCloseButton = false + } + + if let okButtonLayout, let cancelButtonLayout { + strongSelf.arrowNode.isHidden = true + strongSelf.closeButton?.isHidden = true + + let okButton: HighlightableButtonNode + if let current = strongSelf.okButton { + okButton = current + } else { + okButton = HighlightableButtonNode() + strongSelf.okButton = okButton + strongSelf.contentContainer.addSubnode(okButton) + okButton.addTarget(strongSelf, action: #selector(strongSelf.okButtonPressed), forControlEvents: .touchUpInside) + } + + let cancelButton: HighlightableButtonNode + if let current = strongSelf.cancelButton { + cancelButton = current + } else { + cancelButton = HighlightableButtonNode() + strongSelf.cancelButton = cancelButton + strongSelf.contentContainer.addSubnode(cancelButton) + cancelButton.addTarget(strongSelf, action: #selector(strongSelf.cancelButtonPressed), forControlEvents: .touchUpInside) + } + + let okButtonText = okButtonLayout.1() + if okButtonText !== strongSelf.okButtonText { + strongSelf.okButtonText?.removeFromSupernode() + strongSelf.okButtonText = okButtonText + okButton.addSubnode(okButtonText) + } + + let cancelButtonText = cancelButtonLayout.1() + if cancelButtonText !== strongSelf.okButtonText { + strongSelf.cancelButtonText?.removeFromSupernode() + strongSelf.cancelButtonText = cancelButtonText + cancelButton.addSubnode(cancelButtonText) + } + + let buttonsWidth: CGFloat = max(min(300.0, params.width), okButtonLayout.0.size.width + cancelButtonLayout.0.size.width + 32.0) + let buttonWidth: CGFloat = floor(buttonsWidth * 0.5) + let buttonHeight: CGFloat = 32.0 + + let okButtonFrame = CGRect(origin: CGPoint(x: floor((params.width - buttonsWidth) * 0.5), y: strongSelf.textNode.textNode.frame.maxY + 6.0), size: CGSize(width: buttonWidth, height: buttonHeight)) + let cancelButtonFrame = CGRect(origin: CGPoint(x: okButtonFrame.maxX, y: strongSelf.textNode.textNode.frame.maxY + 6.0), size: CGSize(width: buttonWidth, height: buttonHeight)) + + okButton.frame = okButtonFrame + cancelButton.frame = cancelButtonFrame + + okButtonText.frame = CGRect(origin: CGPoint(x: floor((okButtonFrame.width - okButtonLayout.0.size.width) * 0.5), y: floor((okButtonFrame.height - okButtonLayout.0.size.height) * 0.5)), size: okButtonLayout.0.size) + cancelButtonText.frame = CGRect(origin: CGPoint(x: floor((cancelButtonFrame.width - cancelButtonLayout.0.size.width) * 0.5), y: floor((cancelButtonFrame.height - cancelButtonLayout.0.size.height) * 0.5)), size: cancelButtonLayout.0.size) + } else { + strongSelf.arrowNode.isHidden = hasCloseButton + + if let okButton = strongSelf.okButton { + strongSelf.okButton = nil + okButton.removeFromSupernode() + } + if let cancelButton = strongSelf.cancelButton { + strongSelf.cancelButton = nil + cancelButton.removeFromSupernode() + } + if let okButtonText = strongSelf.okButtonText { + strongSelf.okButtonText = nil + okButtonText.removeFromSupernode() + } + if let cancelButtonText = strongSelf.cancelButtonText { + strongSelf.cancelButtonText = nil + cancelButtonText.removeFromSupernode() + } + + if hasCloseButton { + let closeButton: HighlightableButtonNode + if let current = strongSelf.closeButton { + closeButton = current + } else { + closeButton = HighlightableButtonNode() + closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) + closeButton.addTarget(self, action: #selector(strongSelf.closePressed), forControlEvents: [.touchUpInside]) + strongSelf.contentContainer.addSubnode(closeButton) + strongSelf.closeButton = closeButton + } + + if themeUpdated || closeButton.image(for: .normal) == nil { + closeButton.setImage(PresentationResourcesItemList.itemListCloseIconImage(item.theme), for: .normal) + } + + let closeButtonSize = closeButton.measure(CGSize(width: 100.0, height: 100.0)) + closeButton.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - closeButtonSize.width, y: floor((layout.size.height - closeButtonSize.height) / 2.0)), size: closeButtonSize) + } else { + strongSelf.closeButton?.removeFromSupernode() + strongSelf.closeButton = nil + } + } + + strongSelf.contentSize = layout.contentSize + strongSelf.insets = layout.insets + + strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) + + strongSelf.contentContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize) + + switch item.notice { + default: + strongSelf.setRevealOptions((left: [], right: [])) + } + } + }) + } + } + + override public func selected() { + super.selected() + + if case .setupPhoto = self.item?.notice { + self.avatarNode?.playCameraAnimation() + } + } + + @objc private func okButtonPressed() { + self.item?.action(.buttonChoice(isPositive: true)) + } + + @objc private func cancelButtonPressed() { + self.item?.action(.buttonChoice(isPositive: false)) + } + + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { + super.animateInsertion(currentTimestamp, duration: duration, options: options) + + //self.transitionOffset = self.bounds.size.height + //self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp) + } + + override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { + super.updateRevealOffset(offset: offset, transition: transition) + + transition.updateSublayerTransformOffset(layer: self.contentContainer.layer, offset: CGPoint(x: offset, y: 0.0)) + } + + override public func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { + if let item = self.item { + item.action(.hide) + } + + self.setRevealOptionsOpened(false, animated: true) + self.revealOptionsInteractivelyClosed() + } +} diff --git a/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/BUILD b/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/BUILD new file mode 100644 index 00000000..74b602e4 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatListSearchFiltersContainerNode", + module_name = "ChatListSearchFiltersContainerNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/Sources/ChatListSearchFiltersContainerNode.swift b/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/Sources/ChatListSearchFiltersContainerNode.swift new file mode 100644 index 00000000..8b66c1bf --- /dev/null +++ b/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/Sources/ChatListSearchFiltersContainerNode.swift @@ -0,0 +1,295 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import AccountContext +import GlassBackgroundComponent +import ComponentFlow +import ComponentDisplayAdapters + +public enum ChatListSearchFilterEntryId: Hashable { + case filter(Int64) +} + +public enum ChatListSearchFilterEntry: Equatable { + case filter(ChatListSearchFilter) + + public var id: ChatListSearchFilterEntryId { + switch self { + case let .filter(filter): + return .filter(filter.id) + } + } +} + +public final class ChatListSearchFiltersContainerNode: ASDisplayNode { + private let backgroundContainer: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView + + private let scrollNode: ASScrollNode + private let selectionView: UIImageView + private var itemNodes: [ChatListSearchFilterEntryId: ItemNode] = [:] + + public var filterPressed: ((ChatListSearchFilter) -> Void)? + + private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], selectedFilter: ChatListSearchFilterEntryId?, transitionFraction: CGFloat, presentationData: PresentationData)? + + private var previousSelectedAbsFrame: CGRect? + private var previousSelectedFrame: CGRect? + + override public init() { + self.backgroundContainer = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + self.backgroundContainer.contentView.addSubview(self.backgroundView) + + self.scrollNode = ASScrollNode() + + self.selectionView = UIImageView() + + super.init() + + self.scrollNode.view.showsHorizontalScrollIndicator = false + self.scrollNode.view.scrollsToTop = false + self.scrollNode.view.delaysContentTouches = false + self.scrollNode.view.canCancelContentTouches = true + self.scrollNode.view.contentInsetAdjustmentBehavior = .never + + self.view.addSubview(self.backgroundContainer) + + self.backgroundView.contentView.addSubview(self.scrollNode.view) + self.scrollNode.view.addSubview(self.selectionView) + } + + public func cancelAnimations() { + self.scrollNode.layer.removeAllAnimations() + } + + public func update(size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], displayGlobalPostsNewBadge: Bool, selectedFilter: ChatListSearchFilterEntryId?, transitionFraction: CGFloat, presentationData: PresentationData, transition proposedTransition: ContainedViewLayoutTransition) { + let isFirstTime = self.currentParams == nil + let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : proposedTransition + + let componentTransition = ComponentTransition(transition) + + componentTransition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size)) + self.backgroundContainer.update(size: size, isDark: presentationData.theme.overallDarkAppearance, transition: componentTransition) + componentTransition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) + self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: componentTransition) + self.scrollNode.view.layer.cornerRadius = size.height * 0.5 + + var focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter + let previousScrollBounds = self.scrollNode.bounds + let previousContentWidth = self.scrollNode.view.contentSize.width + + self.currentParams = (size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, transitionFraction: transitionFraction, presentationData: presentationData) + + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) + + var hasSelection = false + for i in 0 ..< filters.count { + let filter = filters[i] + if case let .filter(type) = filter { + let itemNode: ItemNode + var itemNodeTransition = transition + if let current = self.itemNodes[filter.id] { + itemNode = current + } else { + itemNodeTransition = .immediate + itemNode = ItemNode(pressed: { [weak self] in + self?.filterPressed?(type) + }) + self.itemNodes[filter.id] = itemNode + } + + let selectionFraction: CGFloat + if selectedFilter == filter.id { + selectionFraction = 1.0 - abs(transitionFraction) + hasSelection = true + } else if i != 0 && selectedFilter == filters[i - 1].id { + selectionFraction = max(0.0, -transitionFraction) + } else if i != filters.count - 1 && selectedFilter == filters[i + 1].id { + selectionFraction = max(0.0, transitionFraction) + } else { + selectionFraction = 0.0 + } + + var displayNewBadge = false + if case .globalPosts = type { + displayNewBadge = displayGlobalPostsNewBadge + } + + itemNode.update(type: type, displayNewBadge: displayNewBadge, presentationData: presentationData, selectionFraction: selectionFraction, transition: itemNodeTransition) + } + } + + var updated = false + + var removeKeys: [ChatListSearchFilterEntryId] = [] + for (id, _) in self.itemNodes { + if !filters.contains(where: { $0.id == id }) { + removeKeys.append(id) + updated = true + } + } + for id in removeKeys { + if let itemNode = self.itemNodes.removeValue(forKey: id) { + transition.updateAlpha(node: itemNode, alpha: 0.0, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + transition.updateTransformScale(node: itemNode, scale: 0.1) + } + } + + var tabSizes: [(ChatListSearchFilterEntryId, CGSize, ItemNode, Bool)] = [] + var totalRawTabSize: CGFloat = 0.0 + var selectionFrames: [CGRect] = [] + + for filter in filters { + 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: itemNodeTransition) + let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height) + tabSizes.append((filter.id, paneNodeSize, itemNode, wasAdded)) + totalRawTabSize += paneNodeSize.width + } + + let minSpacing: CGFloat = 24.0 + var spacing = minSpacing + + let resolvedSideInset: CGFloat = 16.0 + sideInset + var leftOffset: CGFloat = resolvedSideInset + + var longTitlesWidth: CGFloat = resolvedSideInset + var titlesWidth: CGFloat = 0.0 + for i in 0 ..< tabSizes.count { + let (_, paneNodeSize, _, _) = tabSizes[i] + longTitlesWidth += paneNodeSize.width + titlesWidth += paneNodeSize.width + if i != tabSizes.count - 1 { + longTitlesWidth += minSpacing + } + } + longTitlesWidth += resolvedSideInset + + if longTitlesWidth < size.width && hasSelection { + spacing = (size.width - titlesWidth - resolvedSideInset * 2.0) / CGFloat(tabSizes.count - 1) + } + + let verticalOffset: CGFloat = -4.0 + for i in 0 ..< tabSizes.count { + let (_, paneNodeSize, paneNode, wasAdded) = tabSizes[i] + let itemNodeTransition = transition + + let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0) + verticalOffset), size: paneNodeSize) + + var effectiveWasAdded = wasAdded + if !effectiveWasAdded && !self.bounds.intersects(self.scrollNode.view.convert(paneNode.frame, to: self.view)) && self.bounds.intersects(self.scrollNode.view.convert(paneFrame, to: self.view)) { + effectiveWasAdded = true + } + + if effectiveWasAdded { + paneNode.frame = paneFrame + paneNode.alpha = 0.0 + paneNode.subnodeTransform = CATransform3DMakeScale(0.1, 0.1, 1.0) + itemNodeTransition.updateSublayerTransformScale(node: paneNode, scale: 1.0) + itemNodeTransition.updateAlpha(node: paneNode, alpha: 1.0) + } else { + if self.bounds.intersects(self.scrollNode.view.convert(paneFrame, to: self.view)) { + itemNodeTransition.updateFrameAdditive(node: paneNode, frame: paneFrame) + } else if paneNode.frame != paneFrame { + paneNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4) { [weak paneNode] _ in + paneNode?.frame = paneFrame + } + } + } + + paneNode.updateArea(size: paneFrame.size, sideInset: spacing / 2.0, transition: itemNodeTransition) + paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -spacing / 2.0, bottom: 0.0, right: -spacing / 2.0) + + selectionFrames.append(paneFrame) + + leftOffset += paneNodeSize.width + spacing + } + leftOffset -= spacing + leftOffset += resolvedSideInset + + self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: size.height) + + var selectedFrame: CGRect? + if let selectedFilter = selectedFilter, let currentIndex = filters.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))) + } + + if currentIndex != 0 && transitionFraction > 0.0 { + let currentFrame = selectionFrames[currentIndex] + let previousFrame = selectionFrames[currentIndex - 1] + selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) + } else if currentIndex != filters.count - 1 && transitionFraction < 0.0 { + let currentFrame = selectionFrames[currentIndex] + let previousFrame = selectionFrames[currentIndex + 1] + selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) + } else { + selectedFrame = selectionFrames[currentIndex] + } + } + + if let selectedFrame { + let wasAdded = self.selectionView.isHidden + let selectionFrame = CGRect(origin: CGPoint(x: selectedFrame.minX - 13.0, y: 3.0), size: CGSize(width: selectedFrame.width + 26.0, height: size.height - 3.0 * 2.0)) + if wasAdded { + self.selectionView.frame = selectionFrame + ComponentTransition(transition).animateAlpha(view: self.selectionView, from: 0.0, to: 1.0) + } else { + transition.updateFrame(view: self.selectionView, frame: selectionFrame) + } + self.selectionView.isHidden = false + + if self.selectionView.image?.size.height != selectionFrame.height { + self.selectionView.image = generateStretchableFilledCircleImage(diameter: selectionFrame.height, color: .white)?.withRenderingMode(.alwaysTemplate) + } + self.selectionView.tintColor = presentationData.theme.chat.inputPanel.panelControlColor.withAlphaComponent(0.1) + + 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 { + let updatedBounds: CGRect + if transitionFraction.isZero && selectedFilter == filters.first?.id { + updatedBounds = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size) + } else if transitionFraction.isZero && selectedFilter == filters.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))) + updatedBounds = 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.selectionView.isHidden = true + self.previousSelectedAbsFrame = nil + self.previousSelectedFrame = nil + } + + if updated && self.scrollNode.view.contentOffset.x > 0.0 { + self.scrollNode.view.contentOffset = CGPoint() + } + } +} diff --git a/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/Sources/ItemNode.swift b/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/Sources/ItemNode.swift new file mode 100644 index 00000000..d5d2eaab --- /dev/null +++ b/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/Sources/ItemNode.swift @@ -0,0 +1,206 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramPresentationData +import TelegramCore +import AccountContext + +final class ItemNode: ASDisplayNode { + private let pressed: () -> Void + + private let iconNode: ASImageNode + private let titleNode: ImmediateTextNode + private var titleBadgeView: UIImageView? + private let buttonNode: HighlightTrackingButtonNode + + private var selectionFraction: CGFloat = 0.0 + + private var theme: PresentationTheme? + + init(pressed: @escaping () -> Void) { + self.pressed = pressed + + let titleInset: CGFloat = 4.0 + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + + self.titleNode = ImmediateTextNode() + self.titleNode.displaysAsynchronously = false + self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) + + self.buttonNode = HighlightTrackingButtonNode() + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.iconNode) + 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.iconNode.layer.removeAnimation(forKey: "opacity") + strongSelf.iconNode.alpha = 0.4 + + strongSelf.titleNode.layer.removeAnimation(forKey: "opacity") + strongSelf.titleNode.alpha = 0.4 + } else { + strongSelf.iconNode.alpha = 1.0 + strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + + strongSelf.titleNode.alpha = 1.0 + strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + + @objc private func buttonPressed() { + self.pressed() + } + + func update(type: ChatListSearchFilter, displayNewBadge: Bool, presentationData: PresentationData, selectionFraction: CGFloat, transition: ContainedViewLayoutTransition) { + self.selectionFraction = selectionFraction + + let title: String + var titleBadge: String? + let icon: UIImage? + + let color = presentationData.theme.chat.inputPanel.panelControlColor + switch type { + case .chats: + title = presentationData.strings.ChatList_Search_FilterChats + icon = nil + case .topics: + title = presentationData.strings.ChatList_Search_FilterChats + icon = nil + case .channels: + title = presentationData.strings.ChatList_Search_FilterChannels + icon = nil + case .apps: + title = presentationData.strings.ChatList_Search_FilterApps + icon = nil + case .globalPosts: + title = presentationData.strings.ChatList_Search_FilterGlobalPosts + if displayNewBadge { + titleBadge = presentationData.strings.ChatList_ContextMenuBadgeNew + } + icon = nil + case .media: + title = presentationData.strings.ChatList_Search_FilterMedia + icon = nil + case .downloads: + title = presentationData.strings.ChatList_Search_FilterDownloads + icon = nil + case .links: + title = presentationData.strings.ChatList_Search_FilterLinks + icon = nil + case .files: + title = presentationData.strings.ChatList_Search_FilterFiles + icon = nil + case .music: + title = presentationData.strings.ChatList_Search_FilterMusic + icon = nil + case .voice: + title = presentationData.strings.ChatList_Search_FilterVoice + icon = nil + case .instantVideo: + title = presentationData.strings.ChatList_Search_FilterVoice + icon = nil + case .publicPosts: + title = presentationData.strings.ChatList_Search_FilterPublicPosts + icon = nil + case let .peer(peerId, isGroup, displayTitle, _): + title = displayTitle + let image: UIImage? + if isGroup { + image = UIImage(bundleImageName: "Chat List/Search/Group") + } else if peerId.namespace == Namespaces.Peer.CloudChannel { + image = UIImage(bundleImageName: "Chat List/Search/Channel") + } else { + image = UIImage(bundleImageName: "Chat List/Search/User") + } + icon = generateTintedImage(image: image, color: color) + case let .date(_, _, displayTitle): + title = displayTitle + icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Calendar"), color: color) + } + + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(15.0), textColor: color) + + if let titleBadge { + let titleBadgeView: UIImageView + if let current = self.titleBadgeView { + titleBadgeView = current + } else { + titleBadgeView = UIImageView() + self.titleBadgeView = titleBadgeView + self.view.addSubview(titleBadgeView) + + let labelText = NSAttributedString(string: titleBadge, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) + let labelBounds = labelText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil) + let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height)) + let badgeSize = CGSize(width: labelSize.width + 8.0, height: labelSize.height + 2.0 + 1.0) + titleBadgeView.image = generateImage(badgeSize, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let rect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - UIScreenPixel * 2.0)) + + context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 5.0).cgPath) + context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor) + context.fillPath() + + UIGraphicsPushContext(context) + labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel)) + UIGraphicsPopContext() + }) + } + } else if let titleBadgeView = self.titleBadgeView { + self.titleBadgeView = nil + titleBadgeView.removeFromSuperview() + } + + self.buttonNode.accessibilityLabel = title + if selectionFraction == 1.0 { + self.buttonNode.accessibilityTraits = [.button, .selected] + } else { + self.buttonNode.accessibilityTraits = [.button] + } + + if self.theme !== presentationData.theme { + self.theme = presentationData.theme + self.iconNode.image = icon + } + } + + func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + var iconInset: CGFloat = 0.0 + if let image = self.iconNode.image { + iconInset = 22.0 + self.iconNode.frame = CGRect(x: 0.0, y: 4.0 + floorToScreenPixels((height - image.size.height) / 2.0), width: image.size.width, height: image.size.height) + } + + let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) + let titleFrame = CGRect(origin: CGPoint(x: -self.titleNode.insets.left + iconInset, y: self.titleNode.insets.top + floorToScreenPixels((height - titleSize.height) / 2.0)), size: titleSize) + self.titleNode.frame = titleFrame + + var width = titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + iconInset + + if let titleBadgeView = self.titleBadgeView, let image = titleBadgeView.image { + width += 4.0 + image.size.width + titleBadgeView.frame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - image.size.height) * 0.5) + 1.0), size: image.size) + } + + return width + } + + func updateArea(size: CGSize, sideInset: CGFloat, transition: ContainedViewLayoutTransition) { + self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height)) + + self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -sideInset, bottom: 0.0, right: -sideInset) + } +} diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD b/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD index 6f94cb0c..5355cc42 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD @@ -24,6 +24,8 @@ swift_library( "//submodules/Components/ComponentDisplayAdapters", "//submodules/SearchUI", "//submodules/TelegramUI/Components/MoreHeaderButton", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift index 8e235da5..b85ffefe 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift @@ -9,6 +9,7 @@ import AppBundle import StoryPeerListComponent import TelegramCore import MoreHeaderButton +import GlassBackgroundComponent public final class HeaderNetworkStatusComponent: Component { public enum Content: Equatable { @@ -79,7 +80,6 @@ public final class ChatListHeaderComponent: Component { public let chatListTitle: NetworkStatusTitle? public let leftButton: AnyComponentWithIdentity? public let rightButtons: [AnyComponentWithIdentity] - public let backTitle: String? public let backPressed: (() -> Void)? public init( @@ -89,7 +89,6 @@ public final class ChatListHeaderComponent: Component { chatListTitle: NetworkStatusTitle?, leftButton: AnyComponentWithIdentity?, rightButtons: [AnyComponentWithIdentity], - backTitle: String?, backPressed: (() -> Void)? ) { self.title = title @@ -98,7 +97,6 @@ public final class ChatListHeaderComponent: Component { self.chatListTitle = chatListTitle self.leftButton = leftButton self.rightButtons = rightButtons - self.backTitle = backTitle self.backPressed = backPressed } @@ -121,7 +119,7 @@ public final class ChatListHeaderComponent: Component { if lhs.rightButtons != rhs.rightButtons { return false } - if lhs.backTitle != rhs.backTitle { + if (lhs.backPressed == nil) != (rhs.backPressed == nil) { return false } return true @@ -226,8 +224,6 @@ public final class ChatListHeaderComponent: Component { private let onPressed: () -> Void let arrowView: UIImageView - let titleOffsetContainer: UIView - let titleView: ImmediateTextView private var currentColor: UIColor? @@ -235,16 +231,11 @@ public final class ChatListHeaderComponent: Component { self.onPressed = onPressed self.arrowView = UIImageView() - self.titleOffsetContainer = UIView() - self.titleView = ImmediateTextView() super.init(frame: CGRect()) self.addSubview(self.arrowView) - self.addSubview(self.titleOffsetContainer) - self.titleOffsetContainer.addSubview(self.titleView) - self.highligthedChanged = { [weak self] highlighted in guard let self else { return @@ -266,37 +257,37 @@ public final class ChatListHeaderComponent: Component { self.onPressed() } - func update(title: String, theme: PresentationTheme, availableSize: CGSize, transition: ComponentTransition) -> CGSize { - let titleText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor) - let titleTextUpdated = self.titleView.attributedText != titleText - self.titleView.attributedText = titleText - let titleSize = self.titleView.updateLayout(CGSize(width: 100.0, height: 44.0)) - - self.accessibilityLabel = title + func update(theme: PresentationTheme, strings: PresentationStrings, availableSize: CGSize, transition: ComponentTransition) -> CGSize { + self.accessibilityLabel = strings.Common_Back self.accessibilityTraits = [.button] - if self.currentColor != theme.rootController.navigationBar.accentTextColor { - self.currentColor = theme.rootController.navigationBar.accentTextColor - self.arrowView.image = NavigationBarTheme.generateBackArrowImage(color: theme.rootController.navigationBar.accentTextColor) + if self.currentColor != theme.chat.inputPanel.panelControlColor { + self.currentColor = theme.chat.inputPanel.panelControlColor + let imageSize = CGSize(width: 44.0, height: 44.0) + let topRightPoint = CGPoint(x: 24.6, y: 14.0) + let centerPoint = CGPoint(x: 17.0, y: imageSize.height * 0.5) + self.arrowView.image = generateImage(imageSize, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(UIColor.white.cgColor) + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setLineJoin(.round) + context.move(to: topRightPoint) + context.addLine(to: centerPoint) + context.addLine(to: CGPoint(x: topRightPoint.x, y: size.height - topRightPoint.y)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) + self.arrowView.tintColor = theme.chat.inputPanel.panelControlColor } - let iconSpacing: CGFloat = 8.0 - let iconOffset: CGFloat = -7.0 - + let size = CGSize(width: 44.0, height: availableSize.height) let arrowSize = self.arrowView.image?.size ?? CGSize(width: 13.0, height: 22.0) - let arrowFrame = CGRect(origin: CGPoint(x: iconOffset - 1.0, y: floor((availableSize.height - arrowSize.height) / 2.0)), size: arrowSize) + let arrowFrame = arrowSize.centered(in: CGRect(origin: CGPoint(), size: size)) transition.setPosition(view: self.arrowView, position: arrowFrame.center) transition.setBounds(view: self.arrowView, bounds: CGRect(origin: CGPoint(), size: arrowFrame.size)) - let titleFrame = CGRect(origin: CGPoint(x: iconOffset - 3.0 + arrowSize.width + iconSpacing, y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize) - if titleTextUpdated { - self.titleView.frame = titleFrame - } else { - transition.setFrame(view: self.titleView, frame: titleFrame) - } - - return CGSize(width: iconOffset + arrowSize.width + iconSpacing + titleSize.width, height: availableSize.height) + return size } } @@ -305,9 +296,9 @@ public final class ChatListHeaderComponent: Component { let openStatusSetup: (UIView) -> Void let toggleIsLocked: () -> Void - let leftButtonOffsetContainer: UIView + let leftButtonsContainer: UIView var leftButtonViews: [AnyHashable: ComponentView] = [:] - let rightButtonOffsetContainer: UIView + let rightButtonsContainer: UIView var rightButtonViews: [AnyHashable: ComponentView] = [:] var backButtonView: BackButtonView? @@ -324,6 +315,9 @@ public final class ChatListHeaderComponent: Component { private(set) var centerContentOffsetX: CGFloat = 0.0 private(set) var centerContentOrigin: CGFloat = 0.0 + + private(set) var leftButtonsWidth: CGFloat = 0.0 + private(set) var rightButtonsWidth: CGFloat = 0.0 init( backPressed: @escaping () -> Void, @@ -334,8 +328,9 @@ public final class ChatListHeaderComponent: Component { self.openStatusSetup = openStatusSetup self.toggleIsLocked = toggleIsLocked - self.leftButtonOffsetContainer = UIView() - self.rightButtonOffsetContainer = UIView() + self.leftButtonsContainer = UIView() + self.rightButtonsContainer = UIView() + self.titleOffsetContainer = UIView() self.titleScaleContainer = UIView() @@ -345,8 +340,6 @@ public final class ChatListHeaderComponent: Component { self.addSubview(self.titleOffsetContainer) self.titleOffsetContainer.addSubview(self.titleScaleContainer) - self.addSubview(self.leftButtonOffsetContainer) - self.addSubview(self.rightButtonOffsetContainer) self.titleScaleContainer.addSubview(self.titleTextView) } @@ -393,12 +386,12 @@ public final class ChatListHeaderComponent: Component { transition.setSublayerTransform(view: self.titleOffsetContainer, transform: transform) } - func updateNavigationTransitionAsPrevious(nextView: ContentView, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) { - transition.setBounds(view: self.leftButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: fraction * self.bounds.width * 0.5, y: 0.0), size: self.leftButtonOffsetContainer.bounds.size), completion: { _ in - completion() - }) - transition.setAlpha(view: self.leftButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0)) - transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0)) + func updateNavigationTransitionAsPrevious(nextView: ContentView, width: CGFloat, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) { + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3) + alphaTransition.setAlpha(view: self.leftButtonsContainer, alpha: pow(1.0 - fraction, 2.0)) + alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: fraction * 10.0) + alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(1.0 - fraction, 2.0)) + alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: fraction * 10.0) if let backButtonView = self.backButtonView { transition.setBounds(view: backButtonView, bounds: CGRect(origin: CGPoint(x: fraction * self.bounds.width * 0.5, y: 0.0), size: backButtonView.bounds.size), completion: { _ in @@ -406,80 +399,53 @@ public final class ChatListHeaderComponent: Component { }) } - if let chatListTitleView = self.chatListTitleView, let nextBackButtonView = nextView.backButtonView { - let titleFrame = chatListTitleView.titleNode.view.convert(chatListTitleView.titleNode.bounds, to: self.titleOffsetContainer) - let backButtonTitleFrame = nextBackButtonView.convert(nextBackButtonView.titleView.frame, to: nextView) - - let totalOffset = titleFrame.minX - backButtonTitleFrame.minX - - transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: totalOffset * fraction, y: 0.0), size: self.titleOffsetContainer.bounds.size)) - transition.setAlpha(view: self.titleOffsetContainer, alpha: pow(1.0 - fraction, 2.0)) - } + let totalOffset = -width * fraction + + transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: totalOffset * fraction, y: 0.0), size: self.titleOffsetContainer.bounds.size)) + transition.setAlpha(view: self.titleOffsetContainer, alpha: pow(1.0 - fraction, 2.0)) } func updateNavigationTransitionAsNext(previousView: ContentView, storyPeerListView: StoryPeerListComponent.View?, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) { + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3) + transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -(1.0 - fraction) * self.bounds.width, y: 0.0), size: self.titleOffsetContainer.bounds.size), completion: { _ in completion() }) - transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(fraction, 2.0)) - transition.setBounds(view: self.rightButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: -(1.0 - fraction) * self.bounds.width, y: 0.0), size: self.rightButtonOffsetContainer.bounds.size)) - if let backButtonView = self.backButtonView { - transition.setScale(view: backButtonView.arrowView, scale: pow(max(0.001, fraction), 2.0)) - transition.setAlpha(view: backButtonView.arrowView, alpha: pow(fraction, 2.0)) - - if let storyPeerListView { - let previousTitleFrame = storyPeerListView.titleFrame() - let backButtonTitleFrame = backButtonView.convert(backButtonView.titleView.frame, to: self) - - let totalOffset = previousTitleFrame.minX - backButtonTitleFrame.minX - - transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -totalOffset * (1.0 - fraction), y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size)) - transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: pow(fraction, 2.0)) - } else if let previousChatListTitleView = previousView.chatListTitleView { - let previousTitleFrame = previousChatListTitleView.titleNode.view.convert(previousChatListTitleView.titleNode.bounds, to: previousView.titleOffsetContainer) - let backButtonTitleFrame = backButtonView.convert(backButtonView.titleView.frame, to: self) - - let totalOffset = previousTitleFrame.minX - backButtonTitleFrame.minX - - transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -totalOffset * (1.0 - fraction), y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size)) - transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: pow(fraction, 2.0)) - } - } + alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(fraction, 2.0)) + + alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: (1.0 - fraction) * 10.0) + alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: (1.0 - fraction) * 10.0) } func updateNavigationTransitionAsPreviousInplace(nextView: ContentView, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) { - transition.setBounds(view: self.leftButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.leftButtonOffsetContainer.bounds.size), completion: { _ in - }) - transition.setAlpha(view: self.leftButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0)) - transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0), completion: { _ in + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3) + + alphaTransition.setAlpha(view: self.leftButtonsContainer, alpha: pow(1.0 - fraction, 2.0)) + alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: fraction * 10.0) + alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(1.0 - fraction, 2.0), completion: { _ in completion() }) - - if let backButtonView = self.backButtonView { - transition.setBounds(view: backButtonView, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backButtonView.bounds.size), completion: { _ in - }) - } + alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: fraction * 10.0) transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.titleOffsetContainer.bounds.size)) transition.setAlpha(view: self.titleOffsetContainer, alpha: pow(1.0 - fraction, 2.0)) } func updateNavigationTransitionAsNextInplace(previousView: ContentView, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) { + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3) + transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.titleOffsetContainer.bounds.size), completion: { _ in completion() }) - transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(fraction, 2.0)) - transition.setBounds(view: self.rightButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.rightButtonOffsetContainer.bounds.size)) - if let backButtonView = self.backButtonView { - transition.setScale(view: backButtonView.arrowView, scale: pow(max(0.001, fraction), 2.0)) - transition.setAlpha(view: backButtonView.arrowView, alpha: pow(fraction, 2.0)) - - transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size)) - transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: pow(fraction, 2.0)) - } + alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: (1.0 - fraction) * 10.0) + alphaTransition.setAlpha(view: self.leftButtonsContainer, alpha: pow(fraction, 2.0)) + alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: (1.0 - fraction) * 10.0) + alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(fraction, 2.0)) } - func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: Content, backTitle: String?, sideInset: CGFloat, sideContentWidth: CGFloat, sideContentFraction: CGFloat, size: CGSize, transition: ComponentTransition) { + func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: Content, displayBackButton: Bool, sideInset: CGFloat, sideContentWidth: CGFloat, sideContentFraction: CGFloat, size: CGSize, transition: ComponentTransition) { + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3) + transition.setPosition(view: self.titleOffsetContainer, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5)) transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: self.titleOffsetContainer.bounds.origin, size: size)) @@ -490,11 +456,10 @@ public final class ChatListHeaderComponent: Component { let titleTextUpdated = self.titleTextView.attributedText != titleText self.titleTextView.attributedText = titleText - let buttonSpacing: CGFloat = 8.0 + let buttonSpacing: CGFloat = 0.0 + var nextLeftButtonX: CGFloat = 0.0 - var leftOffset = sideInset - - if let backTitle = backTitle { + if displayBackButton { var backButtonTransition = transition let backButtonView: BackButtonView if let current = self.backButtonView { @@ -508,11 +473,14 @@ public final class ChatListHeaderComponent: Component { self.backPressed() }) self.backButtonView = backButtonView - self.addSubview(backButtonView) + self.leftButtonsContainer.addSubview(backButtonView) } - let backButtonSize = backButtonView.update(title: backTitle, theme: theme, availableSize: CGSize(width: 100.0, height: size.height), transition: backButtonTransition) - backButtonTransition.setFrame(view: backButtonView, frame: CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - backButtonSize.height) / 2.0)), size: backButtonSize)) - leftOffset += backButtonSize.width + buttonSpacing + let backButtonSize = backButtonView.update(theme: theme, strings: strings, availableSize: CGSize(width: 100.0, height: size.height), transition: backButtonTransition) + backButtonTransition.setFrame(view: backButtonView, frame: CGRect(origin: CGPoint(x: nextLeftButtonX, y: floor((size.height - backButtonSize.height) / 2.0)), size: backButtonSize)) + if nextLeftButtonX != 0.0 { + nextLeftButtonX += buttonSpacing + } + nextLeftButtonX += backButtonSize.width } else if let backButtonView = self.backButtonView { self.backButtonView = nil backButtonView.removeFromSuperview() @@ -521,6 +489,10 @@ public final class ChatListHeaderComponent: Component { var validLeftButtons = Set() if let leftButton = content.leftButton { validLeftButtons.insert(leftButton.id) + + if nextLeftButtonX != 0.0 { + nextLeftButtonX += buttonSpacing + } var buttonTransition = transition var animateButtonIn = false @@ -541,23 +513,25 @@ public final class ChatListHeaderComponent: Component { }, containerSize: CGSize(width: 100.0, height: size.height) ) - let buttonFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: nextLeftButtonX, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize) if let buttonComponentView = buttonView.view { if buttonComponentView.superview == nil { - self.leftButtonOffsetContainer.addSubview(buttonComponentView) + self.leftButtonsContainer.addSubview(buttonComponentView) } buttonTransition.setFrame(view: buttonComponentView, frame: buttonFrame) if animateButtonIn { - transition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0) + alphaTransition.animateBlur(layer: buttonComponentView.layer, fromRadius: 10.0, toRadius: 0.0) + alphaTransition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0) } } - leftOffset = buttonFrame.maxX + buttonSpacing + nextLeftButtonX += buttonSize.width } var removeLeftButtons: [AnyHashable] = [] for (id, buttonView) in self.leftButtonViews { if !validLeftButtons.contains(id) { if let buttonComponentView = buttonView.view { - transition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in + alphaTransition.setBlur(layer: buttonComponentView.layer, radius: 10.0) + alphaTransition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in buttonComponentView?.removeFromSuperview() }) } @@ -568,10 +542,14 @@ public final class ChatListHeaderComponent: Component { self.leftButtonViews.removeValue(forKey: id) } - var rightOffset = size.width - sideInset + var nextRightButtonX: CGFloat = 0.0 var validRightButtons = Set() - for rightButton in content.rightButtons { + for rightButton in content.rightButtons.reversed() { validRightButtons.insert(rightButton.id) + + if nextRightButtonX != 0.0 { + nextRightButtonX += buttonSpacing + } var buttonTransition = transition var animateButtonIn = false @@ -592,23 +570,25 @@ public final class ChatListHeaderComponent: Component { }, containerSize: CGSize(width: 100.0, height: size.height) ) - let buttonFrame = CGRect(origin: CGPoint(x: rightOffset - buttonSize.width, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: nextRightButtonX, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize) if let buttonComponentView = buttonView.view { if buttonComponentView.superview == nil { - self.rightButtonOffsetContainer.addSubview(buttonComponentView) + self.rightButtonsContainer.addSubview(buttonComponentView) } buttonTransition.setFrame(view: buttonComponentView, frame: buttonFrame) if animateButtonIn { - transition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0) + alphaTransition.animateBlur(layer: buttonComponentView.layer, fromRadius: 10.0, toRadius: 0.0) + alphaTransition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0) } } - rightOffset = buttonFrame.minX - buttonSpacing + nextRightButtonX += buttonSize.width } var removeRightButtons: [AnyHashable] = [] for (id, buttonView) in self.rightButtonViews { if !validRightButtons.contains(id) { if let buttonComponentView = buttonView.view { - transition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in + alphaTransition.setBlur(layer: buttonComponentView.layer, radius: 10.0) + alphaTransition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in buttonComponentView?.removeFromSuperview() }) } @@ -618,8 +598,11 @@ public final class ChatListHeaderComponent: Component { for id in removeRightButtons { self.rightButtonViews.removeValue(forKey: id) } - - let commonInset: CGFloat = max(leftOffset, size.width - rightOffset) + + self.leftButtonsWidth = nextLeftButtonX + self.rightButtonsWidth = nextRightButtonX + + let commonInset: CGFloat = sideInset + max(nextLeftButtonX, nextRightButtonX) + 8.0 let remainingWidth = size.width - commonInset * 2.0 let titleTextSize = self.titleTextView.updateLayout(CGSize(width: remainingWidth, height: size.height)) @@ -662,10 +645,10 @@ public final class ChatListHeaderComponent: Component { } var centerContentLeftInset: CGFloat = 0.0 - centerContentLeftInset = leftOffset - 4.0 + centerContentLeftInset = nextLeftButtonX + 4.0 var centerContentRightInset: CGFloat = 0.0 - centerContentRightInset = size.width - rightOffset - 8.0 + centerContentRightInset = nextRightButtonX + 20.0 var centerContentWidth: CGFloat = 0.0 var centerContentOffsetX: CGFloat = 0.0 @@ -687,13 +670,11 @@ public final class ChatListHeaderComponent: Component { chatListTitleView.theme = theme chatListTitleView.strings = strings chatListTitleView.setTitle(chatListTitle, animated: false) - let titleContentRect = chatListTitleView.updateLayout(size: chatListTitleContentSize, clearBounds: CGRect(origin: CGPoint(), size: chatListTitleContentSize), transition: transition.containedViewLayoutTransition) + let titleContentRect = chatListTitleView.updateLayoutInternal(size: chatListTitleContentSize, transition: transition.containedViewLayoutTransition) centerContentWidth = floor((chatListTitleContentSize.width * 0.5 - titleContentRect.minX) * 2.0) - //sideWidth + centerWidth + centerOffset = size.width - //let centerOffset = -(size.width - (sideContentWidth + centerContentWidth)) * 0.5 + size.width * 0.5 let centerOffset = sideContentWidth * 0.5 - centerContentOffsetX = -max(0.0, centerOffset + titleContentRect.maxX - 2.0 - rightOffset) + centerContentOffsetX = -max(0.0, centerOffset + titleContentRect.maxX - 2.0 - (size.width - sideInset - nextRightButtonX)) chatListTitleView.openStatusSetup = { [weak self] sourceView in guard let self else { @@ -734,13 +715,18 @@ public final class ChatListHeaderComponent: Component { } } - public final class View: UIView, NavigationBarHeaderView { + public final class View: UIView { private var component: ChatListHeaderComponent? private weak var state: EmptyComponentState? private var primaryContentView: ContentView? private var secondaryContentView: ContentView? private var storyOffsetFraction: CGFloat = 0.0 + + private let leftButtonsContainer: UIView + private let rightButtonsContainer: UIView + private var leftButtonsBackgroundContainer: GlassBackgroundView? + private var rightButtonsBackgroundContainer: GlassBackgroundView? private let storyPeerListExternalState = StoryPeerListComponent.ExternalState() private var storyPeerList: ComponentView? @@ -753,6 +739,10 @@ public final class ChatListHeaderComponent: Component { } override init(frame: CGRect) { + self.leftButtonsContainer = UIView() + self.rightButtonsContainer = UIView() + self.rightButtonsContainer.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0) + super.init(frame: frame) self.storyOffsetFraction = 1.0 @@ -766,10 +756,6 @@ public final class ChatListHeaderComponent: Component { return self.effectiveContentView?.backButtonView?.arrowView } - public var backButtonTitleView: UIView? { - return self.effectiveContentView?.backButtonView?.titleView - } - public var rightButtonView: UIView? { return self.effectiveContentView?.rightButtonViews.first?.value.view } @@ -784,29 +770,6 @@ public final class ChatListHeaderComponent: Component { return self.effectiveContentView?.titleContentView?.view } - public func makeTransitionBackArrowView(accentColor: UIColor) -> UIView? { - if let backArrowView = self.backArrowView { - let view = UIImageView() - view.image = NavigationBar.backArrowImage(color: accentColor) - view.frame = backArrowView.convert(backArrowView.bounds, to: self) - return view - } else { - return nil - } - } - - public func makeTransitionBackButtonView(accentColor: UIColor) -> UIView? { - if let backButtonTitleView = self.backButtonTitleView as? ImmediateTextView { - let view = ImmediateTextView() - view.attributedText = NSAttributedString(string: backButtonTitleView.attributedText?.string ?? "", font: Font.regular(17.0), textColor: accentColor) - let _ = view.updateLayout(CGSize(width: 100.0, height: 100.0)) - view.frame = backButtonTitleView.convert(backButtonTitleView.bounds, to: self) - return view - } else { - return nil - } - } - public func storyPeerListView() -> StoryPeerListComponent.View? { return self.storyPeerList?.view as? StoryPeerListComponent.View } @@ -848,8 +811,8 @@ public final class ChatListHeaderComponent: Component { let previousComponent = self.component self.component = component + var primaryContentTransition = transition if var primaryContent = component.primaryContent { - var primaryContentTransition = transition let primaryContentView: ContentView if let current = self.primaryContentView { primaryContentView = current @@ -877,6 +840,8 @@ public final class ChatListHeaderComponent: Component { ) self.primaryContentView = primaryContentView self.addSubview(primaryContentView) + self.leftButtonsContainer.addSubview(primaryContentView.leftButtonsContainer) + self.rightButtonsContainer.addSubview(primaryContentView.rightButtonsContainer) } let sideContentWidth: CGFloat = 0.0 @@ -889,18 +854,19 @@ public final class ChatListHeaderComponent: Component { chatListTitle: nil, leftButton: primaryContent.leftButton, rightButtons: primaryContent.rightButtons, - backTitle: primaryContent.backTitle, backPressed: primaryContent.backPressed ) } - primaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: primaryContent, backTitle: primaryContent.backTitle, sideInset: component.sideInset, sideContentWidth: sideContentWidth, sideContentFraction: (1.0 - component.storiesFraction), size: availableSize, transition: primaryContentTransition) + primaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: primaryContent, displayBackButton: primaryContent.backPressed != nil, sideInset: component.sideInset, sideContentWidth: sideContentWidth, sideContentFraction: (1.0 - component.storiesFraction), size: availableSize, transition: primaryContentTransition) primaryContentTransition.setFrame(view: primaryContentView, frame: CGRect(origin: CGPoint(), size: availableSize)) primaryContentView.updateContentOffsetFraction(contentOffsetFraction: 1.0 - self.storyOffsetFraction, transition: primaryContentTransition) } else if let primaryContentView = self.primaryContentView { self.primaryContentView = nil primaryContentView.removeFromSuperview() + primaryContentView.leftButtonsContainer.removeFromSuperview() + primaryContentView.rightButtonsContainer.removeFromSuperview() } var storyListTransition = transition @@ -991,13 +957,16 @@ public final class ChatListHeaderComponent: Component { ) } + var secondaryContentTransition = transition + var secondaryContentIsAnimatingIn = false + var removedSecondaryContentView: ContentView? if let secondaryContent = component.secondaryContent { - var secondaryContentTransition = transition let secondaryContentView: ContentView if let current = self.secondaryContentView { secondaryContentView = current } else { secondaryContentTransition = .immediate + secondaryContentIsAnimatingIn = true secondaryContentView = ContentView( backPressed: { [weak self] in guard let self, let component = self.component else { @@ -1019,9 +988,15 @@ public final class ChatListHeaderComponent: Component { } ) self.secondaryContentView = secondaryContentView - self.addSubview(secondaryContentView) + if let primaryContentView = self.primaryContentView { + self.insertSubview(secondaryContentView, aboveSubview: primaryContentView) + } else { + self.addSubview(secondaryContentView) + } + self.leftButtonsContainer.addSubview(secondaryContentView.leftButtonsContainer) + self.rightButtonsContainer.addSubview(secondaryContentView.rightButtonsContainer) } - secondaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: secondaryContent, backTitle: component.primaryContent?.navigationBackTitle ?? component.primaryContent?.title, sideInset: component.sideInset, sideContentWidth: 0.0, sideContentFraction: 0.0, size: availableSize, transition: secondaryContentTransition) + secondaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: secondaryContent, displayBackButton: true, sideInset: component.sideInset, sideContentWidth: 0.0, sideContentFraction: 0.0, size: availableSize, transition: secondaryContentTransition) secondaryContentTransition.setFrame(view: secondaryContentView, frame: CGRect(origin: CGPoint(), size: availableSize)) secondaryContentView.updateContentOffsetFraction(contentOffsetFraction: 1.0 - self.storyOffsetFraction, transition: secondaryContentTransition) @@ -1032,7 +1007,7 @@ public final class ChatListHeaderComponent: Component { primaryContentView.updateNavigationTransitionAsPreviousInplace(nextView: secondaryContentView, fraction: 0.0, transition: .immediate, completion: {}) secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: 0.0, transition: .immediate, completion: {}) } else { - primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: 0.0, transition: .immediate, completion: {}) + primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, width: availableSize.width, fraction: 0.0, transition: .immediate, completion: {}) secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: 0.0, transition: .immediate, completion: {}) } } @@ -1041,26 +1016,33 @@ public final class ChatListHeaderComponent: Component { primaryContentView.updateNavigationTransitionAsPreviousInplace(nextView: secondaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {}) secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {}) } else { - primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {}) + primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, width: availableSize.width, fraction: component.secondaryTransition, transition: transition, completion: {}) secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: component.secondaryTransition, transition: transition, completion: {}) } } } else if let secondaryContentView = self.secondaryContentView { self.secondaryContentView = nil + removedSecondaryContentView = secondaryContentView if let primaryContentView = self.primaryContentView { if self.storyOffsetFraction < 0.8 { primaryContentView.updateNavigationTransitionAsPreviousInplace(nextView: secondaryContentView, fraction: 0.0, transition: transition, completion: {}) secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: 0.0, transition: transition, completion: { [weak secondaryContentView] in + secondaryContentView?.leftButtonsContainer.removeFromSuperview() + secondaryContentView?.rightButtonsContainer.removeFromSuperview() secondaryContentView?.removeFromSuperview() }) } else { - primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: 0.0, transition: transition, completion: {}) + primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, width: availableSize.width, fraction: 0.0, transition: transition, completion: {}) secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: 0.0, transition: transition, completion: { [weak secondaryContentView] in + secondaryContentView?.leftButtonsContainer.removeFromSuperview() + secondaryContentView?.rightButtonsContainer.removeFromSuperview() secondaryContentView?.removeFromSuperview() }) } } else { + secondaryContentView.leftButtonsContainer.removeFromSuperview() + secondaryContentView.rightButtonsContainer.removeFromSuperview() secondaryContentView.removeFromSuperview() } } @@ -1070,18 +1052,10 @@ public final class ChatListHeaderComponent: Component { self.addSubview(storyPeerListComponentView) } - //let storyPeerListMinOffset: CGFloat = -7.0 let storyPeerListMaxOffset: CGFloat = availableSize.height + 2.0 - //let storyPeerListPosition: CGFloat = storyPeerListMinOffset * (1.0 - component.storiesFraction) + storyPeerListMaxOffset * component.storiesFraction - var storiesX: CGFloat = 0.0 - if let nextBackButtonView = self.secondaryContentView?.backButtonView { - let backButtonTitleFrame = nextBackButtonView.convert(nextBackButtonView.titleView.frame, to: self) - let storyListTitleFrame = storyPeerListComponentView.titleFrame() - - storiesX += (backButtonTitleFrame.minX - storyListTitleFrame.minX) * component.secondaryTransition - } + storiesX -= availableSize.width * component.secondaryTransition storyListTransition.setFrame(view: storyPeerListComponentView, frame: CGRect(origin: CGPoint(x: storiesX, y: storyPeerListMaxOffset), size: CGSize(width: availableSize.width, height: 79.0))) @@ -1090,6 +1064,90 @@ public final class ChatListHeaderComponent: Component { let storyListAlpha: CGFloat = (1.0 - component.secondaryTransition) * storyListNormalAlpha storyListTransition.setAlpha(view: storyPeerListComponentView, alpha: storyListAlpha) } + + var leftButtonsEffectiveWidth: CGFloat = 0.0 + var rightButtonsEffectiveWidth: CGFloat = 0.0 + if let primaryContentView = self.primaryContentView, let secondaryContentView = self.secondaryContentView { + + leftButtonsEffectiveWidth = primaryContentView.leftButtonsWidth * (1.0 - component.secondaryTransition) + secondaryContentView.leftButtonsWidth * component.secondaryTransition + rightButtonsEffectiveWidth = primaryContentView.rightButtonsWidth * (1.0 - component.secondaryTransition) + secondaryContentView.rightButtonsWidth * component.secondaryTransition + + primaryContentTransition.setFrame(view: primaryContentView.leftButtonsContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.leftButtonsWidth), height: 44.0))) + secondaryContentTransition.setFrame(view: secondaryContentView.leftButtonsContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: max(44.0, secondaryContentView.leftButtonsWidth), height: 44.0))) + + primaryContentTransition.setFrame(view: primaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - primaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.rightButtonsWidth), height: 44.0))) + + if secondaryContentIsAnimatingIn { + secondaryContentView.rightButtonsContainer.frame = CGRect(origin: CGPoint(x: self.rightButtonsContainer.bounds.width - secondaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, secondaryContentView.rightButtonsWidth), height: 44.0)) + } + transition.setFrame(view: secondaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - secondaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, secondaryContentView.rightButtonsWidth), height: 44.0))) + } else if let primaryContentView = self.primaryContentView { + leftButtonsEffectiveWidth = primaryContentView.leftButtonsWidth + rightButtonsEffectiveWidth = primaryContentView.rightButtonsWidth + + primaryContentTransition.setFrame(view: primaryContentView.leftButtonsContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.leftButtonsWidth), height: 44.0))) + primaryContentTransition.setFrame(view: primaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - primaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.rightButtonsWidth), height: 44.0))) + + if let removedSecondaryContentView { + transition.setFrame(view: removedSecondaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - removedSecondaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, removedSecondaryContentView.rightButtonsWidth), height: 44.0))) + } + } + + if leftButtonsEffectiveWidth != 0.0 { + let leftButtonsBackgroundContainer: GlassBackgroundView + var leftButtonsBackgroundContainerTransition = transition + if let current = self.leftButtonsBackgroundContainer { + leftButtonsBackgroundContainer = current + } else { + leftButtonsBackgroundContainerTransition = leftButtonsBackgroundContainerTransition.withAnimation(.none) + leftButtonsBackgroundContainer = GlassBackgroundView() + self.leftButtonsBackgroundContainer = leftButtonsBackgroundContainer + self.addSubview(leftButtonsBackgroundContainer) + leftButtonsBackgroundContainer.contentView.addSubview(self.leftButtonsContainer) + } + let leftButtonsContainerFrame = CGRect(origin: CGPoint(x: component.sideInset, y: 0.0), size: CGSize(width: max(44.0, leftButtonsEffectiveWidth), height: 44.0)) + leftButtonsBackgroundContainerTransition.setFrame(view: leftButtonsBackgroundContainer, frame: leftButtonsContainerFrame) + leftButtonsBackgroundContainer.update(size: leftButtonsContainerFrame.size, cornerRadius: leftButtonsContainerFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: leftButtonsBackgroundContainerTransition) + leftButtonsBackgroundContainerTransition.setFrame(view: self.leftButtonsContainer, frame: CGRect(origin: CGPoint(), size: leftButtonsContainerFrame.size)) + } else { + if let leftButtonsBackgroundContainer = self.leftButtonsBackgroundContainer { + self.leftButtonsBackgroundContainer = nil + transition.setAlpha(view: leftButtonsBackgroundContainer, alpha: 0.0, completion: { [weak leftButtonsBackgroundContainer] _ in + leftButtonsBackgroundContainer?.removeFromSuperview() + }) + } + } + + if rightButtonsEffectiveWidth != 0.0 { + let rightButtonsBackgroundContainer: GlassBackgroundView + var rightButtonsBackgroundContainerTransition = transition + + let rightButtonsContainerFrame = CGRect(origin: CGPoint(x: availableSize.width - component.sideInset - max(44.0, rightButtonsEffectiveWidth), y: 0.0), size: CGSize(width: max(44.0, rightButtonsEffectiveWidth), height: 44.0)) + + if let current = self.rightButtonsBackgroundContainer { + rightButtonsBackgroundContainer = current + } else { + rightButtonsBackgroundContainerTransition = rightButtonsBackgroundContainerTransition.withAnimation(.none) + rightButtonsBackgroundContainer = GlassBackgroundView() + self.rightButtonsBackgroundContainer = rightButtonsBackgroundContainer + self.addSubview(rightButtonsBackgroundContainer) + rightButtonsBackgroundContainer.contentView.addSubview(self.rightButtonsContainer) + + rightButtonsBackgroundContainer.update(size: rightButtonsContainerFrame.size, cornerRadius: rightButtonsContainerFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, isVisible: false, transition: .immediate) + } + rightButtonsBackgroundContainerTransition.setFrame(view: rightButtonsBackgroundContainer, frame: rightButtonsContainerFrame) + rightButtonsBackgroundContainer.update(size: rightButtonsContainerFrame.size, cornerRadius: rightButtonsContainerFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + rightButtonsBackgroundContainerTransition.setFrame(view: self.rightButtonsContainer, frame: CGRect(origin: CGPoint(), size: rightButtonsContainerFrame.size)) + } else { + if let rightButtonsBackgroundContainer = self.rightButtonsBackgroundContainer { + self.rightButtonsBackgroundContainer = nil + + rightButtonsBackgroundContainer.update(size: rightButtonsBackgroundContainer.bounds.size, cornerRadius: rightButtonsBackgroundContainer.bounds.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, isVisible: false, transition: transition) + transition.attachAnimation(view: rightButtonsBackgroundContainer, id: "remove", completion: { [weak rightButtonsBackgroundContainer] _ in + rightButtonsBackgroundContainer?.removeFromSuperview() + }) + } + } return availableSize } @@ -1119,251 +1177,3 @@ public final class ChatListHeaderComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } - -public final class NavigationButtonComponentEnvironment: Equatable { - public let theme: PresentationTheme - - public init(theme: PresentationTheme) { - self.theme = theme - } - - public static func ==(lhs: NavigationButtonComponentEnvironment, rhs: NavigationButtonComponentEnvironment) -> Bool { - if lhs.theme != rhs.theme { - return false - } - return true - } -} - -public final class NavigationButtonComponent: Component { - public typealias EnvironmentType = NavigationButtonComponentEnvironment - - public enum Content: Equatable { - case text(title: String, isBold: Bool) - case more - case icon(imageName: String) - case proxy(status: ChatTitleProxyStatus) - } - - public let content: Content - public let pressed: (UIView) -> Void - public let contextAction: ((UIView, ContextGesture?) -> Void)? - - public init( - content: Content, - pressed: @escaping (UIView) -> Void, - contextAction: ((UIView, ContextGesture?) -> Void)? = nil - ) { - self.content = content - self.pressed = pressed - self.contextAction = contextAction - } - - public static func ==(lhs: NavigationButtonComponent, rhs: NavigationButtonComponent) -> Bool { - if lhs.content != rhs.content { - return false - } - return true - } - - public final class View: HighlightTrackingButton { - private var textView: ImmediateTextView? - - private var iconView: UIImageView? - private var iconImageName: String? - - private var proxyNode: ChatTitleProxyNode? - - private var moreButton: MoreHeaderButton? - - private var component: NavigationButtonComponent? - private var theme: PresentationTheme? - - override init(frame: CGRect) { - super.init(frame: frame) - - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - - self.highligthedChanged = { [weak self] highlighted in - guard let self else { - return - } - if highlighted { - self.textView?.alpha = 0.6 - self.proxyNode?.alpha = 0.6 - self.iconView?.alpha = 0.6 - } else { - self.textView?.alpha = 1.0 - self.textView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) - - self.proxyNode?.alpha = 1.0 - self.proxyNode?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) - - self.iconView?.alpha = 1.0 - self.iconView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) - } - } - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc private func pressed() { - self.component?.pressed(self) - } - - func update(component: NavigationButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.component = component - - let theme = environment[NavigationButtonComponentEnvironment.self].value.theme - var themeUpdated = false - if self.theme !== theme { - self.theme = theme - themeUpdated = true - } - - let iconOffset: CGFloat = 4.0 - - var textString: NSAttributedString? - var imageName: String? - var proxyStatus: ChatTitleProxyStatus? - var isMore: Bool = false - - switch component.content { - case let .text(title, isBold): - textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor) - case .more: - isMore = true - case let .icon(imageNameValue): - imageName = imageNameValue - case let .proxy(status): - proxyStatus = status - } - - var size = CGSize(width: 0.0, height: availableSize.height) - - if let textString = textString { - let textView: ImmediateTextView - if let current = self.textView { - textView = current - } else { - textView = ImmediateTextView() - textView.isUserInteractionEnabled = false - self.textView = textView - self.addSubview(textView) - } - - textView.attributedText = textString - let textSize = textView.updateLayout(availableSize) - size.width = textSize.width - - textView.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((availableSize.height - textSize.height) / 2.0)), size: textSize) - } else if let textView = self.textView { - self.textView = nil - textView.removeFromSuperview() - } - - if let imageName = imageName { - let iconView: UIImageView - if let current = self.iconView { - iconView = current - } else { - iconView = UIImageView() - iconView.isUserInteractionEnabled = false - self.iconView = iconView - self.addSubview(iconView) - } - if self.iconImageName != imageName || themeUpdated { - self.iconImageName = imageName - iconView.image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.rootController.navigationBar.accentTextColor) - } - - if let iconSize = iconView.image?.size { - size.width = iconSize.width - - iconView.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - iconSize.height) / 2.0)), size: iconSize) - } - } else if let iconView = self.iconView { - self.iconView = nil - iconView.removeFromSuperview() - self.iconImageName = nil - } - - if let proxyStatus = proxyStatus { - let proxyNode: ChatTitleProxyNode - if let current = self.proxyNode { - proxyNode = current - } else { - proxyNode = ChatTitleProxyNode(theme: theme) - proxyNode.isUserInteractionEnabled = false - self.proxyNode = proxyNode - self.addSubnode(proxyNode) - } - - let proxySize = CGSize(width: 30.0, height: 30.0) - size.width = proxySize.width - - proxyNode.theme = theme - proxyNode.status = proxyStatus - - proxyNode.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - proxySize.height) / 2.0)), size: proxySize) - } else if let proxyNode = self.proxyNode { - self.proxyNode = nil - proxyNode.removeFromSupernode() - } - - if isMore { - let moreButton: MoreHeaderButton - if let current = self.moreButton, !themeUpdated { - moreButton = current - } else { - if let moreButton = self.moreButton { - moreButton.removeFromSupernode() - self.moreButton = nil - } - - moreButton = MoreHeaderButton(color: theme.rootController.navigationBar.buttonColor) - moreButton.isUserInteractionEnabled = true - moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor))) - moreButton.onPressed = { [weak self] in - guard let self, let component = self.component else { - return - } - self.moreButton?.play() - component.pressed(self) - } - moreButton.contextAction = { [weak self] sourceNode, gesture in - guard let self, let component = self.component else { - return - } - self.moreButton?.play() - component.contextAction?(self, gesture) - } - self.moreButton = moreButton - self.addSubnode(moreButton) - } - - let buttonSize = CGSize(width: 26.0, height: 44.0) - size.width = buttonSize.width - - moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor))) - - moreButton.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - buttonSize.height) / 2.0)), size: buttonSize) - } else if let moreButton = self.moreButton { - self.moreButton = nil - moreButton.removeFromSupernode() - } - - return size - } - } - - public func makeView() -> View { - return View(frame: CGRect()) - } - - public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift index 549c6fb4..4a9be3f3 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift @@ -9,6 +9,16 @@ import SearchUI import AccountContext import TelegramCore import StoryPeerListComponent +import EdgeEffect +import GlassBackgroundComponent + +private func searchScrollHeightValue() -> CGFloat { + return 54.0 +} + +private func storiesHeightValue() -> CGFloat { + return 96.0 +} public final class ChatListNavigationBar: Component { public final class AnimationHint { @@ -20,20 +30,37 @@ public final class ChatListNavigationBar: Component { self.crossfadeStoryPeers = crossfadeStoryPeers } } + + public struct Search: Equatable { + public var isEnabled: Bool + + public init(isEnabled: Bool) { + self.isEnabled = isEnabled + } + } + + public struct ActiveSearch: Equatable { + public var isExternal: Bool + + public init(isExternal: Bool) { + self.isExternal = isExternal + } + } public let context: AccountContext public let theme: PresentationTheme public let strings: PresentationStrings public let statusBarHeight: CGFloat public let sideInset: CGFloat - public let isSearchActive: Bool - public let isSearchEnabled: Bool + public let search: Search? + public let activeSearch: ActiveSearch? public let primaryContent: ChatListHeaderComponent.Content? public let secondaryContent: ChatListHeaderComponent.Content? public let secondaryTransition: CGFloat public let storySubscriptions: EngineStorySubscriptions? public let storiesIncludeHidden: Bool public let uploadProgress: [EnginePeer.Id: Float] + public let headerPanels: AnyComponent? public let tabsNode: ASDisplayNode? public let tabsNodeIsSearch: Bool public let accessoryPanelContainer: ASDisplayNode? @@ -48,14 +75,15 @@ public final class ChatListNavigationBar: Component { strings: PresentationStrings, statusBarHeight: CGFloat, sideInset: CGFloat, - isSearchActive: Bool, - isSearchEnabled: Bool, + search: Search?, + activeSearch: ActiveSearch?, primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?, secondaryTransition: CGFloat, storySubscriptions: EngineStorySubscriptions?, storiesIncludeHidden: Bool, uploadProgress: [EnginePeer.Id: Float], + headerPanels: AnyComponent?, tabsNode: ASDisplayNode?, tabsNodeIsSearch: Bool, accessoryPanelContainer: ASDisplayNode?, @@ -69,14 +97,15 @@ public final class ChatListNavigationBar: Component { self.strings = strings self.statusBarHeight = statusBarHeight self.sideInset = sideInset - self.isSearchActive = isSearchActive - self.isSearchEnabled = isSearchEnabled + self.search = search + self.activeSearch = activeSearch self.primaryContent = primaryContent self.secondaryContent = secondaryContent self.secondaryTransition = secondaryTransition self.storySubscriptions = storySubscriptions self.storiesIncludeHidden = storiesIncludeHidden self.uploadProgress = uploadProgress + self.headerPanels = headerPanels self.tabsNode = tabsNode self.tabsNodeIsSearch = tabsNodeIsSearch self.accessoryPanelContainer = accessoryPanelContainer @@ -102,10 +131,10 @@ public final class ChatListNavigationBar: Component { if lhs.sideInset != rhs.sideInset { return false } - if lhs.isSearchActive != rhs.isSearchActive { + if lhs.search != rhs.search { return false } - if lhs.isSearchEnabled != rhs.isSearchEnabled { + if lhs.activeSearch != rhs.activeSearch { return false } if lhs.primaryContent != rhs.primaryContent { @@ -126,6 +155,9 @@ public final class ChatListNavigationBar: Component { if lhs.uploadProgress != rhs.uploadProgress { return false } + if lhs.headerPanels != rhs.headerPanels { + return false + } if lhs.tabsNode !== rhs.tabsNode { return false } @@ -149,15 +181,13 @@ public final class ChatListNavigationBar: Component { } } - public static let searchScrollHeight: CGFloat = 52.0 - public static let storiesScrollHeight: CGFloat = { - return 83.0 - }() + public static let searchScrollHeight: CGFloat = searchScrollHeightValue() + public static let storiesScrollHeight: CGFloat = storiesHeightValue() public final class View: UIView { - private let backgroundView: BlurredBackgroundView - private let separatorLayer: SimpleLayer + private let edgeEffectView: EdgeEffectView + private let headerBackgroundContainer: GlassBackgroundContainerView public let headerContent = ComponentView() public private(set) var searchContentNode: NavigationBarSearchContentNode? @@ -178,23 +208,36 @@ public final class ChatListNavigationBar: Component { public private(set) var storiesUnlocked: Bool = false + private let bottomContentsContainer: UIView + private var tabsNode: ASDisplayNode? private var tabsNodeIsSearch: Bool = false private weak var disappearingTabsView: UIView? private var disappearingTabsViewSearch: Bool = false + private var headerPanelsView: ComponentView? + private var disappearingHeaderPanels: ComponentView? + public var headerPanels: UIView? { + return self.headerPanelsView?.view + } + private var currentHeaderComponent: ChatListHeaderComponent? + + private var currentHeight: CGFloat = 0.0 override public init(frame: CGRect) { - self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) - self.backgroundView.layer.anchorPoint = CGPoint(x: 0.0, y: 1.0) - self.separatorLayer = SimpleLayer() - self.separatorLayer.anchorPoint = CGPoint() + self.edgeEffectView = EdgeEffectView() + + self.headerBackgroundContainer = GlassBackgroundContainerView() + self.headerBackgroundContainer.layer.anchorPoint = CGPoint() + + self.bottomContentsContainer = UIView() + self.bottomContentsContainer.layer.anchorPoint = CGPoint() super.init(frame: frame) - self.addSubview(self.backgroundView) - self.layer.addSublayer(self.separatorLayer) + self.addSubview(self.edgeEffectView) + self.addSubview(self.bottomContentsContainer) } required init?(coder: NSCoder) { @@ -202,7 +245,7 @@ public final class ChatListNavigationBar: Component { } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if !self.backgroundView.frame.contains(point) { + if point.y >= self.currentHeight { return nil } @@ -248,7 +291,12 @@ public final class ChatListNavigationBar: Component { let searchOffsetDistance: CGFloat = ChatListNavigationBar.searchScrollHeight - let minContentOffset: CGFloat = ChatListNavigationBar.searchScrollHeight + let minContentOffset: CGFloat + if component.search != nil { + minContentOffset = ChatListNavigationBar.searchScrollHeight + } else { + minContentOffset = 0.0 + } let clippedScrollOffset = min(minContentOffset, offset) if self.clippedScrollOffset == clippedScrollOffset && !self.hasDeferredScrollOffset && !forceUpdate && !allowAvatarsExpansionUpdated { @@ -259,71 +307,105 @@ public final class ChatListNavigationBar: Component { let visibleSize = CGSize(width: currentLayout.size.width, height: max(0.0, currentLayout.size.height - clippedScrollOffset)) - let previousHeight = self.separatorLayer.position.y + let previousHeight = self.currentHeight + + self.currentHeight = visibleSize.height - self.backgroundView.update(size: CGSize(width: visibleSize.width, height: 1000.0), transition: transition.containedViewLayoutTransition) - - transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: CGPoint(), size: CGSize(width: visibleSize.width, height: 1000.0))) - transition.animatePosition(view: self.backgroundView, from: CGPoint(x: 0.0, y: -visibleSize.height + self.backgroundView.layer.position.y), to: CGPoint(), additive: true) - self.backgroundView.layer.position = CGPoint(x: 0.0, y: visibleSize.height) - - transition.setFrameWithAdditivePosition(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: UIScreenPixel))) - - let searchContentNode: NavigationBarSearchContentNode - if let current = self.searchContentNode { - searchContentNode = current - - if themeUpdated { + var embeddedSearchBarExpansionHeight: CGFloat = 0.0 + var searchFrameValue: CGRect? + if let search = component.search { + let searchContentNode: NavigationBarSearchContentNode + if let current = self.searchContentNode { + searchContentNode = current + + if themeUpdated { + let placeholder: String + let compactPlaceholder: String + + placeholder = component.strings.Common_Search + compactPlaceholder = component.strings.Common_Search + + searchContentNode.updateThemeAndPlaceholder(theme: component.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder) + } + } else { let placeholder: String let compactPlaceholder: String placeholder = component.strings.Common_Search compactPlaceholder = component.strings.Common_Search - searchContentNode.updateThemeAndPlaceholder(theme: component.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder) - } - } else { - let placeholder: String - let compactPlaceholder: String - - placeholder = component.strings.Common_Search - compactPlaceholder = component.strings.Common_Search - - searchContentNode = NavigationBarSearchContentNode( - theme: component.theme, - placeholder: placeholder, - compactPlaceholder: compactPlaceholder, - activate: { [weak self] in - guard let self, let component = self.component, let searchContentNode = self.searchContentNode else { - return + searchContentNode = NavigationBarSearchContentNode( + theme: component.theme, + placeholder: placeholder, + compactPlaceholder: compactPlaceholder, + activate: { [weak self] in + guard let self, let component = self.component, let searchContentNode = self.searchContentNode else { + return + } + component.activateSearch(searchContentNode) } - component.activateSearch(searchContentNode) + ) + searchContentNode.view.layer.anchorPoint = CGPoint() + self.searchContentNode = searchContentNode + self.addSubview(searchContentNode.view) + } + + let searchSize = CGSize(width: currentLayout.size.width, height: navigationBarSearchContentHeight) + var searchFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - searchSize.height - self.bottomContentsContainer.bounds.height - 2.0), size: searchSize) + if let activeSearch = component.activeSearch, !activeSearch.isExternal { + searchFrame.origin.y = component.statusBarHeight + 8.0 + } + if component.tabsNode != nil { + searchFrame.origin.y -= 40.0 + } + if let activeSearch = component.activeSearch { + if !activeSearch.isExternal { + searchFrame.origin.y -= component.accessoryPanelContainerHeight } - ) - searchContentNode.view.layer.anchorPoint = CGPoint() - self.searchContentNode = searchContentNode - self.addSubview(searchContentNode.view) + } else { + searchFrame.origin.y -= component.accessoryPanelContainerHeight + } + + let clippedSearchOffset = max(0.0, min(clippedScrollOffset, searchOffsetDistance)) + let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance + searchContentNode.expansionProgress = 1.0 - searchOffsetFraction + embeddedSearchBarExpansionHeight = 60.0 - floorToScreenPixels((1.0 - searchOffsetFraction) * searchSize.height) + if searchOffsetFraction > 0.0 { + searchFrame.origin.y -= (60.0 - 44.0) * 0.5 * searchOffsetFraction + } + + searchFrameValue = searchFrame + transition.setFrameWithAdditivePosition(view: searchContentNode.view, frame: searchFrame) + + let _ = searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition) + + var searchAlpha: CGFloat = search.isEnabled ? 1.0 : 0.5 + if let activeSearch = component.activeSearch, activeSearch.isExternal { + searchAlpha = 0.0 + } + transition.setAlpha(view: searchContentNode.view, alpha: searchAlpha) + searchContentNode.isUserInteractionEnabled = search.isEnabled + } else { + if let searchContentNode = self.searchContentNode { + self.searchContentNode = nil + searchContentNode.view.removeFromSuperview() + } } - let searchSize = CGSize(width: currentLayout.size.width - 6.0 * 2.0, height: navigationBarSearchContentHeight) - var searchFrame = CGRect(origin: CGPoint(x: 6.0, y: visibleSize.height - searchSize.height), size: searchSize) - if component.tabsNode != nil { - searchFrame.origin.y -= 40.0 - } - if !component.isSearchActive { - searchFrame.origin.y -= component.accessoryPanelContainerHeight + let edgeEffectHeight: CGFloat = currentLayout.size.height + 20.0 + var edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: currentLayout.size.width, height: edgeEffectHeight)) + if component.search != nil { + if component.activeSearch != nil { + edgeEffectFrame.origin.y -= 16.0 + } else { + edgeEffectFrame.origin.y -= embeddedSearchBarExpansionHeight + } + } else if component.activeSearch != nil { + edgeEffectFrame.origin.y -= 16.0 } + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: component.theme.list.plainBackgroundColor, blur: true, alpha: 0.55, rect: edgeEffectFrame, edge: .top, edgeSize: 40.0, transition: transition) - let clippedSearchOffset = max(0.0, min(clippedScrollOffset, searchOffsetDistance)) - let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance - searchContentNode.expansionProgress = 1.0 - searchOffsetFraction - - transition.setFrameWithAdditivePosition(view: searchContentNode.view, frame: searchFrame) - - searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition) - - transition.setAlpha(view: searchContentNode.view, alpha: component.isSearchEnabled ? 1.0 : 0.5) - searchContentNode.isUserInteractionEnabled = component.isSearchEnabled let headerTransition = transition @@ -405,28 +487,31 @@ public final class ChatListNavigationBar: Component { containerSize: CGSize(width: currentLayout.size.width, height: 44.0) ) let headerContentY: CGFloat - if component.isSearchActive { + if component.activeSearch != nil { headerContentY = -headerContentSize.height } else { if component.statusBarHeight < 1.0 { headerContentY = 0.0 } else { - headerContentY = component.statusBarHeight + 5.0 + headerContentY = component.statusBarHeight + 10.0 } } let headerContentFrame = CGRect(origin: CGPoint(x: 0.0, y: headerContentY), size: headerContentSize) if let headerContentView = self.headerContent.view { if headerContentView.superview == nil { headerContentView.layer.anchorPoint = CGPoint() - self.addSubview(headerContentView) + self.addSubview(self.headerBackgroundContainer) + self.headerBackgroundContainer.contentView.addSubview(headerContentView) } - transition.setFrameWithAdditivePosition(view: headerContentView, frame: headerContentFrame) + transition.setFrameWithAdditivePosition(view: self.headerBackgroundContainer, frame: headerContentFrame) + self.headerBackgroundContainer.update(size: headerContentFrame.size, isDark: component.theme.overallDarkAppearance, transition: transition) + transition.setFrameWithAdditivePosition(view: headerContentView, frame: CGRect(origin: CGPoint(), size: headerContentFrame.size)) - if component.isSearchActive != (headerContentView.alpha == 0.0) { - headerContentView.alpha = component.isSearchActive ? 0.0 : 1.0 + if (component.activeSearch != nil) != (headerContentView.alpha == 0.0) { + headerContentView.alpha = component.activeSearch != nil ? 0.0 : 1.0 if !transition.animation.isImmediate { - if component.isSearchActive { + if component.activeSearch != nil { headerContentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.14) } else { headerContentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) @@ -435,6 +520,18 @@ public final class ChatListNavigationBar: Component { } } + let bottomContentsContainerPosition: CGPoint + if let activeSearch = component.activeSearch { + if let searchFrameValue, !activeSearch.isExternal { + bottomContentsContainerPosition = CGPoint(x: 0.0, y: searchFrameValue.maxY - 8.0) + } else { + bottomContentsContainerPosition = CGPoint(x: 0.0, y: -self.bottomContentsContainer.bounds.height) + } + } else { + bottomContentsContainerPosition = CGPoint(x: 0.0, y: visibleSize.height - self.bottomContentsContainer.bounds.height) + } + transition.setPosition(view: self.bottomContentsContainer, position: bottomContentsContainerPosition) + if component.tabsNode !== self.tabsNode { if let tabsNode = self.tabsNode { tabsNode.layer.anchorPoint = CGPoint() @@ -455,7 +552,8 @@ public final class ChatListNavigationBar: Component { } var tabsFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: 46.0)) - if !component.isSearchActive { + if component.activeSearch != nil { + } else { tabsFrame.origin.y -= component.accessoryPanelContainerHeight } if component.tabsNode != nil { @@ -463,7 +561,8 @@ public final class ChatListNavigationBar: Component { } var accessoryPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: component.accessoryPanelContainerHeight)) - if !component.isSearchActive { + if component.activeSearch != nil { + } else { accessoryPanelContainerFrame.origin.y -= component.accessoryPanelContainerHeight } @@ -528,14 +627,15 @@ public final class ChatListNavigationBar: Component { strings: component.strings, statusBarHeight: component.statusBarHeight, sideInset: component.sideInset, - isSearchActive: component.isSearchActive, - isSearchEnabled: component.isSearchEnabled, + search: component.search, + activeSearch: component.activeSearch, primaryContent: component.primaryContent, secondaryContent: component.secondaryContent, secondaryTransition: component.secondaryTransition, storySubscriptions: component.storySubscriptions, storiesIncludeHidden: component.storiesIncludeHidden, uploadProgress: storyUploadProgress, + headerPanels: component.headerPanels, tabsNode: component.tabsNode, tabsNodeIsSearch: component.tabsNodeIsSearch, accessoryPanelContainer: component.accessoryPanelContainer, @@ -575,8 +675,6 @@ public final class ChatListNavigationBar: Component { } func update(component: ChatListNavigationBar, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - let themeUpdated = self.component?.theme !== component.theme - var uploadProgressUpdated = false var storySubscriptionsUpdated = false if let previousComponent = self.component { @@ -591,32 +689,85 @@ public final class ChatListNavigationBar: Component { self.component = component self.state = state - if themeUpdated { - self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.separatorLayer.backgroundColor = component.theme.rootController.navigationBar.separatorColor.cgColor - } - var contentHeight = component.statusBarHeight if component.statusBarHeight >= 1.0 { contentHeight += 3.0 } - contentHeight += 44.0 - - if component.isSearchActive { - if component.statusBarHeight < 1.0 { - contentHeight += 8.0 + if let activeSearch = component.activeSearch { + if !activeSearch.isExternal { + contentHeight += navigationBarSearchContentHeight } } else { - contentHeight += navigationBarSearchContentHeight + contentHeight += 44.0 + contentHeight += 9.0 + + if component.search != nil { + contentHeight += navigationBarSearchContentHeight + 2.0 + } } - if component.tabsNode != nil { - contentHeight += 40.0 + var headersContentHeight: CGFloat = 0.0 + if let disappearingHeaderPanelsView = self.disappearingHeaderPanels?.view { + let headerPanelsFrame = CGRect(origin: CGPoint(x: 0.0, y: headersContentHeight), size: disappearingHeaderPanelsView.bounds.size) + transition.setFrame(view: disappearingHeaderPanelsView, frame: headerPanelsFrame) + } + if let headerPanels = component.headerPanels { + let headerPanelsView: ComponentView + var headerPanelsTransition = transition + if let current = self.headerPanelsView { + headerPanelsView = current + } else { + headerPanelsTransition = headerPanelsTransition.withAnimation(.none) + headerPanelsView = ComponentView() + self.headerPanelsView = headerPanelsView + } + let headerPanelsSize = headerPanelsView.update( + transition: headerPanelsTransition, + component: headerPanels, + environment: {}, + containerSize: CGSize(width: availableSize.width - component.sideInset * 2.0, height: 10000.0) + ) + let headerPanelsFrame = CGRect(origin: CGPoint(x: component.sideInset, y: headersContentHeight), size: headerPanelsSize) + if let headerPanelsComponentView = headerPanelsView.view { + if headerPanelsComponentView.superview == nil { + self.bottomContentsContainer.addSubview(headerPanelsComponentView) + transition.animateAlpha(view: headerPanelsComponentView, from: 0.0, to: 1.0) + } + headerPanelsTransition.setFrame(view: headerPanelsComponentView, frame: headerPanelsFrame) + } + headersContentHeight += headerPanelsSize.height + } else if let headerPanelsView = self.headerPanelsView { + self.headerPanelsView = nil + self.disappearingHeaderPanels = headerPanelsView + + if let headerPanelsComponentView = headerPanelsView.view { + transition.setAlpha(view: headerPanelsComponentView, alpha: 0.0, completion: { [weak self, weak headerPanelsComponentView] _ in + guard let self, let headerPanelsComponentView else { + return + } + headerPanelsComponentView.removeFromSuperview() + if self.disappearingHeaderPanels?.view === headerPanelsComponentView { + self.disappearingHeaderPanels = nil + } + }) + } + } + headersContentHeight += 3.0 + transition.setBounds(view: self.bottomContentsContainer, bounds: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: headersContentHeight))) + + if let activeSearch = component.activeSearch, !activeSearch.isExternal { + transition.setAlpha(view: self.bottomContentsContainer, alpha: 0.0) + } else { + transition.setAlpha(view: self.bottomContentsContainer, alpha: 1.0) } - if component.accessoryPanelContainer != nil && !component.isSearchActive { - contentHeight += component.accessoryPanelContainerHeight + if component.activeSearch == nil { + contentHeight += headersContentHeight + + if component.tabsNode != nil { + contentHeight += 40.0 + } } let size = CGSize(width: availableSize.width, height: contentHeight) diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift new file mode 100644 index 00000000..5ebc8200 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift @@ -0,0 +1,254 @@ +import Foundation +import UIKit +import ComponentFlow +import ChatListTitleView +import TelegramPresentationData +import Display +import MoreHeaderButton + +public final class NavigationButtonComponentEnvironment: Equatable { + public let theme: PresentationTheme + + public init(theme: PresentationTheme) { + self.theme = theme + } + + public static func ==(lhs: NavigationButtonComponentEnvironment, rhs: NavigationButtonComponentEnvironment) -> Bool { + if lhs.theme != rhs.theme { + return false + } + return true + } +} + +public final class NavigationButtonComponent: Component { + public typealias EnvironmentType = NavigationButtonComponentEnvironment + + public enum Content: Equatable { + case text(title: String, isBold: Bool) + case more + case icon(imageName: String) + case proxy(status: ChatTitleProxyStatus) + } + + public let content: Content + public let pressed: (UIView) -> Void + public let contextAction: ((UIView, ContextGesture?) -> Void)? + + public init( + content: Content, + pressed: @escaping (UIView) -> Void, + contextAction: ((UIView, ContextGesture?) -> Void)? = nil + ) { + self.content = content + self.pressed = pressed + self.contextAction = contextAction + } + + public static func ==(lhs: NavigationButtonComponent, rhs: NavigationButtonComponent) -> Bool { + if lhs.content != rhs.content { + return false + } + return true + } + + public final class View: HighlightTrackingButton { + private var textView: ImmediateTextView? + + private var iconView: UIImageView? + private var iconImageName: String? + + private var proxyNode: ChatTitleProxyNode? + + private var moreButton: MoreHeaderButton? + + private var component: NavigationButtonComponent? + private var theme: PresentationTheme? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + + self.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if highlighted { + self.textView?.alpha = 0.6 + self.proxyNode?.alpha = 0.6 + self.iconView?.alpha = 0.6 + } else { + self.textView?.alpha = 1.0 + self.textView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) + + self.proxyNode?.alpha = 1.0 + self.proxyNode?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) + + self.iconView?.alpha = 1.0 + self.iconView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) + } + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func pressed() { + self.component?.pressed(self) + } + + func update(component: NavigationButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let theme = environment[NavigationButtonComponentEnvironment.self].value.theme + var themeUpdated = false + if self.theme !== theme { + self.theme = theme + themeUpdated = true + } + + var textString: NSAttributedString? + var imageName: String? + var proxyStatus: ChatTitleProxyStatus? + var isMore: Bool = false + + switch component.content { + case let .text(title, isBold): + textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.medium(17.0), textColor: theme.chat.inputPanel.panelControlColor) + case .more: + isMore = true + case let .icon(imageNameValue): + imageName = imageNameValue + case let .proxy(status): + proxyStatus = status + } + + var size = CGSize(width: 0.0, height: availableSize.height) + + if let textString = textString { + let textView: ImmediateTextView + if let current = self.textView { + textView = current + } else { + textView = ImmediateTextView() + textView.isUserInteractionEnabled = false + self.textView = textView + self.addSubview(textView) + } + + textView.attributedText = textString + let textSize = textView.updateLayout(availableSize) + let textInset: CGFloat = 12.0 + size.width = max(44.0, textSize.width + textInset * 2.0) + + textView.frame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((availableSize.height - textSize.height) / 2.0)), size: textSize) + } else if let textView = self.textView { + self.textView = nil + textView.removeFromSuperview() + } + + if let imageName = imageName { + let iconView: UIImageView + if let current = self.iconView { + iconView = current + } else { + iconView = UIImageView() + iconView.isUserInteractionEnabled = false + self.iconView = iconView + self.addSubview(iconView) + } + if self.iconImageName != imageName || themeUpdated { + self.iconImageName = imageName + iconView.image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.chat.inputPanel.panelControlColor) + } + + if let iconSize = iconView.image?.size { + size.width = 44.0 + + iconView.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((availableSize.height - iconSize.height) / 2.0)), size: iconSize) + } + } else if let iconView = self.iconView { + self.iconView = nil + iconView.removeFromSuperview() + self.iconImageName = nil + } + + if let proxyStatus = proxyStatus { + let proxyNode: ChatTitleProxyNode + if let current = self.proxyNode { + proxyNode = current + } else { + proxyNode = ChatTitleProxyNode(theme: theme) + proxyNode.isUserInteractionEnabled = false + self.proxyNode = proxyNode + self.addSubnode(proxyNode) + } + + let proxySize = CGSize(width: 30.0, height: 30.0) + size.width = 44.0 + + proxyNode.theme = theme + proxyNode.status = proxyStatus + + proxyNode.frame = CGRect(origin: CGPoint(x: floor((size.width - proxySize.width) / 2.0), y: floor((availableSize.height - proxySize.height) / 2.0)), size: proxySize) + } else if let proxyNode = self.proxyNode { + self.proxyNode = nil + proxyNode.removeFromSupernode() + } + + if isMore { + let moreButton: MoreHeaderButton + if let current = self.moreButton, !themeUpdated { + moreButton = current + } else { + if let moreButton = self.moreButton { + moreButton.removeFromSupernode() + self.moreButton = nil + } + + moreButton = MoreHeaderButton(color: theme.chat.inputPanel.panelControlColor) + moreButton.isUserInteractionEnabled = true + moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.chat.inputPanel.panelControlColor))) + moreButton.onPressed = { [weak self] in + guard let self, let component = self.component else { + return + } + self.moreButton?.play() + component.pressed(self) + } + moreButton.contextAction = { [weak self] sourceNode, gesture in + guard let self, let component = self.component else { + return + } + self.moreButton?.play() + component.contextAction?(self, gesture) + } + self.moreButton = moreButton + self.addSubnode(moreButton) + } + + let buttonSize = CGSize(width: 44.0, height: 44.0) + size.width = 44.0 + + moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor))) + + moreButton.frame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize) + } else if let moreButton = self.moreButton { + self.moreButton = nil + moreButton.removeFromSupernode() + } + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleProxyNode.swift b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleProxyNode.swift index 5ec474b5..b3d2606b 100644 --- a/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleProxyNode.swift +++ b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleProxyNode.swift @@ -44,13 +44,13 @@ public final class ChatTitleProxyNode: ASDisplayNode { if self.theme !== oldValue { switch self.status { case .connecting: - self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: false) + self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: false, off: false) case .connected: - self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: true, off: false) + self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: true, off: false) case .available: - self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: true) + self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: false, off: true) } - self.activityIndicator.type = .custom(theme.rootController.navigationBar.accentTextColor, 10.0, 1.3333, true) + self.activityIndicator.type = .custom(theme.chat.inputPanel.panelControlColor, 10.0, 1.3333, true) } } } @@ -61,13 +61,13 @@ public final class ChatTitleProxyNode: ASDisplayNode { switch self.status { case .connecting: self.activityIndicator.isHidden = false - self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: false) + self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: false, off: false) case .connected: self.activityIndicator.isHidden = true - self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: true, off: false) + self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: true, off: false) case .available: self.activityIndicator.isHidden = true - self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: true) + self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: false, off: true) } } } @@ -80,9 +80,9 @@ public final class ChatTitleProxyNode: ASDisplayNode { self.iconNode.isLayerBacked = true self.iconNode.displayWithoutProcessing = true self.iconNode.displaysAsynchronously = false - self.iconNode.image = generateIcon(color: theme.rootController.navigationBar.accentTextColor, connected: false, off: true) + self.iconNode.image = generateIcon(color: theme.chat.inputPanel.panelControlColor, connected: false, off: true) - self.activityIndicator = ActivityIndicator(type: .custom(theme.rootController.navigationBar.accentTextColor, 10.0, 1.3333, true), speed: .slow) + self.activityIndicator = ActivityIndicator(type: .custom(theme.chat.inputPanel.panelControlColor, 10.0, 1.3333, true), speed: .slow) super.init() diff --git a/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift index 0fc758a8..41cfe13d 100644 --- a/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift +++ b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift @@ -60,9 +60,10 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer + public var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? public var openStatusSetup: ((UIView) -> Void)? - private var validLayout: (CGSize, CGRect)? + private var validLayout: CGSize? public var manualLayout: Bool = false @@ -321,13 +322,18 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation override public func layoutSubviews() { super.layoutSubviews() - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if !self.manualLayout, let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: .immediate) } } - public func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) -> CGRect { - self.validLayout = (size, clearBounds) + public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let _ = self.updateLayoutInternal(size: availableSize, transition: transition) + return availableSize + } + + public func updateLayoutInternal(size: CGSize, transition: ContainedViewLayoutTransition) -> CGRect { + self.validLayout = size var indicatorPadding: CGFloat = 0.0 let indicatorSize = self.activityIndicator.bounds.size @@ -335,7 +341,7 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation if !self.activityIndicator.isHidden { indicatorPadding = indicatorSize.width + 6.0 } - var maxTitleWidth = clearBounds.size.width - indicatorPadding + var maxTitleWidth = size.width - indicatorPadding var proxyPadding: CGFloat = 0.0 if !self.proxyNode.isHidden { maxTitleWidth -= 25.0 @@ -353,7 +359,7 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation var titleContentRect = CGRect(origin: CGPoint(x: indicatorPadding + floor((size.width - combinedWidth - indicatorPadding) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize) - titleContentRect.origin.x = min(titleContentRect.origin.x, clearBounds.maxX - proxyPadding - titleContentRect.width) + titleContentRect.origin.x = min(titleContentRect.origin.x, size.width - proxyPadding - titleContentRect.width) let titleFrame = titleContentRect var titleTransition = transition @@ -362,7 +368,7 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation } titleTransition.updateFrame(node: self.titleNode, frame: titleFrame) - let proxyFrame = CGRect(origin: CGPoint(x: clearBounds.maxX - 9.0 - self.proxyNode.bounds.width, y: floor((size.height - self.proxyNode.bounds.height) / 2.0)), size: self.proxyNode.bounds.size) + let proxyFrame = CGRect(origin: CGPoint(x: size.width - 9.0 - self.proxyNode.bounds.width, y: floor((size.height - self.proxyNode.bounds.height) / 2.0)), size: self.proxyNode.bounds.size) self.proxyNode.frame = proxyFrame self.proxyButton.frame = proxyFrame.insetBy(dx: -2.0, dy: -2.0) diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift index 4e49cf0c..5e1db9b4 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift @@ -179,7 +179,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in diff --git a/submodules/TelegramUI/Components/ChatThemeScreen/BUILD b/submodules/TelegramUI/Components/ChatThemeScreen/BUILD index 783bf604..cb29f704 100644 --- a/submodules/TelegramUI/Components/ChatThemeScreen/BUILD +++ b/submodules/TelegramUI/Components/ChatThemeScreen/BUILD @@ -37,6 +37,9 @@ swift_library( "//submodules/AppBundle", "//submodules/ActivityIndicator", "//submodules/TelegramUI/Components/Gifts/GiftItemComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent", + "//submodules/TelegramUI/Components/AvatarComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift index 589c5c55..abcfe86f 100644 --- a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift @@ -22,6 +22,7 @@ import TelegramAnimatedStickerNode import ShimmerEffect import AttachmentUI import AvatarNode +import AlertComponent private struct ThemeSettingsThemeEntry: Comparable, Identifiable { let index: Int @@ -308,7 +309,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { self.placeholderNode = StickerShimmerEffectNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.imageNode) @@ -925,7 +926,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega self.animationNode = AnimationNode(animation: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0) self.animationNode.isUserInteractionEnabled = false - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 11.0) + self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), glass: true, height: 52.0, cornerRadius: 26.0) self.otherButton = HighlightableButtonNode() @@ -1530,7 +1531,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { var presentingAlertController = false self.controller?.forEachController({ c in - if c is AlertController { + if c is AlertScreen { presentingAlertController = true } return true @@ -1605,13 +1606,14 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: cancelSize) transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame) + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: layout.intrinsicInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let buttonInset: CGFloat = 16.0 - let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) + let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInsets.left - buttonInsets.right, transition: transition) var doneY = contentHeight - doneButtonHeight - 2.0 - insets.bottom if self.controller?.canResetWallpaper == true { doneY = contentHeight - doneButtonHeight - 52.0 - insets.bottom } - transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: doneY, width: contentFrame.width, height: doneButtonHeight)) + transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInsets.left, y: doneY, width: contentFrame.width, height: doneButtonHeight)) let otherButtonSize = self.otherButton.measure(CGSize(width: contentFrame.width - buttonInset * 2.0, height: .greatestFiniteMagnitude)) self.otherButton.frame = CGRect(origin: CGPoint(x: floor((contentFrame.width - otherButtonSize.width) / 2.0), y: contentHeight - otherButtonSize.height - insets.bottom - 15.0), size: otherButtonSize) diff --git a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/GiftThemeTransferAlertController.swift b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/GiftThemeTransferAlertController.swift index 03ea7800..7b4a4907 100644 --- a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/GiftThemeTransferAlertController.swift +++ b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/GiftThemeTransferAlertController.swift @@ -3,293 +3,65 @@ import UIKit import AsyncDisplayKit import Display import ComponentFlow -import Postbox import TelegramCore import TelegramPresentationData -import TelegramUIPreferences import AccountContext -import AppBundle -import AvatarNode -import Markdown +import AlertComponent +import AlertTransferHeaderComponent import GiftItemComponent -import ActivityIndicator - -private final class GiftThemeTransferAlertContentNode: AlertContentNode { - private let context: AccountContext - private let strings: PresentationStrings - private var presentationTheme: PresentationTheme - private let title: String - private let text: String - private let gift: StarGift.UniqueGift - - private let titleNode: ASTextNode - private let giftView = ComponentView() - private let textNode: ASTextNode - private let arrowNode: ASImageNode - private let avatarNode: AvatarNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var activityIndicator: ActivityIndicator? - - private var validLayout: CGSize? - - var inProgress = false { - didSet { - if let size = self.validLayout { - let _ = self.updateLayout(size: size, transition: .immediate) - } - } - } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init( - context: AccountContext, - theme: AlertControllerTheme, - ptheme: PresentationTheme, - strings: PresentationStrings, - gift: StarGift.UniqueGift, - peer: EnginePeer, - title: String, - text: String, - actions: [TextAlertAction] - ) { - self.context = context - self.strings = strings - self.presentationTheme = ptheme - self.title = title - self.text = text - self.gift = gift - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 0 - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.arrowNode = ASImageNode() - self.arrowNode.displaysAsynchronously = false - self.arrowNode.displayWithoutProcessing = true - - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - self.addSubnode(self.arrowNode) - self.addSubnode(self.avatarNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - - self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor) - self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - linkAttribute: { url in - return ("URL", url) - } - ), textAlignment: .center) - self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/CutoutUndo"), color: theme.secondaryColor.withAlphaComponent(0.9)) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let avatarSize = CGSize(width: 60.0, height: 60.0) - self.avatarNode.updateSize(size: avatarSize) - - let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 52.0, y: origin.y), size: avatarSize) - - let _ = self.giftView.update( - transition: .immediate, - component: AnyComponent( - GiftItemComponent( - context: self.context, - theme: self.presentationTheme, - strings: self.strings, - peer: nil, - subject: .uniqueGift(gift: self.gift, price: nil), - mode: .thumbnail - ) - ), - environment: {}, - containerSize: avatarSize - ) - if let view = self.giftView.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = giftFrame - } - - if let arrowImage = self.arrowNode.image { - let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) - transition.updateFrame(node: self.arrowNode, frame: arrowFrame) - } - - let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 52.0, y: origin.y), size: avatarSize) - transition.updateFrame(node: self.avatarNode, frame: avatarFrame) - - origin.y += avatarSize.height + 17.0 - - let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 5.0 - - let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 10.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - minActionsWidth += actionTitleSize.width + actionTitleInsets - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - let actionsHeight = actionButtonHeight - - let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 24.0 + insets.top + insets.bottom) - self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - - let actionNodeFrame: CGRect - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if self.inProgress { - let activityIndicator: ActivityIndicator - if let current = self.activityIndicator { - activityIndicator = current - } else { - activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false)) - self.addSubnode(activityIndicator) - } - - if let actionNode = self.actionNodes.first { - actionNode.isHidden = true - - let indicatorSize = CGSize(width: 22.0, height: 22.0) - transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize)) - } - } - - return resultSize - } -} +import AvatarComponent public func giftThemeTransferAlertController( context: AccountContext, gift: StarGift.UniqueGift, previousPeer: EnginePeer, commit: @escaping () -> Void -) -> AlertController { +) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings - - var contentNode: GiftThemeTransferAlertContentNode? - var dismissImpl: ((Bool) -> Void)? - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Conversation_Theme_GiftTransfer_Proceed, action: { - dismissImpl?(true) - commit() - })] - let text = strings.Conversation_Theme_GiftTransfer_Text(previousPeer.compactDisplayTitle).string - contentNode = GiftThemeTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: previousPeer, title: "", text: text, actions: actions) + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertTransferHeaderComponent( + fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent( + GiftItemComponent( + context: context, + theme: presentationData.theme, + strings: strings, + peer: nil, + subject: .uniqueGift(gift: gift, price: nil), + mode: .thumbnail + ) + )), + toComponent: AnyComponentWithIdentity(id: "user", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: previousPeer + ) + )), + type: .take + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.Conversation_Theme_GiftTransfer_Text(previousPeer.compactDisplayTitle).string)) + ) + )) - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + let alertController = AlertScreen( + context: context, + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.Conversation_Theme_GiftTransfer_Proceed, type: .default, action: { + commit() + }) + ] + ) + return alertController } diff --git a/submodules/TelegramUI/Components/ChatTitleView/BUILD b/submodules/TelegramUI/Components/ChatTitleView/BUILD index 26fb6712..aafb2e9f 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/BUILD +++ b/submodules/TelegramUI/Components/ChatTitleView/BUILD @@ -31,6 +31,8 @@ swift_library( "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", "//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/AnimatedTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift new file mode 100644 index 00000000..9b62a002 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift @@ -0,0 +1,1164 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData +import AccountContext +import TelegramUIPreferences +import Postbox +import TelegramCore +import PeerPresenceStatusManager +import ChatTitleActivityNode +import AnimatedTextComponent +import PhoneNumberFormat +import TelegramStringFormatting +import EmojiStatusComponent +import GlassBackgroundComponent + +public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView { + private final class ContentData { + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let dateTimeFormat: PresentationDateTimeFormat + let nameDisplayOrder: PresentationPersonNameOrder + let content: ChatTitleContent + + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, content: ChatTitleContent) { + self.context = context + self.theme = theme + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder + self.content = content + } + } + + private let parentTitleState = ComponentState() + private let title = ComponentView() + + private var contentData: ContentData? + private var activities: ChatTitleComponent.Activities? + private var networkState: AccountNetworkState? + + public var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? + public var tapAction: (() -> Void)? + public var longTapAction: (() -> Void)? + + override public init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func animateLayoutTransition() { + } + + public func prepareSnapshotState() -> ChatTitleView.SnapshotState? { + //return titleView.contentView?.snapshotView(afterScreenUpdates: false) + return nil + } + + public func animateFromSnapshot(_ snapshotState: ChatTitleView.SnapshotState, direction: ChatTitleView.AnimateFromSnapshotDirection) { + guard let titleView = self.title.view as? ChatTitleComponent.View else { + return + } + //titleView.contentView?.animateFromSnapshot(snapshotState, direction: direction) + titleView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + public func update( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, + nameDisplayOrder: PresentationPersonNameOrder, + content: ChatTitleContent, + transition: ComponentTransition + ) { + self.contentData = ContentData( + context: context, + theme: theme, + strings: strings, + dateTimeFormat: dateTimeFormat, + nameDisplayOrder: nameDisplayOrder, + content: content + ) + self.update(transition: transition) + } + + public func updateActivities(activities: ChatTitleComponent.Activities?, transition: ComponentTransition) { + if self.activities != activities { + self.activities = activities + self.update(transition: transition) + } + } + + public func updateNetworkState(networkState: AccountNetworkState, transition: ComponentTransition) { + if self.networkState != networkState { + self.networkState = networkState + self.update(transition: transition) + } + } + + private func update(transition: ComponentTransition) { + self.requestUpdate?(transition.containedViewLayoutTransition) + } + + public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let transition = ComponentTransition(transition) + + if let contentData = self.contentData { + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(ChatTitleComponent( + context: contentData.context, + theme: contentData.theme, + strings: contentData.strings, + dateTimeFormat: contentData.dateTimeFormat, + nameDisplayOrder: contentData.nameDisplayOrder, + displayBackground: true, + content: contentData.content, + activities: self.activities, + networkState: self.networkState, + tapped: { [weak self] in + guard let self else { + return + } + self.tapAction?() + }, + longTapped: { [weak self] in + guard let self else { + return + } + self.longTapAction?() + } + )), + environment: {}, + containerSize: availableSize + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.title.parentState = self.parentTitleState + self.parentTitleState._updated = { [weak self] transition, _ in + guard let self else { + return + } + self.requestUpdate?(transition.containedViewLayoutTransition) + } + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(), size: titleSize)) + } + return titleSize + } else { + return availableSize + } + } +} + +public final class ChatTitleComponent: Component { + public struct Activities: Equatable { + public struct Item: Equatable { + public let peer: EnginePeer + public let activity: PeerInputActivity + + public init(peer: EnginePeer, activity: PeerInputActivity) { + self.peer = peer + self.activity = activity + } + } + + public let peerId: EnginePeer.Id + public let items: [Item] + + public init(peerId: EnginePeer.Id, items: [Item]) { + self.peerId = peerId + self.items = items + } + } + + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + public let dateTimeFormat: PresentationDateTimeFormat + public let nameDisplayOrder: PresentationPersonNameOrder + public let displayBackground: Bool + public let content: ChatTitleContent + public let activities: Activities? + public let networkState: AccountNetworkState? + public let tapped: () -> Void + public let longTapped: () -> Void + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, + nameDisplayOrder: PresentationPersonNameOrder, + displayBackground: Bool, + content: ChatTitleContent, + activities: Activities?, + networkState: AccountNetworkState?, + tapped: @escaping () -> Void, + longTapped: @escaping () -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder + self.displayBackground = displayBackground + self.content = content + self.activities = activities + self.networkState = networkState + self.tapped = tapped + self.longTapped = longTapped + } + + public static func ==(lhs: ChatTitleComponent, rhs: ChatTitleComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.dateTimeFormat != rhs.dateTimeFormat { + return false + } + if lhs.nameDisplayOrder != rhs.nameDisplayOrder { + return false + } + if lhs.displayBackground != rhs.displayBackground { + return false + } + if lhs.content != rhs.content { + return false + } + if lhs.activities != rhs.activities { + return false + } + if lhs.networkState != rhs.networkState { + return false + } + return true + } + + public final class View: UIView { + private var backgroundView: GlassBackgroundView? + private let contentContainer: UIView + private let title = ComponentView() + private var subtitleNode: ChatTitleActivityNode? + private var activityMeasureSubtitleNode: ChatTitleActivityNode? + private var leftIcon: ComponentView? + private var rightIcon: ComponentView? + private var credibilityIcon: ComponentView? + private var verifiedIcon: ComponentView? + private var statusIcon: ComponentView? + + private var presenceManager: PeerPresenceStatusManager? + + private var component: ChatTitleComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.contentContainer = UIView() + self.contentContainer.clipsToBounds = true + + super.init(frame: frame) + + self.presenceManager = PeerPresenceStatusManager(update: { [weak self] in + guard let self else { + return + } + self.state?.updated(transition: .spring(duration: 0.4)) + }) + + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))) + recognizer.tapActionAtPoint = { _ in + return .waitForSingleTap + } + self.contentContainer.addGestureRecognizer(recognizer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func onTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { + switch gesture { + case .tap: + self.component?.tapped() + case .longTap: + self.component?.longTapped() + default: + break + } + } + } + + func update(component: ChatTitleComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let statusIconsSpacing: CGFloat = 4.0 + let leftTitleIconSpacing: CGFloat = 3.0 + let rightTitleIconSpacing: CGFloat = 3.0 + let containerSideInset: CGFloat = 14.0 + + self.component = component + self.state = state + + var titleSegments: [AnimatedTextComponent.Item] = [] + var titleLeftIcon: TitleIconComponent.Kind? + var titleRightIcon: TitleIconComponent.Kind? + var titleCredibilityIcon: ChatTitleCredibilityIcon = .none + var titleVerifiedIcon: ChatTitleCredibilityIcon = .none + var titleStatusIcon: ChatTitleCredibilityIcon = .none + var isEnabled = true + switch component.content { + case let .peer(peerView, customTitle, _, _, isScheduledMessages, isMuted, _, isEnabledValue): + if peerView.peerId.isReplies { + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(component.strings.DialogList_Replies) + )] + isEnabled = false + } else if isScheduledMessages { + if peerView.peerId == component.context.account.peerId { + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(component.strings.ScheduledMessages_RemindersTitle) + )] + } else { + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(component.strings.ScheduledMessages_Title) + )] + } + isEnabled = false + } else { + if let peer = peerView.peer { + if let customTitle { + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(customTitle) + )] + } else if peerView.peerId == component.context.account.peerId { + if peerView.isSavedMessages { + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(component.strings.Conversation_MyNotes) + )] + } else { + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(component.strings.Conversation_SavedMessages) + )] + } + } else if peerView.peerId.isAnonymousSavedMessages { + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(component.strings.ChatList_AuthorHidden) + )] + } else { + if !peerView.isContact, let user = peer as? TelegramUser, !user.flags.contains(.isSupport), user.botInfo == nil, let phone = user.phone, !phone.isEmpty { + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(formatPhoneNumber(context: component.context, number: phone)) + )] + } else { + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(EnginePeer(peer).displayTitle(strings: component.strings, displayOrder: component.nameDisplayOrder)) + )] + } + } + if peer.id != component.context.account.peerId { + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with({ $0 })) + if peer.isFake { + titleCredibilityIcon = .fake + } else if peer.isScam { + titleCredibilityIcon = .scam + } else if let emojiStatus = peer.emojiStatus { + titleStatusIcon = .emojiStatus(emojiStatus) + } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { + titleCredibilityIcon = .premium + } + + if peer.isVerified { + titleCredibilityIcon = .verified + } + if let verificationIconFileId = peer.verificationIconFileId { + titleVerifiedIcon = .emojiStatus(PeerEmojiStatus(content: .emoji(fileId: verificationIconFileId), expirationDate: nil)) + } + } + } + if peerView.peerId.namespace == Namespaces.Peer.SecretChat { + titleLeftIcon = .lock + } + if let isMuted { + if isMuted { + titleRightIcon = .mute + } + } else { + if let notificationSettings = peerView.notificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + if titleCredibilityIcon != .verified { + titleRightIcon = .mute + } + } + } + } + if peerView.peerId.isVerificationCodes { + isEnabled = false + } else { + isEnabled = isEnabledValue + } + } + case let .replyThread(type, count): + if count > 0 { + var commentsPart: String + switch type { + case .comments: + commentsPart = component.strings.Conversation_TitleComments(Int32(count)) + case .replies: + commentsPart = component.strings.Conversation_TitleReplies(Int32(count)) + } + + if commentsPart.contains("[") && commentsPart.contains("]") { + if let startIndex = commentsPart.firstIndex(of: "["), let endIndex = commentsPart.firstIndex(of: "]") { + commentsPart.removeSubrange(startIndex ... endIndex) + } + } else { + commentsPart = commentsPart.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,.")) + } + + let rawTextAndRanges: PresentationStrings.FormattedString + switch type { + case .comments: + rawTextAndRanges = component.strings.Conversation_TitleCommentsFormat("\(count)", commentsPart) + case .replies: + rawTextAndRanges = component.strings.Conversation_TitleRepliesFormat("\(count)", commentsPart) + } + + let rawText = rawTextAndRanges.string + + var textIndex = 0 + var latestIndex = 0 + for indexAndRange in rawTextAndRanges.ranges { + let index = indexAndRange.index + let range = indexAndRange.range + + var lowerSegmentIndex = range.lowerBound + if index != 0 { + lowerSegmentIndex = min(lowerSegmentIndex, latestIndex) + } else { + if latestIndex < range.lowerBound { + let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: latestIndex) ..< rawText.index(rawText.startIndex, offsetBy: range.lowerBound)]) + + titleSegments.append(AnimatedTextComponent.Item( + id: AnyHashable(textIndex), + isUnbreakable: true, + content: .text(part) + )) + textIndex += 1 + } + } + latestIndex = range.upperBound + + let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: lowerSegmentIndex) ..< rawText.index(rawText.startIndex, offsetBy: min(rawText.count, range.upperBound))]) + if index == 0 { + titleSegments.append(AnimatedTextComponent.Item( + id: AnyHashable(textIndex), + isUnbreakable: false, + content: .text(part) + )) + textIndex += 1 + } else { + titleSegments.append(AnimatedTextComponent.Item( + id: AnyHashable(textIndex), + isUnbreakable: true, + content: .text(part) + )) + textIndex += 1 + } + } + if latestIndex < rawText.count { + let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: latestIndex)...]) + titleSegments.append(AnimatedTextComponent.Item( + id: AnyHashable(textIndex), + isUnbreakable: true, + content: .text(part) + )) + textIndex += 1 + } + } else { + switch type { + case .comments: + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(component.strings.Conversation_TitleCommentsEmpty) + )] + case .replies: + titleSegments = [AnimatedTextComponent.Item( + id: AnyHashable(0), + isUnbreakable: true, + content: .text(component.strings.Conversation_TitleRepliesEmpty) + )] + } + } + + isEnabled = false + case let .custom(textItems, _, enabled): + titleSegments = textItems.map { item -> AnimatedTextComponent.Item in + let mappedContent: AnimatedTextComponent.Item.Content + switch item.content { + case let .number(value, minDigits): + mappedContent = .number(value, minDigits: minDigits) + case let .text(text): + mappedContent = .text(text) + } + return AnimatedTextComponent.Item( + id: item.id, + isUnbreakable: item.isUnbreakable, + content: mappedContent + ) + } + isEnabled = enabled + } + + var accessibilityText = "" + for segment in titleSegments { + switch segment.content { + case let .number(value, _): + accessibilityText.append("\(value)") + case let .text(string): + accessibilityText.append(string) + case .icon: + break + } + } + self.accessibilityLabel = accessibilityText + + var inputActivitiesAllowed = true + switch component.content { + case let .peer(peerView, _, _, _, isScheduledMessages, _, _, _): + if let peer = peerView.peer { + if peer.id == component.context.account.peerId || isScheduledMessages || peer.id.isRepliesOrVerificationCodes { + inputActivitiesAllowed = false + } + } + case .replyThread: + inputActivitiesAllowed = true + default: + inputActivitiesAllowed = false + } + + let subtitleFont = Font.regular(12.0) + var state: ChatTitleActivityNodeState = .none + switch component.networkState { + case .waitingForNetwork, .connecting, .updating: + var infoText: String + switch component.networkState { + case .waitingForNetwork: + infoText = component.strings.ChatState_WaitingForNetwork + case .connecting: + infoText = component.strings.ChatState_Connecting + case .updating: + infoText = component.strings.ChatState_Updating + case .online, .none: + infoText = "" + } + state = .info(NSAttributedString(string: infoText, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor), .generic) + case .online, .none: + if let inputActivities = component.activities, !inputActivities.items.isEmpty, inputActivitiesAllowed { + var stringValue = "" + var mergedActivity = inputActivities.items[0].activity + for item in inputActivities.items { + if item.activity != mergedActivity { + mergedActivity = .typingText + break + } + } + if inputActivities.peerId.namespace == Namespaces.Peer.CloudUser || inputActivities.peerId.namespace == Namespaces.Peer.SecretChat { + switch mergedActivity { + case .typingText: + stringValue = component.strings.Conversation_typing + case .uploadingFile: + stringValue = component.strings.Activity_UploadingDocument + case .recordingVoice: + stringValue = component.strings.Activity_RecordingAudio + case .uploadingPhoto: + stringValue = component.strings.Activity_UploadingPhoto + case .uploadingVideo: + stringValue = component.strings.Activity_UploadingVideo + case .playingGame: + stringValue = component.strings.Activity_PlayingGame + case .recordingInstantVideo: + stringValue = component.strings.Activity_RecordingVideoMessage + case .uploadingInstantVideo: + stringValue = component.strings.Activity_UploadingVideoMessage + case .choosingSticker: + stringValue = component.strings.Activity_ChoosingSticker + case let .seeingEmojiInteraction(emoticon): + stringValue = component.strings.Activity_EnjoyingAnimations(emoticon).string + case .speakingInGroupCall, .interactingWithEmoji: + stringValue = "" + } + } else { + if inputActivities.items.count > 1 { + let peerTitle = inputActivities.items[0].peer.compactDisplayTitle + if inputActivities.items.count == 2 { + let secondPeerTitle = inputActivities.items[1].peer.compactDisplayTitle + stringValue = component.strings.Chat_MultipleTypingPair(peerTitle, secondPeerTitle).string + } else { + stringValue = component.strings.Chat_MultipleTypingMore(peerTitle, String(inputActivities.items.count - 1)).string + } + } else if let item = inputActivities.items.first { + stringValue = item.peer.compactDisplayTitle + } + } + let color = component.theme.rootController.navigationBar.accentTextColor + let string = NSAttributedString(string: stringValue, font: subtitleFont, textColor: color) + switch mergedActivity { + case .typingText: + state = .typingText(string, color) + case .recordingVoice: + state = .recordingVoice(string, color) + case .recordingInstantVideo: + state = .recordingVideo(string, color) + case .uploadingFile, .uploadingInstantVideo, .uploadingPhoto, .uploadingVideo: + state = .uploading(string, color) + case .playingGame: + state = .playingGame(string, color) + case .speakingInGroupCall, .interactingWithEmoji: + state = .typingText(string, color) + case .choosingSticker: + state = .choosingSticker(string, color) + case .seeingEmojiInteraction: + state = .choosingSticker(string, color) + } + } else { + switch component.content { + case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _): + if let customSubtitle { + let string = NSAttributedString(string: customSubtitle, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } else if let customMessageCount = customMessageCount, customMessageCount != 0 { + let string = NSAttributedString(string: component.strings.Conversation_Messages(Int32(customMessageCount)), font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } else if let peer = peerView.peer { + let servicePeer = isServicePeer(peer) + if peer.id == component.context.account.peerId || isScheduledMessages || peer.id.isRepliesOrVerificationCodes { + let string = NSAttributedString(string: "", font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } else if let user = peer as? TelegramUser { + if user.isDeleted { + state = .none + } else if servicePeer { + let string = NSAttributedString(string: "", font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } else if user.flags.contains(.isSupport) { + let statusText = component.strings.Bot_GenericSupportStatus + let string = NSAttributedString(string: statusText, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } else if let _ = user.botInfo { + let statusText: String + if let subscriberCount = user.subscriberCount { + statusText = component.strings.Conversation_StatusBotSubscribers(subscriberCount) + } else { + statusText = component.strings.Bot_GenericBotStatus + } + + let string = NSAttributedString(string: statusText, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } else if let peer = peerView.peer { + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + let userPresence: TelegramUserPresence + if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence { + userPresence = presence + self.presenceManager?.reset(presence: EnginePeer.Presence(presence)) + } else { + userPresence = TelegramUserPresence(status: .none, lastActivity: 0) + } + let (string, activity) = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: component.dateTimeFormat, presence: EnginePeer.Presence(userPresence), relativeTo: Int32(timestamp)) + let attributedString = NSAttributedString(string: string, font: subtitleFont, textColor: activity ? component.theme.rootController.navigationBar.accentTextColor : component.theme.chat.inputPanel.inputControlColor) + state = .info(attributedString, activity ? .online : .lastSeenTime) + } else { + let string = NSAttributedString(string: "", font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } + } else if let group = peer as? TelegramGroup { + var onlineCount = 0 + if let cachedGroupData = peerView.cachedData as? CachedGroupData, let participants = cachedGroupData.participants { + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + for participant in participants.participants { + if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence { + let relativeStatus = relativeUserPresenceStatus(EnginePeer.Presence(presence), relativeTo: Int32(timestamp)) + switch relativeStatus { + case .online: + onlineCount += 1 + default: + break + } + } + } + } + if onlineCount > 1 { + let string = NSMutableAttributedString() + + string.append(NSAttributedString(string: "\(component.strings.Conversation_StatusMembers(Int32(group.participantCount))), ", font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor)) + string.append(NSAttributedString(string: component.strings.Conversation_StatusOnline(Int32(onlineCount)), font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor)) + state = .info(string, .generic) + } else { + let string = NSAttributedString(string: component.strings.Conversation_StatusMembers(Int32(group.participantCount)), font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } + } else if let channel = peer as? TelegramChannel { + if channel.isForumOrMonoForum, customTitle != nil { + let string = NSAttributedString(string: EnginePeer(peer).displayTitle(strings: component.strings, displayOrder: component.nameDisplayOrder), font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = onlineMemberCount.total ?? cachedChannelData.participantsSummary.memberCount { + if memberCount == 0 { + let string: NSAttributedString + if case .group = channel.info { + string = NSAttributedString(string: component.strings.Group_Status, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + } else { + string = NSAttributedString(string: component.strings.Channel_Status, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + } + state = .info(string, .generic) + } else { + if case .group = channel.info, let onlineMemberCount = onlineMemberCount.recent, onlineMemberCount > 1 { + let string = NSMutableAttributedString() + + string.append(NSAttributedString(string: "\(component.strings.Conversation_StatusMembers(Int32(memberCount))), ", font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor)) + string.append(NSAttributedString(string: component.strings.Conversation_StatusOnline(Int32(onlineMemberCount)), font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor)) + state = .info(string, .generic) + } else { + let membersString: String + if case .group = channel.info { + membersString = component.strings.Conversation_StatusMembers(memberCount) + } else { + membersString = component.strings.Conversation_StatusSubscribers(memberCount) + } + let string = NSAttributedString(string: membersString, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } + } + } else { + switch channel.info { + case .group: + let string = NSAttributedString(string: component.strings.Group_Status, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + case .broadcast: + let string = NSAttributedString(string: component.strings.Channel_Status, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + } + } + } + } + case let .custom(_, subtitle?, _): + let string = NSAttributedString(string: subtitle, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) + state = .info(string, .generic) + default: + break + } + + self.accessibilityValue = state.string + } + } + + var rightIconSize: CGSize? + if let titleRightIcon { + let rightIcon: ComponentView + var rightIconTransition = transition + if let current = self.rightIcon { + rightIcon = current + } else { + rightIconTransition = rightIconTransition.withAnimation(.none) + rightIcon = ComponentView() + self.rightIcon = rightIcon + } + rightIconSize = rightIcon.update( + transition: rightIconTransition, + component: AnyComponent(TitleIconComponent( + kind: titleRightIcon, + color: component.theme.chat.inputPanel.inputControlColor + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + } else if let rightIcon = self.rightIcon { + self.rightIcon = nil + if let rightIconView = rightIcon.view { + transition.setScale(view: rightIconView, scale: 0.001) + transition.setAlpha(view: rightIconView, alpha: 0.0, completion: { [weak rightIconView] _ in + rightIconView?.removeFromSuperview() + }) + } + } + + var leftIconSize: CGSize? + if let titleLeftIcon { + let leftIcon: ComponentView + var leftIconTransition = transition + if let current = self.leftIcon { + leftIcon = current + } else { + leftIconTransition = leftIconTransition.withAnimation(.none) + leftIcon = ComponentView() + self.leftIcon = leftIcon + } + leftIconSize = leftIcon.update( + transition: leftIconTransition, + component: AnyComponent(TitleIconComponent( + kind: titleLeftIcon, + color: component.theme.chat.inputPanel.panelControlColor + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + } else if let leftIcon = self.leftIcon { + self.leftIcon = nil + if let leftIconView = leftIcon.view { + transition.setScale(view: leftIconView, scale: 0.001) + transition.setAlpha(view: leftIconView, alpha: 0.0, completion: { [weak leftIconView] _ in + leftIconView?.removeFromSuperview() + }) + } + } + + let mapTitleIcon: (ChatTitleCredibilityIcon) -> EmojiStatusComponent.Content? = { value in + switch value { + case .none: + return nil + case .premium: + return .premium(color: component.theme.list.itemAccentColor) + case .verified: + return .verified(fillColor: component.theme.list.itemCheckColors.fillColor, foregroundColor: component.theme.list.itemCheckColors.foregroundColor, sizeType: .large) + case .fake: + return .text(color: component.theme.chat.message.incoming.scamColor, string: component.strings.Message_FakeAccount.uppercased()) + case .scam: + return .text(color: component.theme.chat.message.incoming.scamColor, string: component.strings.Message_ScamAccount.uppercased()) + case let .emojiStatus(emojiStatus): + return .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.theme.list.itemAccentColor, loopMode: .count(2)) + } + } + + var credibilityIconSize: CGSize? + if let titleCredibilityIcon = mapTitleIcon(titleCredibilityIcon) { + let credibilityIcon: ComponentView + if let current = self.credibilityIcon { + credibilityIcon = current + } else { + credibilityIcon = ComponentView() + self.credibilityIcon = credibilityIcon + } + credibilityIconSize = credibilityIcon.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + content: titleCredibilityIcon, + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: CGSize(width: 20.0, height: 20.0) + ) + } else if let credibilityIcon = self.credibilityIcon { + self.credibilityIcon = nil + if let credibilityIconView = credibilityIcon.view { + transition.setScale(view: credibilityIconView, scale: 0.001) + transition.setAlpha(view: credibilityIconView, alpha: 0.0, completion: { [weak credibilityIconView] _ in + credibilityIconView?.removeFromSuperview() + }) + } + } + + var statusIconSize: CGSize? + if let titleStatusIcon = mapTitleIcon(titleStatusIcon) { + let statusIcon: ComponentView + if let current = self.statusIcon { + statusIcon = current + } else { + statusIcon = ComponentView() + self.statusIcon = statusIcon + } + statusIconSize = statusIcon.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + content: titleStatusIcon, + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: CGSize(width: 20.0, height: 20.0) + ) + } else if let statusIcon = self.statusIcon { + self.statusIcon = nil + if let statusIconView = statusIcon.view { + transition.setScale(view: statusIconView, scale: 0.001) + transition.setAlpha(view: statusIconView, alpha: 0.0, completion: { [weak statusIconView] _ in + statusIconView?.removeFromSuperview() + }) + } + } + + var verifiedIconSize: CGSize? + if let titleVerifiedIcon = mapTitleIcon(titleVerifiedIcon) { + let verifiedIcon: ComponentView + if let current = self.verifiedIcon { + verifiedIcon = current + } else { + verifiedIcon = ComponentView() + self.verifiedIcon = verifiedIcon + } + verifiedIconSize = verifiedIcon.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + content: titleVerifiedIcon, + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: CGSize(width: 20.0, height: 20.0) + ) + } else if let verifiedIcon = self.verifiedIcon { + self.verifiedIcon = nil + if let verifiedIconView = verifiedIcon.view { + transition.setScale(view: verifiedIconView, scale: 0.001) + transition.setAlpha(view: verifiedIconView, alpha: 0.0, completion: { [weak verifiedIconView] _ in + verifiedIconView?.removeFromSuperview() + }) + } + } + + let subtitleNode: ChatTitleActivityNode + if let current = self.subtitleNode { + subtitleNode = current + } else { + subtitleNode = ChatTitleActivityNode() + self.subtitleNode = subtitleNode + subtitleNode.isUserInteractionEnabled = false + self.contentContainer.addSubview(subtitleNode.view) + } + + var titleLeftIconsWidth: CGFloat = 0.0 + if let leftIconSize { + titleLeftIconsWidth += leftIconSize.width + leftTitleIconSpacing + } + if let verifiedIconSize { + titleLeftIconsWidth += verifiedIconSize.width + statusIconsSpacing + } + + var titleRightIconsWidth: CGFloat = 0.0 + if let rightIconSize { + titleRightIconsWidth += rightIconSize.width + rightTitleIconSpacing + } + if let credibilityIconSize { + titleRightIconsWidth += credibilityIconSize.width + statusIconsSpacing + } + if let statusIconSize { + titleRightIconsWidth += statusIconSize.width + statusIconsSpacing + } + + let maxTitleWidth = availableSize.width - titleLeftIconsWidth - titleRightIconsWidth - containerSideInset * 2.0 + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(AnimatedTextComponent( + font: Font.semibold(17.0), + color: component.theme.chat.inputPanel.panelControlColor, + items: titleSegments, + noDelay: false, + animateScale: true, + animateSlide: true, + blur: true + )), + environment: {}, + containerSize: CGSize(width: maxTitleWidth, height: 100.0) + ) + + let _ = subtitleNode.transitionToState(state, animation: transition.animation.isImmediate ? .none : .slide) + let subtitleSize = subtitleNode.updateLayout(CGSize(width: availableSize.width - containerSideInset * 2.0, height: 100.0), alignment: .center) + + var minSubtitleWidth: CGFloat? + let activityMeasureSubtitleNode: ChatTitleActivityNode + if let current = self.activityMeasureSubtitleNode { + activityMeasureSubtitleNode = current + } else { + activityMeasureSubtitleNode = ChatTitleActivityNode() + self.activityMeasureSubtitleNode = activityMeasureSubtitleNode + } + let measureTypingTextString = NSAttributedString(string: component.strings.Conversation_typing, font: subtitleFont, textColor: .black) + let _ = activityMeasureSubtitleNode.transitionToState(.typingText(measureTypingTextString, .black), animation: .none) + let activityMeasureSubtitleSize = activityMeasureSubtitleNode.updateLayout(CGSize(width: availableSize.width - containerSideInset * 2.0, height: 100.0), alignment: .center) + minSubtitleWidth = activityMeasureSubtitleSize.width + + var contentSize = titleSize + contentSize.width += titleLeftIconsWidth + titleRightIconsWidth + contentSize.width = max(contentSize.width, subtitleSize.width) + if let minSubtitleWidth { + contentSize.width = max(contentSize.width, minSubtitleWidth) + } + contentSize.height += subtitleSize.height + + let containerSize = CGSize(width: contentSize.width + containerSideInset * 2.0, height: 44.0) + let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((availableSize.height - containerSize.height) * 0.5)), size: containerSize) + + let titleFrame = CGRect(origin: CGPoint(x: titleLeftIconsWidth + floor((containerFrame.width - titleSize.width - titleLeftIconsWidth - titleRightIconsWidth) * 0.5), y: floor((containerFrame.height - contentSize.height) * 0.5)), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + titleView.isUserInteractionEnabled = false + self.contentContainer.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + let subtitleFrame = CGRect(origin: CGPoint(x: floor((containerFrame.width - subtitleSize.width) * 0.5), y: titleFrame.maxY), size: subtitleSize) + // Internally, the status view has zero width + transition.setFrame(view: subtitleNode.view, frame: CGRect(origin: CGPoint(x: subtitleFrame.midX, y: subtitleFrame.minY), size: CGSize(width: 0.0, height: subtitleFrame.height))) + + var nextLeftIconX: CGFloat = titleFrame.minX + + if let leftIconSize, let leftIconView = self.leftIcon?.view { + let leftIconFrame = CGRect(origin: CGPoint(x: nextLeftIconX - leftTitleIconSpacing - leftIconSize.width, y: titleFrame.minY + leftTitleIconSpacing), size: leftIconSize) + if leftIconView.superview == nil { + leftIconView.isUserInteractionEnabled = false + self.contentContainer.addSubview(leftIconView) + leftIconView.frame = leftIconFrame + ComponentTransition.immediate.setScale(view: leftIconView, scale: 0.001) + leftIconView.alpha = 0.0 + } + transition.setPosition(view: leftIconView, position: leftIconFrame.center) + transition.setBounds(view: leftIconView, bounds: CGRect(origin: CGPoint(), size: leftIconFrame.size)) + transition.setAlpha(view: leftIconView, alpha: 1.0) + transition.setScale(view: leftIconView, scale: 1.0) + } + + if let verifiedIconSize, let verifiedIconView = self.verifiedIcon?.view { + let verifiedIconFrame = CGRect(origin: CGPoint(x: nextLeftIconX - statusIconsSpacing - verifiedIconSize.width, y: titleFrame.minY), size: verifiedIconSize) + if verifiedIconView.superview == nil { + verifiedIconView.isUserInteractionEnabled = false + self.contentContainer.addSubview(verifiedIconView) + verifiedIconView.frame = verifiedIconFrame + ComponentTransition.immediate.setScale(view: verifiedIconView, scale: 0.001) + verifiedIconView.alpha = 0.0 + } + transition.setPosition(view: verifiedIconView, position: verifiedIconFrame.center) + transition.setBounds(view: verifiedIconView, bounds: CGRect(origin: CGPoint(), size: verifiedIconFrame.size)) + transition.setAlpha(view: verifiedIconView, alpha: 1.0) + transition.setScale(view: verifiedIconView, scale: 1.0) + nextLeftIconX -= statusIconsSpacing + verifiedIconSize.width + } + + var nextRightIconX: CGFloat = titleFrame.maxX + + if let credibilityIconSize, let credibilityIconView = self.credibilityIcon?.view { + let credibilityIconFrame = CGRect(origin: CGPoint(x: nextRightIconX + statusIconsSpacing, y: titleFrame.minY), size: credibilityIconSize) + if credibilityIconView.superview == nil { + credibilityIconView.isUserInteractionEnabled = false + self.contentContainer.addSubview(credibilityIconView) + credibilityIconView.frame = credibilityIconFrame + ComponentTransition.immediate.setScale(view: credibilityIconView, scale: 0.001) + credibilityIconView.alpha = 0.0 + } + transition.setPosition(view: credibilityIconView, position: credibilityIconFrame.center) + transition.setBounds(view: credibilityIconView, bounds: CGRect(origin: CGPoint(), size: credibilityIconFrame.size)) + transition.setAlpha(view: credibilityIconView, alpha: 1.0) + transition.setScale(view: credibilityIconView, scale: 1.0) + nextRightIconX += statusIconsSpacing + credibilityIconSize.width + } + + if let statusIconSize, let statusIconView = self.statusIcon?.view { + let statusIconFrame = CGRect(origin: CGPoint(x: nextRightIconX + statusIconsSpacing, y: titleFrame.minY), size: statusIconSize) + if statusIconView.superview == nil { + statusIconView.isUserInteractionEnabled = false + self.contentContainer.addSubview(statusIconView) + statusIconView.frame = statusIconFrame + ComponentTransition.immediate.setScale(view: statusIconView, scale: 0.001) + statusIconView.alpha = 0.0 + } + transition.setPosition(view: statusIconView, position: statusIconFrame.center) + transition.setBounds(view: statusIconView, bounds: CGRect(origin: CGPoint(), size: statusIconFrame.size)) + transition.setAlpha(view: statusIconView, alpha: 1.0) + transition.setScale(view: statusIconView, scale: 1.0) + nextRightIconX += statusIconsSpacing + statusIconSize.width + } + + if let rightIconSize, let rightIconView = self.rightIcon?.view { + let rightIconFrame = CGRect(origin: CGPoint(x: nextRightIconX + rightTitleIconSpacing, y: titleFrame.minY + 5.0), size: rightIconSize) + if rightIconView.superview == nil { + rightIconView.isUserInteractionEnabled = false + self.contentContainer.addSubview(rightIconView) + rightIconView.frame = rightIconFrame + ComponentTransition.immediate.setScale(view: rightIconView, scale: 0.001) + rightIconView.alpha = 0.0 + } + transition.setPosition(view: rightIconView, position: rightIconFrame.center) + transition.setBounds(view: rightIconView, bounds: CGRect(origin: CGPoint(), size: rightIconFrame.size)) + transition.setAlpha(view: rightIconView, alpha: 1.0) + transition.setScale(view: rightIconView, scale: 1.0) + nextRightIconX += rightTitleIconSpacing + rightIconSize.width + } + + if component.displayBackground { + let backgroundView: GlassBackgroundView + if let current = self.backgroundView { + backgroundView = current + } else { + backgroundView = GlassBackgroundView() + self.backgroundView = backgroundView + self.addSubview(backgroundView) + backgroundView.contentView.addSubview(self.contentContainer) + } + transition.setFrame(view: backgroundView, frame: containerFrame) + backgroundView.update(size: containerFrame.size, cornerRadius: containerFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: isEnabled, transition: transition) + transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: containerFrame.size)) + self.contentContainer.layer.cornerRadius = containerFrame.height * 0.5 + } else { + if let backgroundView = self.backgroundView { + self.backgroundView = nil + backgroundView.removeFromSuperview() + } + if self.contentContainer.superview !== self { + self.addSubview(self.contentContainer) + } + transition.setFrame(view: self.contentContainer, frame: containerFrame) + self.contentContainer.layer.cornerRadius = 0.0 + } + + return CGSize(width: containerSize.width, height: availableSize.height) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index d1226299..1b2e41fd 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -14,7 +14,6 @@ import PeerPresenceStatusManager import ChatTitleActivityNode import LocalizedPeerData import PhoneNumberFormat -import ChatTitleActivityNode import AnimatedCountLabelNode import AccountContext import ComponentFlow @@ -22,6 +21,8 @@ import EmojiStatusComponent import AnimationCache import MultiAnimationRenderer import ComponentDisplayAdapters +import GlassBackgroundComponent +import AnimatedTextComponent private let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]) private let subtitleFont = Font.regular(13.0) @@ -92,9 +93,26 @@ public enum ChatTitleContent: Equatable { case replies } + public struct TitleTextItem: Equatable { + public enum Content: Equatable { + case text(String) + case number(Int, minDigits: Int) + } + + public var id: AnyHashable + public var isUnbreakable: Bool + public var content: Content + + public init(id: AnyHashable, isUnbreakable: Bool = true, content: Content) { + self.id = id + self.isUnbreakable = isUnbreakable + self.content = content + } + } + case peer(peerView: PeerData, customTitle: String?, customSubtitle: String?, onlineMemberCount: (total: Int32?, recent: Int32?), isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool) case replyThread(type: ReplyThreadType, count: Int) - case custom(String, String?, Bool) + case custom(title: [TitleTextItem], subtitle: String?, isEnabled: Bool) public static func ==(lhs: ChatTitleContent, rhs: ChatTitleContent) -> Bool { switch lhs { @@ -144,13 +162,13 @@ public enum ChatTitleContent: Equatable { } } -private enum ChatTitleIcon { +enum ChatTitleIcon { case none case lock case mute } -private enum ChatTitleCredibilityIcon: Equatable { +enum ChatTitleCredibilityIcon: Equatable { case none case fake case scam @@ -170,7 +188,6 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { private let context: AccountContext private var theme: PresentationTheme - private var hasEmbeddedTitleContent: Bool = false private var strings: PresentationStrings private var dateTimeFormat: PresentationDateTimeFormat private var nameDisplayOrder: PresentationPersonNameOrder @@ -178,6 +195,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { private let animationRenderer: MultiAnimationRenderer private let contentContainer: ASDisplayNode + private let backgroundView: GlassBackgroundView public let titleContainerView: PortalSourceView public let titleTextNode: ImmediateAnimatedCountLabelNode public let titleLeftIconNode: ASImageNode @@ -192,7 +210,9 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { public var disableAnimations: Bool = false var manualLayout: Bool = false - private var validLayout: (CGSize, CGRect)? + private var validLayout: CGSize? + + public var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? private var titleLeftIcon: ChatTitleIcon = .none private var titleRightIcon: ChatTitleIcon = .none @@ -204,7 +224,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { private var pointerInteraction: PointerInteraction? - public var inputActivities: (PeerId, [(Peer, PeerInputActivity)])? { + public var inputActivities: ChatTitleComponent.Activities? { didSet { let _ = self.updateStatus() } @@ -239,7 +259,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { public var titleContent: ChatTitleContent? { didSet { if let titleContent = self.titleContent { - let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme + let titleTheme = self.theme var segments: [AnimatedCountLabelNode.Segment] = [] var titleLeftIcon: ChatTitleIcon = .none @@ -394,8 +414,17 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } isEnabled = false - case let .custom(text, _, enabled): - segments = [.text(0, NSAttributedString(string: text, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] + case let .custom(textItems, _, enabled): + var nextId = -1 + segments = textItems.map { item -> AnimatedCountLabelNode.Segment in + nextId += 1 + switch item.content { + case let .number(value, _): + return .number(nextId, NSAttributedString(string: "\(value)", font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor)) + case let .text(text): + return .text(nextId, NSAttributedString(string: text, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor)) + } + } isEnabled = enabled } @@ -461,8 +490,8 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { if !self.updateStatus(enableAnimation: enableAnimation) { if updated { - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: (self.disableAnimations || !enableAnimation) ? .immediate : .animated(duration: 0.2, curve: .easeInOut)) + if !self.manualLayout, let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: (self.disableAnimations || !enableAnimation) ? .immediate : .animated(duration: 0.2, curve: .easeInOut)) } } } @@ -487,7 +516,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } } - let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme + let titleTheme = self.theme var state = ChatTitleActivityNodeState.none switch self.networkState { @@ -505,51 +534,51 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } state = .info(NSAttributedString(string: infoText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor), .generic) case .online: - if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty, inputActivitiesAllowed { + if let inputActivities = self.inputActivities, !inputActivities.items.isEmpty, inputActivitiesAllowed { var stringValue = "" - var mergedActivity = inputActivities[0].1 - for (_, activity) in inputActivities { - if activity != mergedActivity { + var mergedActivity = inputActivities.items[0].activity + for item in inputActivities.items { + if item.activity != mergedActivity { mergedActivity = .typingText break } } - if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat { + if inputActivities.peerId.namespace == Namespaces.Peer.CloudUser || inputActivities.peerId.namespace == Namespaces.Peer.SecretChat { switch mergedActivity { - case .typingText: - stringValue = strings.Conversation_typing - case .uploadingFile: - stringValue = strings.Activity_UploadingDocument - case .recordingVoice: - stringValue = strings.Activity_RecordingAudio - case .uploadingPhoto: - stringValue = strings.Activity_UploadingPhoto - case .uploadingVideo: - stringValue = strings.Activity_UploadingVideo - case .playingGame: - stringValue = strings.Activity_PlayingGame - case .recordingInstantVideo: - stringValue = strings.Activity_RecordingVideoMessage - case .uploadingInstantVideo: - stringValue = strings.Activity_UploadingVideoMessage - case .choosingSticker: - stringValue = strings.Activity_ChoosingSticker - case let .seeingEmojiInteraction(emoticon): - stringValue = strings.Activity_EnjoyingAnimations(emoticon).string - case .speakingInGroupCall, .interactingWithEmoji: - stringValue = "" + case .typingText: + stringValue = strings.Conversation_typing + case .uploadingFile: + stringValue = strings.Activity_UploadingDocument + case .recordingVoice: + stringValue = strings.Activity_RecordingAudio + case .uploadingPhoto: + stringValue = strings.Activity_UploadingPhoto + case .uploadingVideo: + stringValue = strings.Activity_UploadingVideo + case .playingGame: + stringValue = strings.Activity_PlayingGame + case .recordingInstantVideo: + stringValue = strings.Activity_RecordingVideoMessage + case .uploadingInstantVideo: + stringValue = strings.Activity_UploadingVideoMessage + case .choosingSticker: + stringValue = strings.Activity_ChoosingSticker + case let .seeingEmojiInteraction(emoticon): + stringValue = strings.Activity_EnjoyingAnimations(emoticon).string + case .speakingInGroupCall, .interactingWithEmoji: + stringValue = "" } } else { - if inputActivities.count > 1 { - let peerTitle = EnginePeer(inputActivities[0].0).compactDisplayTitle - if inputActivities.count == 2 { - let secondPeerTitle = EnginePeer(inputActivities[1].0).compactDisplayTitle - stringValue = strings.Chat_MultipleTypingPair(peerTitle, secondPeerTitle).string + if inputActivities.items.count > 1 { + let peerTitle = inputActivities.items[0].peer.compactDisplayTitle + if inputActivities.items.count == 2 { + let secondPeerTitle = inputActivities.items[1].peer.compactDisplayTitle + stringValue = self.strings.Chat_MultipleTypingPair(peerTitle, secondPeerTitle).string } else { - stringValue = strings.Chat_MultipleTypingMore(peerTitle, String(inputActivities.count - 1)).string + stringValue = self.strings.Chat_MultipleTypingMore(peerTitle, String(inputActivities.items.count - 1)).string } - } else if let (peer, _) = inputActivities.first { - stringValue = EnginePeer(peer).compactDisplayTitle + } else if let item = inputActivities.items.first { + stringValue = item.peer.compactDisplayTitle } } let color = titleTheme.rootController.navigationBar.accentTextColor @@ -719,8 +748,8 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } if self.activityNode.transitionToState(state, animation: enableAnimation ? .slide : .none) { - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: enableAnimation ? .animated(duration: 0.3, curve: .spring) : .immediate) + if !self.manualLayout, let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: enableAnimation ? .animated(duration: 0.3, curve: .spring) : .immediate) } return true } else { @@ -739,6 +768,8 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { self.contentContainer = ASDisplayNode() + self.backgroundView = GlassBackgroundView() + self.titleContainerView = PortalSourceView() self.titleTextNode = ImmediateAnimatedCountLabelNode() @@ -770,6 +801,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { self.accessibilityTraits = .header self.addSubnode(self.contentContainer) + self.contentContainer.view.addSubview(self.backgroundView) self.titleContainerView.addSubnode(self.titleTextNode) self.contentContainer.view.addSubview(self.titleContainerView) self.contentContainer.addSubnode(self.activityNode) @@ -813,15 +845,14 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { override public func layoutSubviews() { super.layoutSubviews() - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if !self.manualLayout, let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: .immediate) } } - public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, hasEmbeddedTitleContent: Bool) { - if self.theme !== theme || self.strings !== strings || self.hasEmbeddedTitleContent != hasEmbeddedTitleContent { + public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + if self.theme !== theme || self.strings !== strings { self.theme = theme - self.hasEmbeddedTitleContent = hasEmbeddedTitleContent self.strings = strings let titleContent = self.titleContent @@ -830,17 +861,19 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { self.titleContent = titleContent let _ = self.updateStatus() - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - let _ = self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if !self.manualLayout, let size = self.validLayout { + let _ = self.updateLayout(availableSize: size, transition: .immediate) } } } - public func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) -> CGRect { - self.validLayout = (size, clearBounds) + public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let size = availableSize - self.button.frame = clearBounds - self.contentContainer.frame = clearBounds + self.validLayout = size + + self.button.frame = CGRect(origin: CGPoint(), size: size) + self.contentContainer.frame = CGRect(origin: CGPoint(), size: size) var leftIconWidth: CGFloat = 0.0 var rightIconWidth: CGFloat = 0.0 @@ -986,108 +1019,87 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } let statusSpacing: CGFloat = 3.0 - let titleSideInset: CGFloat = 6.0 + let titleSideInset: CGFloat = 12.0 + 8.0 var titleFrame: CGRect - if size.height > 40.0 { - var titleInsets: UIEdgeInsets = .zero - if case .emojiStatus = self.titleVerifiedIcon, verifiedIconWidth > 0.0 { - titleInsets.left = verifiedIconWidth - } - - var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - verifiedIconWidth - statusIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), insets: titleInsets, animated: titleTransition.isAnimated) - titleSize.width += credibilityIconWidth - titleSize.width += verifiedIconWidth - if statusIconWidth > 0.0 { - titleSize.width += statusIconWidth - if credibilityIconWidth > 0.0 { - titleSize.width += statusSpacing - } - } - - let activitySize = self.activityNode.updateLayout(CGSize(width: clearBounds.size.width - titleSideInset * 2.0, height: clearBounds.size.height), alignment: .center) - let titleInfoSpacing: CGFloat = 0.0 - - if activitySize.height.isZero { - titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) - if titleFrame.size.width < size.width { - titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0) - } - titleTransition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame) - titleTransition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) - } else { - let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing - - titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize) - if titleFrame.size.width < size.width { - titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0) - } - titleFrame.origin.x = max(titleFrame.origin.x, clearBounds.minX + leftIconWidth) - titleTransition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame) - titleTransition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) - - var activityFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - activitySize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize) - if activitySize.width < size.width { - activityFrame.origin.x = -clearBounds.minX + floor((size.width - activityFrame.width) / 2.0) - } - titleTransition.updateFrameAdditiveToCenter(node: self.activityNode, frame: activityFrame) - } - - if let image = self.titleLeftIconNode.image { - titleTransition.updateFrame(node: self.titleLeftIconNode, frame: CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size)) - } - - var nextIconX: CGFloat = titleFrame.width - - titleTransition.updateFrame(view: self.titleVerifiedIconView, frame: CGRect(origin: CGPoint(x: 0.0, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize)) - - self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) - nextIconX -= titleCredibilitySize.width - if credibilityIconWidth > 0.0 { - nextIconX -= statusSpacing - } - - self.titleStatusIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleStatusSize.width, y: floor((titleFrame.height - titleStatusSize.height) / 2.0)), size: titleStatusSize) - nextIconX -= titleStatusSize.width - if let image = self.titleRightIconNode.image { - self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0 + UIScreenPixel, y: 6.0), size: image.size) + var titleInsets: UIEdgeInsets = .zero + if case .emojiStatus = self.titleVerifiedIcon, verifiedIconWidth > 0.0 { + titleInsets.left = verifiedIconWidth + } + + var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: size.width - leftIconWidth - credibilityIconWidth - verifiedIconWidth - statusIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), insets: titleInsets, animated: titleTransition.isAnimated) + titleSize.width += credibilityIconWidth + titleSize.width += verifiedIconWidth + if statusIconWidth > 0.0 { + titleSize.width += statusIconWidth + if credibilityIconWidth > 0.0 { + titleSize.width += statusSpacing } + } + + let activitySize = self.activityNode.updateLayout(CGSize(width: size.width - titleSideInset * 2.0, height: size.height), alignment: .center) + let titleInfoSpacing: CGFloat = 0.0 + + var activityFrame = CGRect() + + if activitySize.height.isZero { + titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) + if titleFrame.size.width < size.width { + titleFrame.origin.x = floor((size.width - titleFrame.width) / 2.0) + } + titleTransition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame) + titleTransition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) } else { - let titleSize = self.titleTextNode.updateLayout(size: CGSize(width: floor(clearBounds.width / 2.0 - leftIconWidth - credibilityIconWidth - verifiedIconWidth - statusIconWidth - rightIconWidth - titleSideInset * 2.0), height: size.height), animated: titleTransition.isAnimated) - let activitySize = self.activityNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0), height: size.height), alignment: .center) + let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing - let titleInfoSpacing: CGFloat = 8.0 - let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + verifiedIconWidth + statusIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing + let contentWidth = max(titleSize.width + rightIconWidth, activitySize.width) + var contentX = floor((size.width - contentWidth) / 2.0) + contentX = max(contentX, 20.0) - titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) + titleFrame = CGRect(origin: CGPoint(x: contentX + floor((contentWidth - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize) - titleTransition.updateFrameAdditiveToCenter(view: self.titleContainerView, frame: titleFrame) - titleTransition.updateFrameAdditiveToCenter(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) + titleFrame.origin.x = max(titleFrame.origin.x, leftIconWidth) + titleTransition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame) + titleTransition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) - titleTransition.updateFrameAdditiveToCenter(node: self.activityNode, frame: CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + verifiedIconWidth + statusIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize)) - - if let image = self.titleLeftIconNode.image { - self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size) - } - - var nextIconX: CGFloat = titleFrame.maxX - - self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize) - - self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) - nextIconX -= titleCredibilitySize.width - - titleTransition.updateFrame(view: self.titleStatusIconView, frame: CGRect(origin: CGPoint(x: nextIconX - titleStatusSize.width, y: floor((titleFrame.height - titleStatusSize.height) / 2.0)), size: titleStatusSize)) - nextIconX -= titleStatusSize.width - - if let image = self.titleRightIconNode.image { - titleTransition.updateFrame(node: self.titleRightIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size)) - } + activityFrame = CGRect(origin: CGPoint(x: titleFrame.minX + floor((titleFrame.width - activitySize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize) + titleTransition.updateFrameAdditiveToCenter(node: self.activityNode, frame: activityFrame.offsetBy(dx: activitySize.width * 0.5, dy: 0.0)) + } + + if let image = self.titleLeftIconNode.image { + titleTransition.updateFrame(node: self.titleLeftIconNode, frame: CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size)) + } + + var nextIconX: CGFloat = titleFrame.width + + titleTransition.updateFrame(view: self.titleVerifiedIconView, frame: CGRect(origin: CGPoint(x: 0.0, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize)) + + self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) + nextIconX -= titleCredibilitySize.width + if credibilityIconWidth > 0.0 { + nextIconX -= statusSpacing + } + + self.titleStatusIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleStatusSize.width, y: floor((titleFrame.height - titleStatusSize.height) / 2.0)), size: titleStatusSize) + nextIconX -= titleStatusSize.width + + if let image = self.titleRightIconNode.image { + self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0 + UIScreenPixel, y: 6.0), size: image.size) } self.pointerInteraction = PointerInteraction(view: self, style: .rectangle(CGSize(width: titleFrame.width + 16.0, height: 40.0))) - return titleFrame + var backgroundFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: 6.0), size: CGSize(width: titleFrame.width, height: 44.0)) + if !activityFrame.isEmpty { + backgroundFrame.origin.x = min(backgroundFrame.minX, activityFrame.minX) + backgroundFrame.size.width = max(backgroundFrame.maxX, activityFrame.maxX) - backgroundFrame.minX + } + backgroundFrame = backgroundFrame.insetBy(dx: -12.0, dy: 0.0) + let componentTransition = ComponentTransition(transition) + componentTransition.setFrame(view: self.backgroundView, frame: backgroundFrame) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: false, transition: componentTransition) + + return availableSize } @objc private func buttonPressed() { @@ -1162,122 +1174,3 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -offset.x, y: -offset.y), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) } } - -public final class ChatTitleComponent: Component { - public let context: AccountContext - public let theme: PresentationTheme - public let strings: PresentationStrings - public let dateTimeFormat: PresentationDateTimeFormat - public let nameDisplayOrder: PresentationPersonNameOrder - public let content: ChatTitleContent - public let tapped: () -> Void - public let longTapped: () -> Void - - public init( - context: AccountContext, - theme: PresentationTheme, - strings: PresentationStrings, - dateTimeFormat: PresentationDateTimeFormat, - nameDisplayOrder: PresentationPersonNameOrder, - content: ChatTitleContent, - tapped: @escaping () -> Void, - longTapped: @escaping () -> Void - ) { - self.context = context - self.theme = theme - self.strings = strings - self.dateTimeFormat = dateTimeFormat - self.nameDisplayOrder = nameDisplayOrder - self.content = content - self.tapped = tapped - self.longTapped = longTapped - } - - public static func ==(lhs: ChatTitleComponent, rhs: ChatTitleComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.theme !== rhs.theme { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.dateTimeFormat != rhs.dateTimeFormat { - return false - } - if lhs.nameDisplayOrder != rhs.nameDisplayOrder { - return false - } - if lhs.content != rhs.content { - return false - } - return true - } - - public final class View: UIView { - public private(set) var contentView: ChatTitleView? - - private var component: ChatTitleComponent? - - override init(frame: CGRect) { - super.init(frame: frame) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(component: ChatTitleComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.component = component - - let contentView: ChatTitleView - if let current = self.contentView { - contentView = current - } else { - contentView = ChatTitleView( - context: component.context, - theme: component.theme, - strings: component.strings, - dateTimeFormat: component.dateTimeFormat, - nameDisplayOrder: component.nameDisplayOrder, - animationCache: component.context.animationCache, - animationRenderer: component.context.animationRenderer - ) - contentView.pressed = { [weak self] in - guard let self else { - return - } - self.component?.tapped() - } - contentView.longPressed = { [weak self] in - guard let self else { - return - } - self.component?.longTapped() - } - contentView.manualLayout = true - self.contentView = contentView - self.addSubview(contentView) - } - - if contentView.titleContent != component.content { - contentView.titleContent = component.content - } - contentView.updateThemeAndStrings(theme: component.theme, strings: component.strings, hasEmbeddedTitleContent: false) - - let _ = contentView.updateLayout(size: availableSize, clearBounds: CGRect(origin: CGPoint(), size: availableSize), transition: transition.containedViewLayoutTransition) - transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: availableSize)) - - return availableSize - } - } - - public func makeView() -> View { - return View(frame: CGRect()) - } - - public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/TitleIconComponent.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/TitleIconComponent.swift new file mode 100644 index 00000000..53f34d7f --- /dev/null +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/TitleIconComponent.swift @@ -0,0 +1,96 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData + +final class TitleIconComponent: Component { + enum Kind { + case mute + case lock + } + + let kind: Kind + let color: UIColor + + init( + kind: Kind, + color: UIColor + ) { + self.kind = kind + self.color = color + } + + static func ==(lhs: TitleIconComponent, rhs: TitleIconComponent) -> Bool { + if lhs.kind != rhs.kind { + return false + } + if lhs.color != rhs.color { + return false + } + return true + } + + final class View: UIView { + let iconView: UIImageView + + var component: TitleIconComponent? + + override init(frame: CGRect) { + self.iconView = UIImageView() + + super.init(frame: frame) + + self.addSubview(self.iconView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: TitleIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + if self.component?.kind != component.kind { + switch component.kind { + case .mute: + self.iconView.image = generateImage(CGSize(width: 9.0, height: 9.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.white.cgColor) + + let _ = try? drawSvgPath(context, path: "M2.97607626,2.27306995 L5.1424026,0.18411241 C5.25492443,0.0756092198 5.40753677,0.0146527621 5.56666667,0.0146527621 C5.89803752,0.0146527621 6.16666667,0.273688014 6.16666667,0.593224191 L6.16666667,5.47790407 L8.86069303,8.18395735 C9.05193038,8.37604845 9.04547086,8.68126082 8.84626528,8.86566828 C8.6470597,9.05007573 8.33054317,9.0438469 8.13930581,8.85175581 L0.139306972,0.816042647 C-0.0519303838,0.623951552 -0.0454708626,0.318739175 0.153734717,0.134331724 C0.352940296,-0.0500757275 0.669456833,-0.0438469035 0.860694189,0.148244192 L2.97607626,2.27306995 Z M0.933196438,2.75856564 L6.16666667,8.01539958 L6.16666667,8.40677707 C6.16666667,8.56022375 6.10345256,8.70738566 5.99093074,8.81588885 C5.75661616,9.04183505 5.37671717,9.04183505 5.1424026,8.81588885 L2.59763107,6.36200202 C2.53511895,6.30172247 2.45033431,6.26785777 2.36192881,6.26785777 L1.16666667,6.26785777 C0.614381917,6.26785777 0.166666667,5.83613235 0.166666667,5.30357206 L0.166666667,3.6964292 C0.166666667,3.24138962 0.493527341,2.85996592 0.933196438,2.75856564 Z ") + })?.withRenderingMode(.alwaysTemplate) + case .lock: + self.iconView.image = generateImage(CGSize(width: 9.0, height: 13.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.translateBy(x: 0.0, y: 1.0) + + context.setFillColor(UIColor.white.cgColor) + context.setStrokeColor(UIColor.white.cgColor) + context.setLineWidth(1.32) + + let _ = try? drawSvgPath(context, path: "M4.5,0.600000024 C5.88071187,0.600000024 7,1.88484952 7,3.46979169 L7,7.39687502 C7,8.9818172 5.88071187,10.2666667 4.5,10.2666667 C3.11928813,10.2666667 2,8.9818172 2,7.39687502 L2,3.46979169 C2,1.88484952 3.11928813,0.600000024 4.5,0.600000024 S ") + let _ = try? drawSvgPath(context, path: "M1.32,5.65999985 L7.68,5.65999985 C8.40901587,5.65999985 9,6.25098398 9,6.97999985 L9,10.6733332 C9,11.4023491 8.40901587,11.9933332 7.68,11.9933332 L1.32,11.9933332 C0.59098413,11.9933332 1.11022302e-16,11.4023491 0,10.6733332 L2.22044605e-16,6.97999985 C1.11022302e-16,6.25098398 0.59098413,5.65999985 1.32,5.65999985 Z ") + })?.withRenderingMode(.alwaysTemplate) + } + } + + let size = CGSize(width: 14.0, height: 14.0) + + if let image = self.iconView.image { + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - image.size.width) * 0.5), y: floorToScreenPixels((size.height - image.size.height) * 0.5)), size: image.size) + self.iconView.frame = iconFrame + } + self.iconView.tintColor = component.color + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/CocoonInfoScreen/BUILD b/submodules/TelegramUI/Components/CocoonInfoScreen/BUILD new file mode 100644 index 00000000..014f566a --- /dev/null +++ b/submodules/TelegramUI/Components/CocoonInfoScreen/BUILD @@ -0,0 +1,37 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "CocoonInfoScreen", + module_name = "CocoonInfoScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/AccountContext", + "//submodules/TelegramStringFormatting", + "//submodules/TextFormat", + "//submodules/PresentationDataUtils", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/SheetComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/CocoonInfoScreen/Sources/CocoonInfoScreen.swift b/submodules/TelegramUI/Components/CocoonInfoScreen/Sources/CocoonInfoScreen.swift new file mode 100644 index 00000000..61d04d1e --- /dev/null +++ b/submodules/TelegramUI/Components/CocoonInfoScreen/Sources/CocoonInfoScreen.swift @@ -0,0 +1,669 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import PresentationDataUtils +import ComponentFlow +import ViewControllerComponent +import SheetComponent +import MultilineTextComponent +import BalancedTextComponent +import BundleIconComponent +import Markdown +import TextFormat +import TelegramStringFormatting +import GlassBarButtonComponent +import ButtonComponent +import LottieComponent + +private final class CocoonInfoSheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let animateOut: ActionSlot> + let getController: () -> ViewController? + + init( + context: AccountContext, + animateOut: ActionSlot>, + getController: @escaping () -> ViewController? + ) { + self.context = context + self.animateOut = animateOut + self.getController = getController + } + + static func ==(lhs: CocoonInfoSheetContent, rhs: CocoonInfoSheetContent) -> Bool { + if lhs.context !== rhs.context { + return false + } + return true + } + + final class State: ComponentState { + private let context: AccountContext + private let animateOut: ActionSlot> + private let getController: () -> ViewController? + + fileprivate let playButtonAnimation = ActionSlot() + private var didPlayAnimation = false + + init( + context: AccountContext, + animateOut: ActionSlot>, + getController: @escaping () -> ViewController? + ) { + self.context = context + self.animateOut = animateOut + self.getController = getController + + super.init() + } + + func playAnimationIfNeeded() { + if !self.didPlayAnimation { + self.didPlayAnimation = true + self.playButtonAnimation.invoke(Void()) + } + } + + func dismiss(animated: Bool) { + guard let controller = self.getController() as? CocoonInfoScreen else { + return + } + if animated { + self.animateOut.invoke(Action { [weak controller] _ in + controller?.dismiss(completion: nil) + }) + } else { + controller.dismiss(animated: false) + } + } + } + + func makeState() -> State { + return State(context: self.context, animateOut: self.animateOut, getController: self.getController) + } + + static var body: Body { + let closeButton = Child(GlassBarButtonComponent.self) + let icon = Child(BundleIconComponent.self) + let title = Child(BalancedTextComponent.self) + let text = Child(BalancedTextComponent.self) + let list = Child(List.self) + let additionalText = Child(MultilineTextComponent.self) + let button = Child(ButtonComponent.self) + + let navigateDisposable = MetaDisposable() + + return { context in + let component = context.component + let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + let state = context.state + + let theme = environment.theme + let strings = environment.strings + + let sideInset: CGFloat = 30.0 + environment.safeInsets.left + let textSideInset: CGFloat = 30.0 + environment.safeInsets.left + + let titleFont = Font.bold(24.0) + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + + let textColor = theme.actionSheet.primaryTextColor + let secondaryTextColor = theme.actionSheet.secondaryTextColor + let linkColor = theme.actionSheet.controlAccentColor + + let spacing: CGFloat = 16.0 + var contentSize = CGSize(width: context.availableSize.width, height: 28.0) + + let icon = icon.update( + component: BundleIconComponent( + name: "Premium/Cocoon", tintColor: nil + ), + availableSize: context.availableSize, + transition: context.transition + ) + context.add(icon + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + icon.size.height / 2.0)) + ) + contentSize.height += icon.size.height + contentSize.height += 14.0 + + let title = title.update( + component: BalancedTextComponent( + text: .plain(NSAttributedString(string: strings.CocoonInfo_Title, font: titleFont, textColor: textColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.1 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) + ) + contentSize.height += title.size.height + contentSize.height += spacing - 8.0 + + let attributedText = parseMarkdownIntoAttributedString( + strings.CocoonInfo_Description, + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: textFont, textColor: textColor), + bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), + link: MarkdownAttributeSet(font: textFont, textColor: linkColor), + linkAttribute: { _ in return nil } + ) + ) + let text = text.update( + component: BalancedTextComponent( + text: .plain(attributedText), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(text + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) + ) + contentSize.height += text.size.height + contentSize.height += spacing + 9.0 + + var items: [AnyComponentWithIdentity] = [] + items.append( + AnyComponentWithIdentity( + id: "private", + component: AnyComponent(ParagraphComponent( + title: strings.CocoonInfo_Private_Title, + titleColor: textColor, + text: strings.CocoonInfo_Private_Text, + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Ads/Privacy", + iconColor: linkColor, + action: { _, _ in + } + )) + ) + ) + items.append( + AnyComponentWithIdentity( + id: "efficient", + component: AnyComponent(ParagraphComponent( + title: strings.CocoonInfo_Efficient_Title, + titleColor: textColor, + text: strings.CocoonInfo_Efficient_Text, + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Premium/Stats", + iconColor: linkColor, + action: { _, _ in + } + )) + ) + ) + items.append( + AnyComponentWithIdentity( + id: "for_everyone", + component: AnyComponent(ParagraphComponent( + title: strings.CocoonInfo_ForEveryone_Title, + titleColor: textColor, + text: strings.CocoonInfo_ForEveryone_Text, + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Chat/Input/Accessory Panels/Gift", + iconColor: linkColor, + action: { attributes, _ in + guard let link = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, let controller = environment.controller() else { + return + } + switch link { + case "telegram": + component.context.sharedContext.handleTextLinkAction(context: component.context, peerId: nil, navigateDisposable: navigateDisposable, controller: controller, action: .tap, itemLink: .url(url: "https://t.me/cocoon", concealed: false)) + case "web": + component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: "https://cocoon.org", forceExternal: true, presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {}) + default: + break + } + } + )) + ) + ) + + let list = list.update( + component: List(items), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 10000.0), + transition: context.transition + ) + context.add(list + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0)) + ) + contentSize.height += list.size.height + contentSize.height += spacing - 6.0 + + let attributedAdditionalText = parseMarkdownIntoAttributedString( + strings.CocoonInfo_IntergrateInfo, + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: secondaryTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: secondaryTextColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: linkColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + ) + let additionalText = additionalText.update( + component: MultilineTextComponent( + text: .plain(attributedAdditionalText), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: linkColor.withAlphaComponent(0.1), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { _, _ in + guard let controller = environment.controller() else { + return + } + component.context.sharedContext.handleTextLinkAction(context: component.context, peerId: nil, navigateDisposable: navigateDisposable, controller: controller, action: .tap, itemLink: .url(url: "https://t.me/cocoon?direct", concealed: false)) + } + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(additionalText + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + additionalText.size.height / 2.0)) + ) + contentSize.height += additionalText.size.height + contentSize.height += spacing + 6.0 + + let closeButton = closeButton.update( + component: GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { [weak state] _ in + guard let state else { + return + } + state.dismiss(animated: true) + } + ), + availableSize: CGSize(width: 40.0, height: 40.0), + transition: .immediate + ) + context.add(closeButton + .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0)) + ) + + + var buttonTitle: [AnyComponentWithIdentity] = [] + buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "anim_ok"), + color: theme.list.itemCheckColors.foregroundColor, + startingPosition: .begin, + size: CGSize(width: 28.0, height: 28.0), + playOnce: state.playButtonAnimation + )))) + buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent( + text: strings.CocoonInfo_Understood, + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor + )))) + + let button = button.update( + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(HStack(buttonTitle, spacing: 2.0)) + ), + isEnabled: true, + displaysProgress: false, + action: { [weak state] in + guard let state else { + return + } + state.dismiss(animated: true) + } + ), + availableSize: CGSize(width: context.availableSize.width - 30.0 * 2.0, height: 52.0), + transition: .immediate + ) + context.add(button + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) + ) + contentSize.height += button.size.height + + contentSize.height += 30.0 + + state.playAnimationIfNeeded() + + return contentSize + } + } +} + +final class CocoonInfoSheetComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + + init( + context: AccountContext + ) { + self.context = context + } + + static func ==(lhs: CocoonInfoSheetComponent, rhs: CocoonInfoSheetComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + return true + } + + static var body: Body { + let sheet = Child(SheetComponent.self) + let animateOut = StoredActionSlot(Action.self) + + let sheetExternalState = SheetComponent.ExternalState() + + return { context in + let environment = context.environment[EnvironmentType.self] + let controller = environment.controller + + let sheet = sheet.update( + component: SheetComponent( + content: AnyComponent(CocoonInfoSheetContent( + context: context.component.context, + animateOut: animateOut, + getController: controller + )), + style: .glass, + backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), + followContentSizeChanges: true, + clipsContent: true, + autoAnimateOut: false, + externalState: sheetExternalState, + animateOut: animateOut, + onPan: { + }, + willDismiss: { + } + ), + environment: { + environment + SheetComponentEnvironment( + isDisplaying: environment.value.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { animated in + if animated { + if let controller = controller() as? CocoonInfoScreen { + animateOut.invoke(Action { _ in + controller.dismiss(completion: nil) + }) + } + } else { + if let controller = controller() as? CocoonInfoScreen { + controller.dismiss(completion: nil) + } + } + } + ) + }, + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(sheet + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + if let controller = controller(), !controller.automaticallyControlPresentationContextLayout { + var sideInset: CGFloat = 0.0 + var bottomInset: CGFloat = max(environment.safeInsets.bottom, sheetExternalState.contentHeight) + if case .regular = environment.metrics.widthClass { + sideInset = floor((context.availableSize.width - 430.0) / 2.0) - 12.0 + bottomInset = (context.availableSize.height - sheetExternalState.contentHeight) / 2.0 + sheetExternalState.contentHeight + } + + let layout = ContainerViewLayout( + size: context.availableSize, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0), + safeInsets: UIEdgeInsets(top: 0.0, left: max(sideInset, environment.safeInsets.left), bottom: 0.0, right: max(sideInset, environment.safeInsets.right)), + additionalInsets: .zero, + statusBarHeight: environment.statusBarHeight, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ) + controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition) + } + + return context.availableSize + } + } +} + +public final class CocoonInfoScreen: ViewControllerComponentContainer { + private let context: AccountContext + + public init( + context: AccountContext + ) { + self.context = context + + super.init( + context: context, + component: CocoonInfoSheetComponent( + context: context + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: .default + ) + + self.navigationPresentation = .flatModal + self.automaticallyControlPresentationContextLayout = false + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + public override func viewDidLoad() { + super.viewDidLoad() + + self.view.disablesInteractiveModalDismiss = true + } + + public override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { + view.dismissAnimated() + } + } +} + +private final class ParagraphComponent: CombinedComponent { + let title: String + let titleColor: UIColor + let text: String + let textColor: UIColor + let accentColor: UIColor + let iconName: String + let iconColor: UIColor + let action: (([NSAttributedString.Key: Any], Int) -> Void)? + + public init( + title: String, + titleColor: UIColor, + text: String, + textColor: UIColor, + accentColor: UIColor, + iconName: String, + iconColor: UIColor, + action: (([NSAttributedString.Key: Any], Int) -> Void)? + ) { + self.title = title + self.titleColor = titleColor + self.text = text + self.textColor = textColor + self.accentColor = accentColor + self.iconName = iconName + self.iconColor = iconColor + self.action = action + } + + static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.titleColor != rhs.titleColor { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.textColor != rhs.textColor { + return false + } + if lhs.accentColor != rhs.accentColor { + return false + } + if lhs.iconName != rhs.iconName { + return false + } + if lhs.iconColor != rhs.iconColor { + return false + } + return true + } + + static var body: Body { + let title = Child(MultilineTextComponent.self) + let text = Child(MultilineTextComponent.self) + let icon = Child(BundleIconComponent.self) + + return { context in + let component = context.component + + let leftInset: CGFloat = 32.0 + let rightInset: CGFloat = 24.0 + let textSideInset: CGFloat = leftInset + 8.0 + let spacing: CGFloat = 5.0 + + let textTopInset: CGFloat = 9.0 + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.title, + font: Font.semibold(15.0), + textColor: component.titleColor, + paragraphAlignment: .natural + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + let textColor = component.textColor + let accentColor = component.accentColor + let markdownAttributes = MarkdownAttributes( + body: MarkdownAttributeSet(font: textFont, textColor: textColor), + bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), + link: MarkdownAttributeSet(font: textFont, textColor: accentColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + + let text = text.update( + component: MultilineTextComponent( + text: .markdown(text: component.text, attributes: markdownAttributes), + horizontalAlignment: .natural, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: accentColor.withAlphaComponent(0.1), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { attributes, index in + component.action?(attributes, index) + } + ), + availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height), + transition: .immediate + ) + + let icon = icon.update( + component: BundleIconComponent( + name: component.iconName, + tintColor: component.iconColor + ), + availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), + transition: .immediate + ) + + context.add(title + .position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0)) + ) + + context.add(text + .position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0)) + ) + + context.add(icon + .position(CGPoint(x: 15.0, y: textTopInset + 18.0)) + ) + + return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 20.0) + } + } +} diff --git a/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD b/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD index 691a1891..f10ddb89 100644 --- a/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD +++ b/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD @@ -44,7 +44,6 @@ swift_library( "//submodules/TelegramUI/Components/ListComposePollOptionComponent", "//submodules/ComposePollUI", "//submodules/Markdown", - "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ diff --git a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift index 2a8d1fa1..0f9c4740 100644 --- a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift +++ b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift @@ -29,24 +29,26 @@ import TextFieldComponent import ListComposePollOptionComponent import Markdown import PresentationDataUtils -import EdgeEffect import GlassBarButtonComponent final class ComposeTodoScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let peer: EnginePeer let initialData: ComposeTodoScreen.InitialData let completion: (TelegramMediaTodo) -> Void init( context: AccountContext, + overNavigationContainer: UIView, peer: EnginePeer, initialData: ComposeTodoScreen.InitialData, completion: @escaping (TelegramMediaTodo) -> Void ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.peer = peer self.initialData = initialData self.completion = completion @@ -69,7 +71,6 @@ final class ComposeTodoScreenComponent: Component { final class View: UIView, UIScrollViewDelegate { private let scrollView: UIScrollView - private let edgeEffectView: EdgeEffectView private let todoTextSection = ComponentView() @@ -131,8 +132,6 @@ final class ComposeTodoScreenComponent: Component { self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.alwaysBounceVertical = true - self.edgeEffectView = EdgeEffectView() - self.todoItemsSectionContainer = ListSectionContentView(frame: CGRect()) self.todoItemsSectionContainer.automaticallyLayoutExternalContentBackgroundView = false @@ -141,8 +140,6 @@ final class ComposeTodoScreenComponent: Component { self.scrollView.delegate = self self.addSubview(self.scrollView) - self.addSubview(self.edgeEffectView) - let reorderRecognizer = ReorderGestureRecognizer( shouldBegin: { [weak self] point in guard let self, let (id, item) = self.item(at: point) else { @@ -468,7 +465,6 @@ final class ComposeTodoScreenComponent: Component { pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, @@ -1539,11 +1535,6 @@ final class ComposeTodoScreenComponent: Component { } } } - - let edgeEffectHeight: CGFloat = 80.0 - let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) - transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) let title: String if !component.initialData.canEdit && component.initialData.existingTodo != nil { @@ -1571,23 +1562,23 @@ final class ComposeTodoScreenComponent: Component { let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((environment.navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { - self.addSubview(titleView) + component.overNavigationContainer.addSubview(titleView) } transition.setFrame(view: titleView, frame: titleFrame) } - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let cancelButtonSize = self.cancelButton.update( transition: transition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: environment.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -1603,7 +1594,7 @@ final class ComposeTodoScreenComponent: Component { let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize) if let cancelButtonView = self.cancelButton.view { if cancelButtonView.superview == nil { - self.addSubview(cancelButtonView) + component.overNavigationContainer.addSubview(cancelButtonView) } transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) } @@ -1639,7 +1630,7 @@ final class ComposeTodoScreenComponent: Component { let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize) if let doneButtonView = self.doneButton.view { if doneButtonView.superview == nil { - self.addSubview(doneButtonView) + component.overNavigationContainer.addSubview(doneButtonView) } transition.setFrame(view: doneButtonView, frame: doneButtonFrame) } @@ -1704,6 +1695,8 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont fileprivate let completion: (TelegramMediaTodo) -> Void private var isDismissed: Bool = false + private let overNavigationContainer: UIView + fileprivate private(set) var sendButtonItem: UIBarButtonItem? public var isMinimized: Bool = false @@ -1747,12 +1740,15 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont self.context = context self.completion = completion + self.overNavigationContainer = SparseContainerView() + super.init(context: context, component: ComposeTodoScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, peer: peer, initialData: initialData, completion: completion - ), navigationBarAppearance: .transparent, theme: .default) + ), navigationBarAppearance: .default, theme: .default) self._hasGlassStyle = true @@ -1787,6 +1783,10 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont return componentView.attemptNavigation(complete: complete) } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift index ae98d94b..47447151 100644 --- a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift @@ -897,7 +897,7 @@ final class NewContactScreenComponent: Component { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift index bfb74ff6..b87ce202 100644 --- a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift @@ -175,7 +175,7 @@ final class PhoneInputItemNode: ListViewItemNode, ItemListItemNode { self.checkNode.displaysAsynchronously = false self.checkNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.phoneBackground) self.addSubnode(self.countryButton) diff --git a/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift b/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift index ae83820e..e7da15e9 100644 --- a/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift +++ b/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift @@ -529,7 +529,7 @@ private final class SheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent( BundleIconComponent( name: isBack ? "Navigation/Back" : "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak state] _ in diff --git a/submodules/TelegramUI/Components/EdgeEffect/BUILD b/submodules/TelegramUI/Components/EdgeEffect/BUILD index 62ff9343..e5bc97de 100644 --- a/submodules/TelegramUI/Components/EdgeEffect/BUILD +++ b/submodules/TelegramUI/Components/EdgeEffect/BUILD @@ -12,6 +12,7 @@ swift_library( deps = [ "//submodules/Display", "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift index e48426b0..ecd4bc66 100644 --- a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift +++ b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import ComponentFlow +import ComponentDisplayAdapters public class EdgeEffectView: UIView { public enum Edge { @@ -27,7 +28,13 @@ public class EdgeEffectView: UIView { fatalError("init(coder:) has not been implemented") } - public func update(content: UIColor, blur: Bool = false, alpha: CGFloat = 0.65, rect: CGRect, edge: Edge, edgeSize: CGFloat, transition: ComponentTransition) { + public func update(content: UIColor, blur: Bool = false, alpha: CGFloat = 0.75, rect: CGRect, edge: Edge, edgeSize: CGFloat, transition: ComponentTransition) { + #if DEBUG && false + let content: UIColor = .blue + let blur: Bool = !"".isEmpty + self.backgroundColor = .blue + #endif + transition.setBackgroundColor(view: self.contentView, color: content) switch edge { @@ -76,38 +83,39 @@ public class EdgeEffectView: UIView { } if blur { - let gradientMaskLayer = SimpleGradientLayer() - let baseGradientAlpha: CGFloat = 1.0 - let numSteps = 8 - let firstStep = 1 - let firstLocation = 0.8 - gradientMaskLayer.colors = (0 ..< numSteps).map { i in - if i < firstStep { - return UIColor(white: 1.0, alpha: 1.0).cgColor - } else { - let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) - let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step) - return UIColor(white: 1.0, alpha: baseGradientAlpha * value).cgColor - } - } - gradientMaskLayer.locations = (0 ..< numSteps).map { i -> NSNumber in - if i < firstStep { - return 0.0 as NSNumber - } else { - let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) - return (firstLocation + (1.0 - firstLocation) * step) as NSNumber - } - } - let blurView: VariableBlurView if let current = self.blurView { blurView = current } else { + let gradientMaskLayer = SimpleGradientLayer() + let baseGradientAlpha: CGFloat = 1.0 + let numSteps = 8 + let firstStep = 1 + let firstLocation = 0.8 + gradientMaskLayer.colors = (0 ..< numSteps).map { i in + if i < firstStep { + return UIColor(white: 1.0, alpha: 1.0).cgColor + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step) + return UIColor(white: 1.0, alpha: baseGradientAlpha * value).cgColor + } + } + gradientMaskLayer.locations = (0 ..< numSteps).map { i -> NSNumber in + if i < firstStep { + return 0.0 as NSNumber + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + return (firstLocation + (1.0 - firstLocation) * step) as NSNumber + } + } + blurView = VariableBlurView(gradientMask: self.contentMaskView.image ?? UIImage(), maxBlurRadius: 8.0) blurView.layer.mask = gradientMaskLayer self.insertSubview(blurView, at: 0) self.blurView = blurView } + blurView.update(size: bounds.size, transition: transition.containedViewLayoutTransition) transition.setFrame(view: blurView, frame: bounds) if let maskLayer = blurView.layer.mask { transition.setFrame(layer: maskLayer, frame: bounds) @@ -271,4 +279,10 @@ public final class VariableBlurView: UIVisualEffectView { backdropLayer?.filters = [variableBlur] backdropLayer?.setValue(UIScreenScale, forKey: "scale") } + + public func update(size: CGSize, transition: ContainedViewLayoutTransition) { + for layer in self.layer.sublayers ?? [] { + transition.updateFrame(layer: layer, frame: CGRect(origin: CGPoint(), size: size)) + } + } } diff --git a/submodules/TelegramUI/Components/EmojiGameStakeScreen/BUILD b/submodules/TelegramUI/Components/EmojiGameStakeScreen/BUILD new file mode 100644 index 00000000..0200885e --- /dev/null +++ b/submodules/TelegramUI/Components/EmojiGameStakeScreen/BUILD @@ -0,0 +1,55 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "EmojiGameStakeScreen", + module_name = "EmojiGameStakeScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/ItemListUI", + "//submodules/TelegramStringFormatting", + "//submodules/PresentationDataUtils", + "//submodules/Components/SheetComponent", + "//submodules/UndoUI", + "//submodules/TextFormat", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/ScrollComponent", + "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/PasswordSetupUI", + "//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController", + "//submodules/TelegramUI/Components/ChatScheduleTimeController", + "//submodules/TelegramUI/Components/TabSelectorComponent", + "//submodules/TelegramUI/Components/Stars/BalanceNeededScreen", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/LottieComponentResourceContent", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/Components/MultilineTextWithEntitiesComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/EmojiGameStakeScreen/Sources/EmojiGameStakeScreen.swift b/submodules/TelegramUI/Components/EmojiGameStakeScreen/Sources/EmojiGameStakeScreen.swift new file mode 100644 index 00000000..59a534c9 --- /dev/null +++ b/submodules/TelegramUI/Components/EmojiGameStakeScreen/Sources/EmojiGameStakeScreen.swift @@ -0,0 +1,2220 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import SwiftSignalKit +import Postbox +import TelegramCore +import Markdown +import TextFormat +import TelegramPresentationData +import ViewControllerComponent +import SheetComponent +import BalancedTextComponent +import MultilineTextComponent +import MultilineTextWithEntitiesComponent +import BundleIconComponent +import ButtonComponent +import AccountContext +import PresentationDataUtils +import ListSectionComponent +import TelegramStringFormatting +import UndoUI +import ListActionItemComponent +import PresentationDataUtils +import BalanceNeededScreen +import GlassBarButtonComponent +import GlassBackgroundComponent +import StarsBalanceOverlayComponent +import LottieComponent +import LottieComponentResourceContent +import EdgeEffect +import PlainButtonComponent + +private let amountTag = GenericComponentViewTag() + +private final class SheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let gameInfo: EmojiGameInfo.Info + let controller: () -> ViewController? + let dismiss: () -> Void + + init( + context: AccountContext, + gameInfo: EmojiGameInfo.Info, + controller: @escaping () -> ViewController?, + dismiss: @escaping () -> Void + ) { + self.context = context + self.gameInfo = gameInfo + self.controller = controller + self.dismiss = dismiss + } + + static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { + return true + } + + static var body: (CombinedComponentContext) -> CGSize { + let description = Child(BalancedTextComponent.self) + let resultsTitle = Child(MultilineTextComponent.self) + let results = Child(VStack.self) + let resultsFooter = Child(MultilineTextWithEntitiesComponent.self) + let amountSection = Child(ListSectionComponent.self) + let button = Child(ButtonComponent.self) + + let body: (CombinedComponentContext) -> CGSize = { (context: CombinedComponentContext) -> CGSize in + let environment = context.environment[EnvironmentType.self] + let component = context.component + let state = context.state + + state.component = component + + let controller = environment.controller + + let theme = environment.theme.withModalBlocksBackground() + let strings = environment.strings + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let textSideInset: CGFloat = 32.0 + environment.safeInsets.left + var contentSize = CGSize(width: context.availableSize.width, height: 75.0) + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + let textColor = theme.actionSheet.primaryTextColor + let linkColor = theme.actionSheet.controlAccentColor + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + + let description = description.update( + component: BalancedTextComponent( + text: .markdown(text: strings.EmojiStake_Description, attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(description + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + description.size.height * 0.5)) + ) + contentSize.height += description.size.height + contentSize.height += 32.0 + + let resultsTitle = resultsTitle.update( + component: MultilineTextComponent(text: .plain(NSAttributedString( + string: strings.EmojiStake_ResultsTitle.uppercased(), + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + ))), + availableSize: context.availableSize, + transition: .immediate + ) + context.add(resultsTitle + .position(CGPoint(x: textSideInset + resultsTitle.size.width * 0.5, y: contentSize.height + resultsTitle.size.height * 0.5)) + ) + contentSize.height += resultsTitle.size.height + contentSize.height += 6.0 + + let resultSpacing: CGFloat = 8.0 + let resultSize = CGSize(width: (context.availableSize.width - sideInset * 2.0 - resultSpacing * 3.0) / 4.0, height: 64.0) + let doubleResultSize = CGSize(width: resultSize.width * 2.0 + resultSpacing, height: resultSize.height) + + var resultValue1: Int32 = 0 + var resultValue2: Int32 = 300 + var resultValue3: Int32 = 600 + var resultValue4: Int32 = 1300 + var resultValue5: Int32 = 1600 + var resultValue6: Int32 = 2000 + var resultValue7: Int32 = 20000 + if context.component.gameInfo.parameters.count == 7 { + resultValue1 = context.component.gameInfo.parameters[0] + resultValue2 = context.component.gameInfo.parameters[1] + resultValue3 = context.component.gameInfo.parameters[2] + resultValue4 = context.component.gameInfo.parameters[3] + resultValue5 = context.component.gameInfo.parameters[4] + resultValue6 = context.component.gameInfo.parameters[5] + resultValue7 = context.component.gameInfo.parameters[6] + } + + let results = results.update( + component: VStack([ + AnyComponentWithIdentity(id: "first", component: AnyComponent( + HStack([ + AnyComponentWithIdentity(id: 1, component: AnyComponent( + ResultCellComponent(context: component.context, theme: environment.theme, files: state.emojiFiles.flatMap { [$0[1]] }, value: resultValue1, size: resultSize) + )), + AnyComponentWithIdentity(id: 2, component: AnyComponent( + ResultCellComponent(context: component.context, theme: environment.theme, files: state.emojiFiles.flatMap { [$0[2]] }, value: resultValue2, size: resultSize) + )), + AnyComponentWithIdentity(id: 3, component: AnyComponent( + ResultCellComponent(context: component.context, theme: environment.theme, files: state.emojiFiles.flatMap { [$0[3]] }, value: resultValue3, size: resultSize) + )), + AnyComponentWithIdentity(id: 4, component: AnyComponent( + ResultCellComponent(context: component.context, theme: environment.theme, files: state.emojiFiles.flatMap { [$0[4]] }, value: resultValue4, size: resultSize) + )) + ], spacing: resultSpacing) + )), + AnyComponentWithIdentity(id: "second", component: AnyComponent( + HStack([ + AnyComponentWithIdentity(id: 5, component: AnyComponent( + ResultCellComponent(context: component.context, theme: environment.theme, files: state.emojiFiles.flatMap { [$0[5]] }, value: resultValue5, size: resultSize) + )), + AnyComponentWithIdentity(id: 6, component: AnyComponent( + ResultCellComponent(context: component.context, theme: environment.theme, files: state.emojiFiles.flatMap { [$0[6]] }, value: resultValue6, size: resultSize) + )), + AnyComponentWithIdentity(id: 7, component: AnyComponent( + ResultCellComponent(context: component.context, theme: environment.theme, files: state.emojiFiles.flatMap { [$0[6], $0[6], $0[6]] }, value: resultValue7, size: doubleResultSize) + )), + ], spacing: resultSpacing) + )) + ], spacing: resultSpacing), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), + transition: context.transition + ) + context.add(results + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + results.size.height * 0.5)) + ) + contentSize.height += results.size.height + contentSize.height += 7.0 + + let resultsFooterAttributedText = NSMutableAttributedString( + string: strings.EmojiStake_StreakInfo, + font: Font.regular(13.0), + textColor: theme.list.freeTextColor + ) + if let emojiFile = state.emojiFiles?[6] { + let range = (resultsFooterAttributedText.string as NSString).range(of: "#") + if range.location != NSNotFound { + resultsFooterAttributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: emojiFile, custom: .dice, enableAnimation: false), range: range) + } + } + + let resultsFooter = resultsFooter.update( + component: MultilineTextWithEntitiesComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + placeholderColor: .clear, + text: .plain(resultsFooterAttributedText), + maximumNumberOfLines: 0, + enableLooping: true + ), + availableSize: context.availableSize, + transition: .immediate + ) + context.add(resultsFooter + .position(CGPoint(x: textSideInset + resultsFooter.size.width * 0.5, y: contentSize.height + resultsFooter.size.height * 0.5)) + ) + contentSize.height += resultsFooter.size.height + contentSize.height += 39.0 + + if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme { + state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme) + } + + let configuration = EmojiGameStakeConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) + var amountLabel = "" + if let tonUsdRate = configuration.tonUsdRate, let value = state.amount?.value, value > 0 { + amountLabel = "~\(formatTonUsdValue(value, divide: true, rate: tonUsdRate, dateTimeFormat: environment.dateTimeFormat))" + } + + let amountItems: [AnyComponentWithIdentity] = [ + AnyComponentWithIdentity( + id: "amount", + component: AnyComponent( + AmountFieldComponent( + textColor: theme.list.itemPrimaryTextColor, + secondaryColor: theme.list.itemSecondaryTextColor, + placeholderColor: theme.list.itemPlaceholderTextColor, + accentColor: theme.list.itemAccentColor, + value: state.amount?.value, + minValue: 0, + forceMinValue: false, + allowZero: true, + maxValue: nil, + placeholderText: strings.EmojiStake_StakePlaceholder, + labelText: amountLabel, + currency: .ton, + dateTimeFormat: presentationData.dateTimeFormat, + amountUpdated: { [weak state] amount in + state?.amount = amount.flatMap { StarsAmount(value: $0, nanos: 0) } + state?.updated() + }, + tag: amountTag + ) + ) + ), + AnyComponentWithIdentity(id: "presets", component: AnyComponent( + AmountPresetsListItemComponent( + context: component.context, + theme: theme, + values: configuration.suggestedAmounts, + valueSelected: { [weak state] value in + guard let state else { + return + } + state.amount = StarsAmount(value: value, nanos: 0) + if let controller = controller() as? EmojiGameStakeScreen { + controller.dismissInput() + state.updated() + controller.resetValue() + } + } + ) + )) + ] + + let amountSection = amountSection.update( + component: ListSectionComponent( + theme: theme, + style: .glass, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.EmojiStake_StakeTitle.uppercased(), + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + footer: nil, + items: amountItems + ), + environment: {}, + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude), + transition: .immediate + ) + context.add(amountSection + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + amountSection.size.height / 2.0)) + .clipsToBounds(true) + .cornerRadius(10.0) + ) + contentSize.height += amountSection.size.height + contentSize.height += 24.0 + + + var buttonItems: [AnyComponentWithIdentity] = [] + buttonItems.append(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Premium/Dice", tintColor: theme.list.itemCheckColors.foregroundColor)))) + buttonItems.append(AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: environment.strings.EmojiStake_Roll, font: Font.semibold(17.0), color: theme.list.itemCheckColors.foregroundColor)))) + + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) + let button = button.update( + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(HStack(buttonItems, spacing: 7.0)) + ), + action: { [weak state] in + if let state, let amount = state.amount, let controller = controller() as? EmojiGameStakeScreen { + controller.complete(amount: amount) + } + } + ), + availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), + transition: .immediate + ) + context.add(button + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) + ) + contentSize.height += button.size.height + + if environment.inputHeight > 0.0 { + contentSize.height += 15.0 + contentSize.height += max(environment.inputHeight, environment.safeInsets.bottom) + } else { + contentSize.height += buttonInsets.bottom + } + + return contentSize + } + + return body + } + + final class State: ComponentState { + fileprivate let context: AccountContext + + fileprivate var component: SheetContent + + fileprivate var forceUpdateAmount = false + fileprivate var amount: StarsAmount? + fileprivate var currency: CurrencyAmount.Currency = .ton + + var cachedChevronImage: (UIImage, PresentationTheme)? + + var emojiFiles: [TelegramMediaFile]? + var emojiFilesDisposable: Disposable? + + init(component: SheetContent) { + self.context = component.context + self.component = component + + let amount: StarsAmount? = StarsAmount(value: component.gameInfo.previousStake, nanos: 0) + let currency: CurrencyAmount.Currency = .ton + + self.currency = currency + self.amount = amount + + super.init() + + self.emojiFilesDisposable = (self.context.engine.stickers.loadedStickerPack(reference: .dice("๐ŸŽฒ"), forceActualized: false) + |> mapToSignal { stickerPack -> Signal<[TelegramMediaFile], NoError> in + switch stickerPack { + case let .result(_, items, _): + var emojiStickers: [TelegramMediaFile] = [] + for item in items { + emojiStickers.append(item.file._parse()) + } + return .single(emojiStickers) + default: + return .complete() + } + } + |> deliverOnMainQueue).start(next: { [weak self] files in + guard let self else { + return + } + self.emojiFiles = files + self.updated() + }) + } + + deinit { + self.emojiFilesDisposable?.dispose() + } + } + + func makeState() -> State { + return State(component: self) + } +} + +private final class EmojiGameStakeSheetComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + private let context: AccountContext + private let gameInfo: EmojiGameInfo.Info + + init( + context: AccountContext, + gameInfo: EmojiGameInfo.Info + ) { + self.context = context + self.gameInfo = gameInfo + } + + static func ==(lhs: EmojiGameStakeSheetComponent, rhs: EmojiGameStakeSheetComponent) -> Bool { + return true + } + + static var body: Body { + let sheet = Child(ResizableSheetComponent<(EnvironmentType)>.self) + let animateOut = StoredActionSlot(Action.self) + + return { context in + let environment = context.environment[EnvironmentType.self] + + let controller = environment.controller + + let dismiss: (Bool) -> Void = { animated in + if animated { + animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + }) + } else { + if let controller = controller() { + controller.dismiss(completion: nil) + } + } + } + + let theme = environment.theme.withModalBlocksBackground() + + var buttonItems: [AnyComponentWithIdentity] = [] + buttonItems.append(AnyComponentWithIdentity(id: "icon", component: AnyComponent(Image(image: PresentationResourcesItemList.itemListRoundTopupIcon(environment.theme), tintColor: theme.list.itemCheckColors.foregroundColor, size: CGSize(width: 16.0, height: 18.0))))) + buttonItems.append(AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: environment.strings.EmojiStake_Roll, font: Font.semibold(17.0), color: theme.list.itemCheckColors.foregroundColor)))) + + let sheet = sheet.update( + component: ResizableSheetComponent( + content: AnyComponent(SheetContent( + context: context.component.context, + gameInfo: context.component.gameInfo, + controller: { + return controller() + }, + dismiss: { + dismiss(true) + } + )), + titleItem: AnyComponent( + Text(text: environment.strings.EmojiStake_Title, font: Font.bold(17.0), color: theme.list.itemPrimaryTextColor) + ), + leftItem: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + dismiss(true) + } + ) + ), + bottomItem: AnyComponent( + ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(HStack(buttonItems, spacing: 7.0)) + ), + isEnabled: true, + displaysProgress: false, + action: { + dismiss(true) + } + ) + ), + backgroundColor: .color(theme.list.blocksBackgroundColor), + animateOut: animateOut + ), + environment: { + environment + ResizableSheetComponentEnvironment( + theme: theme, + statusBarHeight: environment.statusBarHeight, + safeInsets: environment.safeInsets, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.value.isVisible, + isCentered: environment.metrics.widthClass == .regular, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { animated in + dismiss(animated) + } + ) + }, + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(sheet + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + return context.availableSize + } + } +} + +public final class EmojiGameStakeScreen: ViewControllerComponentContainer { + private let context: AccountContext + fileprivate let completion: (StarsAmount) -> Void + + fileprivate let balanceOverlay = ComponentView() + private var showBalance = true + + public init( + context: AccountContext, + gameInfo: EmojiGameInfo.Info, + completion: @escaping (StarsAmount) -> Void + ) { + self.context = context + self.completion = completion + + super.init( + context: context, + component: EmojiGameStakeSheetComponent( + context: context, + gameInfo: gameInfo + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: .default + ) + + self.navigationPresentation = .flatModal + + self.context.tonContext?.load(force: true) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + fileprivate func dismissInput() { + if let view = self.node.hostView.findTaggedView(tag: amountTag) as? AmountFieldComponent.View { + view.deactivateInput() + } + } + + fileprivate func resetValue() { + if let view = self.node.hostView.findTaggedView(tag: amountTag) as? AmountFieldComponent.View { + view.resetValue() + } + } + + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() + } + } + + func complete(amount: StarsAmount) { + if let tonState = self.context.tonContext?.currentState, tonState.balance < amount { + let needed = amount - tonState.balance + var fragmentUrl = "https://fragment.com/ads/topup" + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String { + fragmentUrl = value + } + self.push(BalanceNeededScreen( + context: self.context, + amount: needed, + buttonAction: { [weak self] in + self?.context.sharedContext.applicationBindings.openUrl(fragmentUrl) + } + )) + } else { + self.completion(amount) + self.dismissAnimated() + } + } + + func dismissBalanceOverlay() { + if let view = self.balanceOverlay.view, view.superview != nil { + view.alpha = 0.0 + view.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4) + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { _ in + view.removeFromSuperview() + view.alpha = 1.0 + }) + } + } + + public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + if self.showBalance { + let context = self.context + let insets = layout.insets(options: .statusBar) + let balanceSize = self.balanceOverlay.update( + transition: .immediate, + component: AnyComponent( + StarsBalanceOverlayComponent( + context: context, + peerId: context.account.peerId, + theme: context.sharedContext.currentPresentationData.with { $0 }.theme, + currency: .ton, + action: { [weak self] in + guard let self else { + return + } + var fragmentUrl = "https://fragment.com/ads/topup" + if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String { + fragmentUrl = value + } + context.sharedContext.applicationBindings.openUrl(fragmentUrl) + + self.dismissAnimated() + } + ) + ), + environment: {}, + containerSize: layout.size + ) + if let view = self.balanceOverlay.view { + if view.superview == nil { + self.view.addSubview(view) + + view.layer.animatePosition(from: CGPoint(x: 0.0, y: -64.0), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + view.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil) + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - balanceSize.width) / 2.0), y: insets.top + 5.0), size: balanceSize) + } + } else if let view = self.balanceOverlay.view, view.superview != nil { + view.alpha = 0.0 + view.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4) + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { _ in + view.removeFromSuperview() + view.alpha = 1.0 + }) + } + } +} + +private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { + private let currency: CurrencyAmount.Currency + private let dateTimeFormat: PresentationDateTimeFormat + + private let textField: UITextField + private let minValue: Int64 + private let forceMinValue: Bool + private let allowZero: Bool + private let maxValue: Int64 + private let updated: (Int64) -> Void + private let isEmptyUpdated: (Bool) -> Void + private let animateError: () -> Void + private let focusUpdated: (Bool) -> Void + + init?(textField: UITextField, currency: CurrencyAmount.Currency, dateTimeFormat: PresentationDateTimeFormat, minValue: Int64, forceMinValue: Bool, allowZero: Bool, maxValue: Int64, updated: @escaping (Int64) -> Void, isEmptyUpdated: @escaping (Bool) -> Void, animateError: @escaping () -> Void, focusUpdated: @escaping (Bool) -> Void) { + self.textField = textField + self.currency = currency + self.dateTimeFormat = dateTimeFormat + self.minValue = minValue + self.forceMinValue = forceMinValue + self.allowZero = allowZero + self.maxValue = maxValue + self.updated = updated + self.isEmptyUpdated = isEmptyUpdated + self.animateError = animateError + self.focusUpdated = focusUpdated + + super.init() + } + + func amountFrom(text: String) -> Int64 { + var amount: Int64? + if !text.isEmpty { + switch self.currency { + case .stars: + if let value = Int64(text) { + amount = value + } + case .ton: + let scale: Int64 = 1_000_000_000 // 10โน (one โ€œnanoโ€) + if let decimalSeparator = self.dateTimeFormat.decimalSeparator.first, let dot = text.firstIndex(of: decimalSeparator) { + // Slices for the parts on each side of the dot + var wholeSlice = String(text[.. 9 { + fractionStr = String(fractionStr.prefix(9)) // trim extra digits + } else { + fractionStr = fractionStr.padding( + toLength: 9, withPad: "0", startingAt: 0) // pad with zeros + } + + // Convert and combine + if let whole = Int64(wholeSlice), + let frac = Int64(fractionStr) { + + let whole = min(whole, Int64.max / scale) + + amount = whole * scale + frac + } + } else if let whole = Int64(text) { // string had no dot at all + let whole = min(whole, Int64.max / scale) + + amount = whole * scale + } + } + } + return amount ?? 0 + } + + func onTextChanged(text: String) { + self.updated(self.amountFrom(text: text)) + self.isEmptyUpdated(text.isEmpty) + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + var acceptZero = false + if case .ton = self.currency, self.minValue < 1_000_000_000 { + acceptZero = true + } + + var newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string) + if newText.contains(where: { c in + switch c { + case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9": + return false + default: + if case .ton = self.currency { + if let decimalSeparator = self.dateTimeFormat.decimalSeparator.first, c == decimalSeparator { + return false + } + } + return true + } + }) { + return false + } + if let decimalSeparator = self.dateTimeFormat.decimalSeparator.first, newText.count(where: { $0 == decimalSeparator }) > 1 { + return false + } + + switch self.currency { + case .stars: + if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0")) { + newText.removeFirst() + textField.text = newText + self.onTextChanged(text: newText) + return false + } + case .ton: + var fixedText = false + if let decimalSeparator = self.dateTimeFormat.decimalSeparator.first, let index = newText.firstIndex(of: decimalSeparator) { + let fractionalString = newText[newText.index(after: index)...] + if fractionalString.count > 2 { + newText = String(newText[newText.startIndex ..< newText.index(index, offsetBy: 3)]) + fixedText = true + } + } + + if newText == self.dateTimeFormat.decimalSeparator { + if !acceptZero { + newText.removeFirst() + } else { + newText = "0\(newText)" + } + fixedText = true + } + + if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0") && !newText.hasPrefix("0\(self.dateTimeFormat.decimalSeparator)")) { + newText.removeFirst() + fixedText = true + } + + if fixedText { + textField.text = newText + self.onTextChanged(text: newText) + return false + } + } + + let amount: Int64 = self.amountFrom(text: newText) + if self.forceMinValue && amount < self.minValue { + switch self.currency { + case .stars: + textField.text = "\(self.minValue)" + case .ton: + textField.text = "\(formatTonAmountText(self.minValue, dateTimeFormat: PresentationDateTimeFormat(timeFormat: self.dateTimeFormat.timeFormat, dateFormat: self.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: self.dateTimeFormat.decimalSeparator, groupingSeparator: ""), maxDecimalPositions: nil))" + } + self.onTextChanged(text: self.textField.text ?? "") + self.animateError() + return false + } else if amount > self.maxValue { + switch self.currency { + case .stars: + textField.text = "\(self.maxValue)" + case .ton: + textField.text = "\(formatTonAmountText(self.maxValue, dateTimeFormat: PresentationDateTimeFormat(timeFormat: self.dateTimeFormat.timeFormat, dateFormat: self.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: self.dateTimeFormat.decimalSeparator, groupingSeparator: ""), maxDecimalPositions: nil))" + } + self.onTextChanged(text: self.textField.text ?? "") + self.animateError() + return false + } + + self.onTextChanged(text: newText) + + return true + } +} + +public final class AmountFieldComponent: Component { + public typealias EnvironmentType = Empty + + let textColor: UIColor + let secondaryColor: UIColor + let placeholderColor: UIColor + let accentColor: UIColor + let value: Int64? + let minValue: Int64? + let forceMinValue: Bool + let allowZero: Bool + let maxValue: Int64? + let placeholderText: String + let textFieldOffset: CGPoint + let labelText: String? + let currency: CurrencyAmount.Currency + let dateTimeFormat: PresentationDateTimeFormat + let amountUpdated: (Int64?) -> Void + let tag: AnyObject? + + public init( + textColor: UIColor, + secondaryColor: UIColor, + placeholderColor: UIColor, + accentColor: UIColor, + value: Int64?, + minValue: Int64?, + forceMinValue: Bool, + allowZero: Bool, + maxValue: Int64?, + placeholderText: String, + textFieldOffset: CGPoint = .zero, + labelText: String?, + currency: CurrencyAmount.Currency, + dateTimeFormat: PresentationDateTimeFormat, + amountUpdated: @escaping (Int64?) -> Void, + tag: AnyObject? = nil + ) { + self.textColor = textColor + self.secondaryColor = secondaryColor + self.placeholderColor = placeholderColor + self.accentColor = accentColor + self.value = value + self.minValue = minValue + self.forceMinValue = forceMinValue + self.allowZero = allowZero + self.maxValue = maxValue + self.placeholderText = placeholderText + self.textFieldOffset = textFieldOffset + self.labelText = labelText + self.currency = currency + self.dateTimeFormat = dateTimeFormat + self.amountUpdated = amountUpdated + self.tag = tag + } + + public static func ==(lhs: AmountFieldComponent, rhs: AmountFieldComponent) -> Bool { + if lhs.textColor != rhs.textColor { + return false + } + if lhs.secondaryColor != rhs.secondaryColor { + return false + } + if lhs.placeholderColor != rhs.placeholderColor { + return false + } + if lhs.accentColor != rhs.accentColor { + return false + } + if lhs.value != rhs.value { + return false + } + if lhs.minValue != rhs.minValue { + return false + } + if lhs.allowZero != rhs.allowZero { + return false + } + if lhs.maxValue != rhs.maxValue { + return false + } + if lhs.placeholderText != rhs.placeholderText { + return false + } + if lhs.labelText != rhs.labelText { + return false + } + if lhs.currency != rhs.currency { + return false + } + return true + } + + public final class View: UIView, ListSectionComponent.ChildView, UITextFieldDelegate, ComponentTaggedView { + public func matches(tag: Any) -> Bool { + if let component = self.component, let componentTag = component.tag { + let tag = tag as AnyObject + if componentTag === tag { + return true + } + } + return false + } + + private let placeholderView: ComponentView + private let icon = ComponentView() + private let textField: TextFieldNodeView + private var starsFormatter: AmountFieldStarsFormatter? + private var tonFormatter: AmountFieldStarsFormatter? + private let labelView: ComponentView + + private var component: AmountFieldComponent? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + + private var didSetValueOnce = false + + public var customUpdateIsHighlighted: ((Bool) -> Void)? + public var enumerateSiblings: (((UIView) -> Void) -> Void)? + public let separatorInset: CGFloat = 16.0 + + public override init(frame: CGRect) { + self.placeholderView = ComponentView() + self.textField = TextFieldNodeView(frame: .zero) + self.labelView = ComponentView() + + super.init(frame: frame) + + self.addSubview(self.textField) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func activateInput() { + self.textField.becomeFirstResponder() + } + + public func deactivateInput() { + self.textField.resignFirstResponder() + } + + public func selectAll() { + self.textField.selectAll(nil) + } + + public func animateError() { + self.textField.layer.addShakeAnimation() + let hapticFeedback = HapticFeedback() + hapticFeedback.error() + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: { + let _ = hapticFeedback + }) + } + + public func resetValue() { + guard let component = self.component, let value = component.value else { + return + } + var text = "" + switch component.currency { + case .stars: + text = "\(value)" + case .ton: + text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), maxDecimalPositions: nil))" + } + self.textField.text = text + self.placeholderView.view?.isHidden = !(self.textField.text ?? "").isEmpty + } + + func update(component: AmountFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + self.textField.textColor = component.textColor + if self.component?.currency != component.currency || ((self.textField.text ?? "").isEmpty && !self.didSetValueOnce) { + if let value = component.value, value != .zero { + var text = "" + switch component.currency { + case .stars: + text = "\(value)" + case .ton: + text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), maxDecimalPositions: nil))" + } + self.textField.text = text + self.placeholderView.view?.isHidden = !text.isEmpty + } else { + self.textField.text = "" + } + self.didSetValueOnce = true + } + self.textField.font = Font.regular(17.0) + + self.textField.returnKeyType = .done + self.textField.autocorrectionType = .no + self.textField.autocapitalizationType = .none + + if self.component?.currency != component.currency { + switch component.currency { + case .stars: + self.textField.delegate = self + self.textField.keyboardType = .numberPad + if self.starsFormatter == nil { + self.starsFormatter = AmountFieldStarsFormatter( + textField: self.textField, + currency: component.currency, + dateTimeFormat: component.dateTimeFormat, + minValue: component.minValue ?? 0, + forceMinValue: component.forceMinValue, + allowZero: component.allowZero, + maxValue: component.maxValue ?? Int64.max, + updated: { [weak self] value in + guard let self, let component = self.component else { + return + } + if !self.isUpdating { + component.amountUpdated(value == 0 ? nil : value) + } + }, + isEmptyUpdated: { [weak self] isEmpty in + guard let self else { + return + } + self.placeholderView.view?.isHidden = !isEmpty + }, + animateError: { [weak self] in + guard let self else { + return + } + self.animateError() + }, + focusUpdated: { _ in + } + ) + } + self.tonFormatter = nil + self.textField.delegate = self.starsFormatter + case .ton: + self.textField.keyboardType = .decimalPad + if self.tonFormatter == nil { + self.tonFormatter = AmountFieldStarsFormatter( + textField: self.textField, + currency: component.currency, + dateTimeFormat: component.dateTimeFormat, + minValue: component.minValue ?? 0, + forceMinValue: component.forceMinValue, + allowZero: component.allowZero, + maxValue: component.maxValue ?? Int64.max, + updated: { [weak self] value in + guard let self, let component = self.component else { + return + } + if !self.isUpdating { + component.amountUpdated(value == 0 ? nil : value) + } + }, + isEmptyUpdated: { [weak self] isEmpty in + guard let self else { + return + } + self.placeholderView.view?.isHidden = !isEmpty + }, + animateError: { [weak self] in + guard let self else { + return + } + self.animateError() + }, + focusUpdated: { _ in + } + ) + } + self.starsFormatter = nil + self.textField.delegate = self.tonFormatter + } + self.textField.reloadInputViews() + } + + self.component = component + self.state = state + + let size = CGSize(width: availableSize.width, height: 52.0) + + let sideInset: CGFloat = 16.0 + var leftInset: CGFloat = 16.0 + + let iconName: String + var iconTintColor: UIColor? + let iconMaxSize: CGSize? + var iconOffset = CGPoint() + switch component.currency { + case .stars: + iconName = "Premium/Stars/StarLarge" + iconMaxSize = CGSize(width: 22.0, height: 22.0) + case .ton: + iconName = "Ads/TonBig" + iconTintColor = component.accentColor + iconMaxSize = CGSize(width: 18.0, height: 18.0) + iconOffset = CGPoint(x: 3.0, y: 1.0) + } + let iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent( + name: iconName, + tintColor: iconTintColor, + maxSize: iconMaxSize + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + + if let iconView = self.icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + iconView.frame = CGRect(origin: CGPoint(x: iconOffset.x + 15.0, y: iconOffset.y - 1.0 + floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize) + } + + leftInset += 24.0 + 6.0 + + let placeholderSize = self.placeholderView.update( + transition: .easeInOut(duration: 0.2), + component: AnyComponent( + Text( + text: component.placeholderText, + font: Font.regular(17.0), + color: component.placeholderColor + ) + ), + environment: {}, + containerSize: availableSize + ) + + if let placeholderComponentView = self.placeholderView.view { + if placeholderComponentView.superview == nil { + self.insertSubview(placeholderComponentView, at: 0) + } + + placeholderComponentView.frame = CGRect(origin: CGPoint(x: leftInset, y: -1.0 + floorToScreenPixels((size.height - placeholderSize.height) / 2.0) + 1.0 - UIScreenPixel), size: placeholderSize) + placeholderComponentView.isHidden = !(self.textField.text ?? "").isEmpty + } + + if let labelText = component.labelText { + let labelSize = self.labelView.update( + transition: .immediate, + component: AnyComponent( + Text( + text: labelText, + font: Font.regular(17.0), + color: component.secondaryColor + ) + ), + environment: {}, + containerSize: availableSize + ) + + if let labelView = self.labelView.view { + if labelView.superview == nil { + self.insertSubview(labelView, at: 0) + } + + labelView.frame = CGRect(origin: CGPoint(x: size.width - sideInset - labelSize.width, y: floorToScreenPixels((size.height - labelSize.height) / 2.0) + 1.0 - UIScreenPixel), size: labelSize) + } + } else if let labelView = self.labelView.view, labelView.superview != nil { + labelView.removeFromSuperview() + } + + self.textField.frame = CGRect(x: leftInset + component.textFieldOffset.x, y: 4.0 + component.textFieldOffset.y, width: size.width - 30.0, height: 44.0) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ResultCellComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let files: [TelegramMediaFile]? + let value: Int32 + let size: CGSize + + init( + context: AccountContext, + theme: PresentationTheme, + files: [TelegramMediaFile]?, + value: Int32, + size: CGSize + ) { + self.context = context + self.theme = theme + self.files = files + self.value = value + self.size = size + } + + static func ==(lhs: ResultCellComponent, rhs: ResultCellComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.files != rhs.files { + return false + } + if lhs.value != rhs.value { + return false + } + if lhs.size != rhs.size { + return false + } + return true + } + + final class View: UIView { + private var component: ResultCellComponent? + + private let background = ComponentView() + private let emoji = ComponentView() + private let title = ComponentView() + + override init(frame: CGRect) { + + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ResultCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let size = component.size + + let backgroundSize = self.background.update( + transition: transition, + component: AnyComponent(FilledRoundedRectangleComponent(color: component.theme.list.itemBlocksBackgroundColor, cornerRadius: .value(22.0), smoothCorners: true)), + environment: {}, + containerSize: size + ) + let backgroundFrame = CGRect(origin: .zero, size: backgroundSize) + if let backgroundView = self.background.view { + if backgroundView.superview == nil { + self.addSubview(backgroundView) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + + let value = Double(component.value) / 1000.0 + let titleString = String(format: "%0.1f", value).replacingOccurrences(of: ".0", with: "").replacingOccurrences(of: ",0", with: "") + + var items: [AnyComponentWithIdentity] = [] + if let files = component.files { + for file in files { + items.append(AnyComponentWithIdentity(id: items.count, component: AnyComponent( + LottieComponent( + content: LottieComponent.ResourceContent( + context: component.context, + file: file, + attemptSynchronously: true, + providesPlaceholder: true + ), + placeholderColor: component.theme.list.mediaPlaceholderColor, + startingPosition: .end, + size: CGSize(width: 50.0, height: 50.0), + loop: false + ) + ))) + } + } + let emojiSize = self.emoji.update( + transition: transition, + component: AnyComponent( + HStack(items, spacing: -18.0) + ), + environment: {}, + containerSize: availableSize + ) + let emojiFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - emojiSize.width) / 2.0), y: -9.0), size: emojiSize) + if let emojiView = self.emoji.view { + if emojiView.superview == nil { + self.addSubview(emojiView) + } + transition.setFrame(view: emojiView, frame: emojiFrame) + } + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: "ร—\(titleString)", font: Font.semibold(11.0), textColor: component.theme.list.itemPrimaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ) + ), + environment: {}, + containerSize: availableSize + ) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 10.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class AmountPresetsListItemComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let values: [Int64] + let valueSelected: (Int64) -> Void + + init( + context: AccountContext, + theme: PresentationTheme, + values: [Int64], + valueSelected: @escaping (Int64) -> Void + ) { + self.context = context + self.theme = theme + self.values = values + self.valueSelected = valueSelected + } + + static func ==(lhs: AmountPresetsListItemComponent, rhs: AmountPresetsListItemComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.values != rhs.values { + return false + } + return true + } + + final class View: UIView { + private var component: AmountPresetsListItemComponent? + + private var itemViews: [Int64: ComponentView] = [:] + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: AmountPresetsListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + + let sideInset: CGFloat = 16.0 + let spacing: CGFloat = 6.0 + let itemSize = CGSize(width: floorToScreenPixels((availableSize.width - sideInset * 2.0 - spacing * 2.0) / 3.0), height: 28.0) + + var itemOrigin = CGPoint(x: sideInset, y: sideInset) + for value in component.values { + let itemView: ComponentView + if let current = self.itemViews[value] { + itemView = current + } else { + itemView = ComponentView() + self.itemViews[value] = itemView + } + let _ = itemView.update( + transition: .immediate, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(AmountPresetComponent( + context: component.context, + theme: component.theme, + value: value + )), + action: { + component.valueSelected(value) + }, + animateScale: false + )), + environment: {}, + containerSize: itemSize + ) + var itemFrame = CGRect(origin: itemOrigin, size: itemSize) + if itemFrame.maxX > availableSize.width { + itemOrigin = CGPoint(x: sideInset, y: itemOrigin.y + itemSize.height + spacing) + itemFrame.origin = itemOrigin + } + if let itemView = itemView.view { + if itemView.superview == nil { + self.addSubview(itemView) + } + transition.setFrame(view: itemView, frame: itemFrame) + } + itemOrigin.x += itemSize.width + spacing + } + + let size = CGSize(width: availableSize.width, height: itemOrigin.y + itemSize.height + sideInset) + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class AmountPresetComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let value: Int64 + + init( + context: AccountContext, + theme: PresentationTheme, + value: Int64 + ) { + self.context = context + self.theme = theme + self.value = value + } + + static func ==(lhs: AmountPresetComponent, rhs: AmountPresetComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.value != rhs.value { + return false + } + return true + } + + final class View: UIView { + private var component: AmountPresetComponent? + + private let background = ComponentView() + private let title = ComponentView() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: AmountPresetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let size = availableSize + + let backgroundSize = self.background.update( + transition: transition, + component: AnyComponent(FilledRoundedRectangleComponent(color: component.theme.list.itemAccentColor.withMultipliedAlpha(0.1), cornerRadius: .minEdge, smoothCorners: false)), + environment: {}, + containerSize: size + ) + let backgroundFrame = CGRect(origin: .zero, size: backgroundSize) + if let backgroundView = self.background.view { + if backgroundView.superview == nil { + self.addSubview(backgroundView) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + + let attributedText = NSMutableAttributedString(string: "$ \(formatTonAmountText(component.value, dateTimeFormat: presentationData.dateTimeFormat))", font: Font.semibold(14.0), textColor: component.theme.list.itemAccentColor) + if let range = attributedText.string.range(of: "$") { + attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: true)), range: NSRange(range, in: attributedText.string)) + attributedText.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedText.string)) + } + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + MultilineTextWithEntitiesComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + placeholderColor: .clear, + text: .plain(attributedText) + ) + ), + environment: {}, + containerSize: availableSize + ) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private struct EmojiGameStakeConfiguration { + static var defaultValue: EmojiGameStakeConfiguration { + return EmojiGameStakeConfiguration( + tonUsdRate: nil, + minStakeAmount: nil, + maxStakeAmount: nil, + suggestedAmounts: [ + 100000000, + 1000000000, + 2000000000, + 5000000000, + 10000000000, + 20000000000 + ] + ) + } + + let tonUsdRate: Double? + let minStakeAmount: Int64? + let maxStakeAmount: Int64? + let suggestedAmounts: [Int64] + + fileprivate init( + tonUsdRate: Double?, + minStakeAmount: Int64?, + maxStakeAmount: Int64?, + suggestedAmounts: [Int64] + ) { + self.tonUsdRate = tonUsdRate + self.minStakeAmount = minStakeAmount + self.maxStakeAmount = maxStakeAmount + self.suggestedAmounts = suggestedAmounts + } + + static func with(appConfiguration: AppConfiguration) -> EmojiGameStakeConfiguration { + if let data = appConfiguration.data { + var tonUsdRate: Double? + if let value = data["ton_usd_rate"] as? Double { + tonUsdRate = value + } + + var minStakeAmount: Int64? + if let value = data["ton_stakedice_stake_amount_min"] as? Double { + minStakeAmount = Int64(value) + } + + var maxStakeAmount: Int64? + if let value = data["ton_stakedice_stake_amount_max"] as? Double { + maxStakeAmount = Int64(value) + } + + var suggestedAmounts: [Int64] = [] + if let value = data["ton_stakedice_stake_suggested_amounts"] as? [Double] { + suggestedAmounts = value.map { Int64($0) } + } else { + suggestedAmounts = EmojiGameStakeConfiguration.defaultValue.suggestedAmounts + } + + return EmojiGameStakeConfiguration( + tonUsdRate: tonUsdRate, + minStakeAmount: minStakeAmount, + maxStakeAmount: maxStakeAmount, + suggestedAmounts: suggestedAmounts + ) + } else { + return .defaultValue + } + } +} + + + + + + + + + + +public final class ResizableSheetComponentEnvironment: Equatable { + public let theme: PresentationTheme + public let statusBarHeight: CGFloat + public let safeInsets: UIEdgeInsets + public let metrics: LayoutMetrics + public let deviceMetrics: DeviceMetrics + public let isDisplaying: Bool + public let isCentered: Bool + public let regularMetricsSize: CGSize? + public let dismiss: (Bool) -> Void + + public init( + theme: PresentationTheme, + statusBarHeight: CGFloat, + safeInsets: UIEdgeInsets, + metrics: LayoutMetrics, + deviceMetrics: DeviceMetrics, + isDisplaying: Bool, + isCentered: Bool, + regularMetricsSize: CGSize?, + dismiss: @escaping (Bool) -> Void + ) { + self.theme = theme + self.statusBarHeight = statusBarHeight + self.safeInsets = safeInsets + self.metrics = metrics + self.deviceMetrics = deviceMetrics + self.isDisplaying = isDisplaying + self.isCentered = isCentered + self.regularMetricsSize = regularMetricsSize + self.dismiss = dismiss + } + + public static func ==(lhs: ResizableSheetComponentEnvironment, rhs: ResizableSheetComponentEnvironment) -> Bool { + if lhs.theme != rhs.theme { + return false + } + if lhs.statusBarHeight != rhs.statusBarHeight { + return false + } + if lhs.safeInsets != rhs.safeInsets { + return false + } + if lhs.metrics != rhs.metrics { + return false + } + if lhs.deviceMetrics != rhs.deviceMetrics { + return false + } + if lhs.isDisplaying != rhs.isDisplaying { + return false + } + if lhs.isCentered != rhs.isCentered { + return false + } + if lhs.regularMetricsSize != rhs.regularMetricsSize { + return false + } + return true + } +} + +public final class ResizableSheetComponent: Component { + public typealias EnvironmentType = (ChildEnvironmentType, ResizableSheetComponentEnvironment) + + public class ExternalState { + public fileprivate(set) var contentHeight: CGFloat + + public init() { + self.contentHeight = 0.0 + } + } + + public enum BackgroundColor: Equatable { + case color(UIColor) + } + + public let content: AnyComponent + public let titleItem: AnyComponent? + public let leftItem: AnyComponent? + public let rightItem: AnyComponent? + public let bottomItem: AnyComponent? + public let backgroundColor: BackgroundColor + public let externalState: ExternalState? + public let animateOut: ActionSlot> + + public init( + content: AnyComponent, + titleItem: AnyComponent? = nil, + leftItem: AnyComponent? = nil, + rightItem: AnyComponent? = nil, + bottomItem: AnyComponent? = nil, + backgroundColor: BackgroundColor, + externalState: ExternalState? = nil, + animateOut: ActionSlot>, + ) { + self.content = content + self.titleItem = titleItem + self.leftItem = leftItem + self.rightItem = rightItem + self.bottomItem = bottomItem + self.backgroundColor = backgroundColor + self.externalState = externalState + self.animateOut = animateOut + } + + public static func ==(lhs: ResizableSheetComponent, rhs: ResizableSheetComponent) -> Bool { + if lhs.content != rhs.content { + return false + } + if lhs.titleItem != rhs.titleItem { + return false + } + if lhs.leftItem != rhs.leftItem { + return false + } + if lhs.rightItem != rhs.rightItem { + return false + } + if lhs.bottomItem != rhs.bottomItem { + return false + } + if lhs.backgroundColor != rhs.backgroundColor { + return false + } + if lhs.animateOut != rhs.animateOut { + return false + } + return true + } + + private struct ItemLayout: Equatable { + var containerSize: CGSize + var containerInset: CGFloat + var containerCornerRadius: CGFloat + var bottomInset: CGFloat + var topInset: CGFloat + + init(containerSize: CGSize, containerInset: CGFloat, containerCornerRadius: CGFloat, bottomInset: CGFloat, topInset: CGFloat) { + self.containerSize = containerSize + self.containerInset = containerInset + self.containerCornerRadius = containerCornerRadius + self.bottomInset = bottomInset + self.topInset = topInset + } + } + + private final class ScrollView: UIScrollView { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + } + + public final class View: UIView, UIScrollViewDelegate, ComponentTaggedView { + public final class Tag { + public init() { + } + } + + public func matches(tag: Any) -> Bool { + if let _ = tag as? Tag { + return true + } + return false + } + + private let dimView: UIView + private let containerView: UIView + private let backgroundLayer: SimpleLayer + private let navigationBarContainer: SparseContainerView + private let scrollView: ScrollView + private let scrollContentClippingView: SparseContainerView + private let scrollContentView: UIView + + private let topEdgeEffectView: EdgeEffectView + private let contentView: ComponentView + + private var titleItemView: ComponentView? + private var leftItemView: ComponentView? + private var rightItemView: ComponentView? + private var bottomItemView: ComponentView? + + private let backgroundHandleView: UIImageView + + private var ignoreScrolling: Bool = false + + private var component: ResizableSheetComponent? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + private var environment: ResizableSheetComponentEnvironment? + private var itemLayout: ItemLayout? + + override init(frame: CGRect) { + self.dimView = UIView() + self.containerView = UIView() + + self.containerView.clipsToBounds = true + self.containerView.layer.cornerRadius = 40.0 + self.containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] + + self.backgroundLayer = SimpleLayer() + self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + self.backgroundLayer.cornerRadius = 40.0 + + self.backgroundHandleView = UIImageView() + + self.navigationBarContainer = SparseContainerView() + + self.scrollView = ScrollView() + + self.scrollContentClippingView = SparseContainerView() + self.scrollContentClippingView.clipsToBounds = true + + self.scrollContentView = UIView() + + self.topEdgeEffectView = EdgeEffectView() + self.topEdgeEffectView.clipsToBounds = true + self.topEdgeEffectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + self.topEdgeEffectView.layer.cornerRadius = 40.0 + + self.contentView = ComponentView() + + super.init(frame: frame) + + self.addSubview(self.dimView) + self.addSubview(self.containerView) + self.containerView.layer.addSublayer(self.backgroundLayer) + + self.scrollView.delaysContentTouches = true + self.scrollView.canCancelContentTouches = true + self.scrollView.clipsToBounds = false + self.scrollView.contentInsetAdjustmentBehavior = .never + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = true + self.scrollView.scrollsToTop = false + self.scrollView.delegate = self + self.scrollView.clipsToBounds = true + + self.containerView.addSubview(self.scrollContentClippingView) + self.scrollContentClippingView.addSubview(self.scrollView) + + self.scrollView.addSubview(self.scrollContentView) + + self.containerView.addSubview(self.navigationBarContainer) + + self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateScrolling(transition: .immediate) + } + } + + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.bounds.contains(point) { + return nil + } + if !self.backgroundLayer.frame.contains(point) { + return self.dimView + } + + if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) { + return result + } + let result = super.hitTest(point, with: event) + return result + } + + @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.dismissAnimated() + } + } + + func dismissAnimated() { + guard let environment = self.environment else { + return + } + self.endEditing(true) + environment.dismiss(true) + } + + private func updateScrolling(transition: ComponentTransition) { + guard let itemLayout = self.itemLayout else { + return + } + var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset + topOffset = max(0.0, topOffset) + transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0)) + + transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset)) + + var topOffsetFraction = self.scrollView.bounds.minY / 100.0 + topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) + + let minScale: CGFloat = (itemLayout.containerSize.width - 6.0 * 2.0) / itemLayout.containerSize.width + let minScaledTranslation: CGFloat = (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0 + let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius + + let scale = minScale * (1.0 - topOffsetFraction) + 1.0 * topOffsetFraction + let scaledTranslation = minScaledTranslation * (1.0 - topOffsetFraction) + let scaledCornerRadius = minScaledCornerRadius * (1.0 - topOffsetFraction) + itemLayout.containerCornerRadius * topOffsetFraction + + var containerTransform = CATransform3DIdentity + containerTransform = CATransform3DTranslate(containerTransform, 0.0, scaledTranslation, 0.0) + containerTransform = CATransform3DScale(containerTransform, scale, scale, scale) + transition.setTransform(view: self.containerView, transform: containerTransform) + transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: scaledCornerRadius) + } + + private var didPlayAppearanceAnimation = false + func animateIn() { + self.didPlayAppearanceAnimation = true + + self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY + self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + + func animateOut(completion: @escaping () -> Void) { + let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY + + self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + } + + func update(component: ResizableSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + let sheetEnvironment = environment[ResizableSheetComponentEnvironment.self].value + component.animateOut.connect { [weak self] completion in + guard let self else { + return + } + self.animateOut { + completion(Void()) + } + } + + let themeUpdated = self.environment?.theme !== sheetEnvironment.theme + + let resetScrolling = self.scrollView.bounds.width != availableSize.width + + let fillingSize: CGFloat + if case .regular = sheetEnvironment.metrics.widthClass { + fillingSize = min(availableSize.width, 414.0) - sheetEnvironment.safeInsets.left * 2.0 + } else { + fillingSize = min(availableSize.width, sheetEnvironment.deviceMetrics.screenSize.width) - sheetEnvironment.safeInsets.left * 2.0 + } + let rawSideInset: CGFloat = floor((availableSize.width - fillingSize) * 0.5) + + self.component = component + self.state = state + self.environment = sheetEnvironment + + let theme = sheetEnvironment.theme.withModalBlocksBackground() + if themeUpdated { + self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + self.backgroundLayer.backgroundColor = theme.list.blocksBackgroundColor.cgColor + } + + transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize)) + + var containerSize: CGSize + if !"".isEmpty, sheetEnvironment.isCentered { + let verticalInset: CGFloat = 44.0 + let maxSide = max(availableSize.width, availableSize.height) + let minSide = min(availableSize.width, availableSize.height) + containerSize = CGSize(width: min(availableSize.width - 20.0, floor(maxSide / 2.0)), height: min(availableSize.height, minSide) - verticalInset * 2.0) + if let regularMetricsSize = sheetEnvironment.regularMetricsSize { + containerSize = regularMetricsSize + } + } else { + containerSize = CGSize(width: fillingSize, height: .greatestFiniteMagnitude) + } + + let containerInset: CGFloat = sheetEnvironment.statusBarHeight + 10.0 + let clippingY: CGFloat + + self.contentView.parentState = state + let contentViewSize = self.contentView.update( + transition: transition, + component: component.content, + environment: { + environment[ChildEnvironmentType.self] + }, + containerSize: containerSize + ) + component.externalState?.contentHeight = contentViewSize.height + + if let contentView = self.contentView.view { + if contentView.superview == nil { + self.scrollContentView.addSubview(contentView) + } + transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: contentViewSize)) + } + + let contentHeight = contentViewSize.height + let initialContentHeight = contentHeight + + let edgeEffectHeight: CGFloat = 80.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: fillingSize, height: edgeEffectHeight)) + transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame) + self.topEdgeEffectView.update(content: theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + if self.topEdgeEffectView.superview == nil { + self.navigationBarContainer.insertSubview(self.topEdgeEffectView, at: 0) + } + + if let titleItem = component.titleItem { + let titleItemView: ComponentView + if let current = self.titleItemView { + titleItemView = current + } else { + titleItemView = ComponentView() + self.titleItemView = titleItemView + } + + let titleItemSize = titleItemView.update( + transition: transition, + component: titleItem, + environment: {}, + containerSize: CGSize(width: containerSize.width - 66.0 * 2.0, height: 66.0) + ) + let titleItemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((containerSize.width - titleItemSize.width)) / 2.0, y: floorToScreenPixels(36.0 - titleItemSize.height * 0.5)), size: titleItemSize) + if let view = titleItemView.view { + if view.superview == nil { + self.navigationBarContainer.addSubview(view) + } + transition.setFrame(view: view, frame: titleItemFrame) + } + } else if let titleItemView = self.titleItemView { + self.titleItemView = nil + titleItemView.view?.removeFromSuperview() + } + + + if let leftItem = component.leftItem { + let leftItemView: ComponentView + if let current = self.leftItemView { + leftItemView = current + } else { + leftItemView = ComponentView() + self.leftItemView = leftItemView + } + + let leftItemSize = leftItemView.update( + transition: transition, + component: leftItem, + environment: {}, + containerSize: CGSize(width: 66.0, height: 66.0) + ) + let leftItemFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: leftItemSize) + if let view = leftItemView.view { + if view.superview == nil { + self.navigationBarContainer.addSubview(view) + } + transition.setFrame(view: view, frame: leftItemFrame) + } + } else if let leftItemView = self.leftItemView { + self.leftItemView = nil + leftItemView.view?.removeFromSuperview() + } + + if let rightItem = component.rightItem { + let rightItemView: ComponentView + if let current = self.rightItemView { + rightItemView = current + } else { + rightItemView = ComponentView() + self.rightItemView = rightItemView + } + + let rightItemSize = rightItemView.update( + transition: transition, + component: rightItem, + environment: {}, + containerSize: CGSize(width: 66.0, height: 66.0) + ) + let rightItemFrame = CGRect(origin: CGPoint(x: containerSize.width - 16.0 - rightItemSize.width, y: 16.0), size: rightItemSize) + if let view = rightItemView.view { + if view.superview == nil { + self.navigationBarContainer.addSubview(view) + } + transition.setFrame(view: view, frame: rightItemFrame) + } + } else if let rightItemView = self.rightItemView { + self.rightItemView = nil + rightItemView.view?.removeFromSuperview() + } + + + clippingY = availableSize.height + + let topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight) + + let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset) + + self.scrollContentClippingView.layer.cornerRadius = 38.0 + + self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, containerCornerRadius: sheetEnvironment.deviceMetrics.screenCornerRadius, bottomInset: sheetEnvironment.safeInsets.bottom, topInset: topInset) + + transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight))) + + transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) + transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: CGSize(width: fillingSize, height: availableSize.height))) + + let scrollClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: containerInset), size: CGSize(width: availableSize.width, height: clippingY - containerInset)) + transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center) + transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size)) + + self.ignoreScrolling = true + transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))) + let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight) + if contentSize != self.scrollView.contentSize { + self.scrollView.contentSize = contentSize + } + if resetScrolling { + self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize) + } + self.ignoreScrolling = false + self.updateScrolling(transition: transition) + + transition.setPosition(view: self.containerView, position: CGRect(origin: CGPoint(), size: availableSize).center) + transition.setBounds(view: self.containerView, bounds: CGRect(origin: CGPoint(), size: availableSize)) + + if sheetEnvironment.isDisplaying && !self.didPlayAppearanceAnimation { + self.animateIn() + } + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift index 8bcb4abb..344e3fc4 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift @@ -19,6 +19,8 @@ import TelegramUIPreferences import GenerateStickerPlaceholderImage import UIKitRuntimeUtils import ComponentFlow +import RLottieBinding +import GZip public func generateTopicIcon(title: String, backgroundColors: [UIColor], strokeColors: [UIColor], size: CGSize) -> UIImage? { let realSize = size @@ -497,6 +499,10 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { case .verification: self.updateVerification() self.updateTintColor() + case .dice: + if let file { + self.updateDice(file: file, attemptSynchronousLoad: attemptSynchronousLoad) + } } } else if let file = file { self.updateFile(file: file, attemptSynchronousLoad: attemptSynchronousLoad) @@ -720,6 +726,44 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } } + private func updateDice(file: TelegramMediaFile, attemptSynchronousLoad: Bool) { + guard let arguments = self.arguments else { + return + } + let _ = (arguments.context.postbox.mediaBox.resourceData(file.resource) + |> filter { resource in + return resource.complete + } + |> map { resource -> UIImage? in + guard var data = try? Data(contentsOf: URL(fileURLWithPath: resource.path)) else { + return nil + } + if let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024) { + data = unpackedData + } + guard let instance = LottieInstance(data: data, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else { + return nil + } + let size = CGSize(width: 128.0, height: 128.0) + if let diceContext = DrawingContext(size: size, scale: 1.0, opaque: false, clear: true) { + instance.renderFrame(with: instance.frameCount - 1, into: diceContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(diceContext.scaledSize.width), height: Int32(diceContext.scaledSize.height), bytesPerRow: Int32(diceContext.bytesPerRow)) + if let diceImage = diceContext.generateImage() { + let drawingContext = DrawingContext(size: size, scale: 1.0, opaque: false, clear: true) + drawingContext?.withFlippedContext { context in + if let cgImage = diceImage.cgImage { + context.draw(cgImage, in: CGRect(origin: CGPoint(x: -30.0, y: 5.0), size: CGSize(width: 180.0, height: 180.0))) + } + } + return drawingContext?.generateImage() + } + } + return nil + } + |> deliverOnMainQueue).start(next: { image in + self.contents = image?.cgImage + }) + } + private func loadLocalAnimation() { guard let arguments = self.arguments else { return diff --git a/submodules/TelegramUI/Components/EntityKeyboard/BUILD b/submodules/TelegramUI/Components/EntityKeyboard/BUILD index 4aa7f411..0ca5c0ae 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/BUILD +++ b/submodules/TelegramUI/Components/EntityKeyboard/BUILD @@ -53,6 +53,9 @@ swift_library( "//submodules/TelegramUI/Components/BatchVideoRendering", "//submodules/TelegramUI/Components/GifVideoLayer", "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/LiquidLens", + "//submodules/TelegramUI/Components/TabSelectionRecognizer", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift index 5028d618..2762c21f 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift @@ -419,13 +419,13 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { context.setFillColor(color.withMultipliedAlpha(0.2).cgColor) - context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 21.0).cgPath) + context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 30.0).cgPath) context.fillPath() context.setFillColor(color.cgColor) - let plusSize = CGSize(width: 3.5, height: 28.0) - context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height).offsetBy(dx: 0.0, dy: -17.0), cornerRadius: plusSize.width / 2.0).cgPath) - context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width).offsetBy(dx: 0.0, dy: -17.0), cornerRadius: plusSize.width / 2.0).cgPath) + let plusSize = CGSize(width: 4.0, height: 27.0) + context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height).offsetBy(dx: 0.0, dy: -18.0), cornerRadius: plusSize.width / 2.0).cgPath) + context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width).offsetBy(dx: 0.0, dy: -18.0), cornerRadius: plusSize.width / 2.0).cgPath) context.fillPath() context.translateBy(x: size.width / 2.0, y: size.height / 2.0) @@ -437,7 +437,7 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { let components = string.components(separatedBy: "\n") for component in components { context.saveGState() - let attributedString = NSAttributedString(string: component, attributes: [NSAttributedString.Key.font: Font.medium(17.0), NSAttributedString.Key.foregroundColor: color]) + let attributedString = NSAttributedString(string: component, attributes: [NSAttributedString.Key.font: Font.medium(16.0), NSAttributedString.Key.foregroundColor: color]) let line = CTLineCreateWithAttributedString(attributedString) let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 3f94209d..2ab6b15e 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -4026,7 +4026,7 @@ public final class EmojiPagerContentComponent: Component { private func updateTopPanelSeparator(transition: ComponentTransition) { if let topPanelSeparator = self.topPanelSeparator { var offset = self.scrollView.contentOffset.y - let startOffset: CGFloat = 40.0 - self.topPanelHeight + let startOffset: CGFloat = 46.0 - self.topPanelHeight let endOffset: CGFloat = startOffset + 10.0 offset = min(max(offset, startOffset), endOffset) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index aa672304..12ec0cfb 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -403,7 +403,7 @@ public final class EntityKeyboardComponent: Component { if let _ = component.maskContent?.inputInteractionHolder.inputInteraction?.openStickerSettings { contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "masks", component: AnyComponent(EntityKeyboardBottomPanelButton( icon: "Chat/Input/Media/EntityInputSettingsIcon", - color: component.theme.chat.inputPanel.inputControlColor, + theme: component.theme, action: { maskContent.inputInteractionHolder.inputInteraction?.openStickerSettings?() } @@ -417,7 +417,7 @@ public final class EntityKeyboardComponent: Component { if let addImage = component.stickerContent?.inputInteractionHolder.inputInteraction?.addImage { contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(EntityKeyboardBottomPanelButton( icon: "Media Editor/AddImage", - color: component.theme.chat.inputPanel.inputControlColor, + theme: component.theme, action: { addImage() } @@ -537,7 +537,7 @@ public final class EntityKeyboardComponent: Component { if let _ = component.stickerContent?.inputInteractionHolder.inputInteraction?.openStickerSettings { contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardBottomPanelButton( icon: "Chat/Input/Media/EntityInputSettingsIcon", - color: component.theme.chat.inputPanel.inputControlColor, + theme: component.theme, action: { stickerContent.inputInteractionHolder.inputInteraction?.openStickerSettings?() } @@ -546,7 +546,7 @@ public final class EntityKeyboardComponent: Component { if let addImage = component.stickerContent?.inputInteractionHolder.inputInteraction?.addImage { contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardBottomPanelButton( icon: "Media Editor/AddImage", - color: component.theme.chat.inputPanel.inputControlColor, + theme: component.theme, action: { addImage() } @@ -649,7 +649,7 @@ public final class EntityKeyboardComponent: Component { if let _ = deleteBackwards { contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardBottomPanelButton( icon: "Chat/Input/Media/EntityInputGlobeIcon", - color: component.theme.chat.inputPanel.inputControlColor, + theme: component.theme, action: { [weak self] in guard let strongSelf = self, let component = strongSelf.component else { return @@ -660,7 +660,7 @@ public final class EntityKeyboardComponent: Component { } else if let addImage = component.emojiContent?.inputInteractionHolder.inputInteraction?.addImage { contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardBottomPanelButton( icon: "Media Editor/AddImage", - color: component.theme.chat.inputPanel.inputControlColor, + theme: component.theme, action: { addImage() } @@ -671,7 +671,7 @@ public final class EntityKeyboardComponent: Component { if let _ = deleteBackwards { contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardBottomPanelButton( icon: "Chat/Input/Media/EntityInputClearIcon", - color: component.theme.chat.inputPanel.inputControlColor, + theme: component.theme, action: { deleteBackwards?() AudioServicesPlaySystemSound(1155) @@ -721,7 +721,8 @@ public final class EntityKeyboardComponent: Component { topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent( theme: component.theme, overflowHeight: component.hiddenInputHeight, - topInset: component.externalTopPanelContainer == nil ? 6.0 : 0.0, + topInset: component.externalTopPanelContainer == nil ? 8.0 : 0.0, + height: component.externalTopPanelContainer == nil ? 40.0 : 34.0, displayBackground: component.externalTopPanelContainer != nil ? .none : component.displayTopPanelBackground )), externalTopPanelContainer: component.externalTopPanelContainer, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelButton.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelButton.swift index faa4d87d..c73235e7 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelButton.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelButton.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import ComponentFlow +import TelegramPresentationData import PagerComponent import ComponentDisplayAdapters import BundleIconComponent @@ -10,18 +11,18 @@ import AppBundle final class EntityKeyboardBottomPanelButton: Component { let icon: String - let color: UIColor + let theme: PresentationTheme let action: () -> Void let holdAction: (() -> Void)? init( icon: String, - color: UIColor, + theme: PresentationTheme, action: @escaping () -> Void, holdAction: (() -> Void)? = nil ) { self.icon = icon - self.color = color + self.theme = theme self.action = action self.holdAction = holdAction } @@ -30,7 +31,7 @@ final class EntityKeyboardBottomPanelButton: Component { if lhs.icon != rhs.icon { return false } - if lhs.color != rhs.color { + if lhs.theme !== rhs.theme { return false } if (lhs.holdAction == nil) != (rhs.holdAction == nil) { @@ -39,7 +40,9 @@ final class EntityKeyboardBottomPanelButton: Component { return true } - final class View: HighlightTrackingButton { + final class View: UIView { + private let backgroundView: GlassBackgroundView + let buttonView: HighlightTrackingButton let iconView: GlassBackgroundView.ContentImageView let tintMaskContainer: UIView @@ -48,15 +51,19 @@ final class EntityKeyboardBottomPanelButton: Component { var component: EntityKeyboardBottomPanelButton? - private var currentIsHighlighted: Bool = false { - didSet { - if self.currentIsHighlighted != oldValue { - self.updateAlpha(transition: .immediate) - } - } - } +// private var currentIsHighlighted: Bool = false { +// didSet { +// if self.currentIsHighlighted != oldValue { +// self.updateAlpha(transition: .immediate) +// } +// } +// } override init(frame: CGRect) { + self.backgroundView = GlassBackgroundView() + + self.buttonView = HighlightTrackingButton() + self.iconView = GlassBackgroundView.ContentImageView() self.iconView.isUserInteractionEnabled = false @@ -65,9 +72,11 @@ final class EntityKeyboardBottomPanelButton: Component { super.init(frame: frame) - self.addSubview(self.iconView) + self.addSubview(self.backgroundView) + self.backgroundView.contentView.addSubview(self.iconView) + self.backgroundView.contentView.addSubview(self.buttonView) - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.buttonView.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) } required init?(coder: NSCoder) { @@ -86,65 +95,65 @@ final class EntityKeyboardBottomPanelButton: Component { } } - override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { - self.currentIsHighlighted = true - - self.holdActionTriggerred = false - - if self.component?.holdAction != nil { - self.holdActionTriggerred = true - self.component?.action() - - self.holdActionTimer?.invalidate() - let holdActionTimer = Timer(timeInterval: 0.5, repeats: false, block: { [weak self] _ in - guard let strongSelf = self else { - return - } - strongSelf.holdActionTimer?.invalidate() - strongSelf.component?.holdAction?() - strongSelf.beginExecuteHoldActionTimer() - }) - self.holdActionTimer = holdActionTimer - RunLoop.main.add(holdActionTimer, forMode: .common) - } - - return super.beginTracking(touch, with: event) - } +// override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { +// self.currentIsHighlighted = true +// +// self.holdActionTriggerred = false +// +// if self.component?.holdAction != nil { +// self.holdActionTriggerred = true +// self.component?.action() +// +// self.holdActionTimer?.invalidate() +// let holdActionTimer = Timer(timeInterval: 0.5, repeats: false, block: { [weak self] _ in +// guard let strongSelf = self else { +// return +// } +// strongSelf.holdActionTimer?.invalidate() +// strongSelf.component?.holdAction?() +// strongSelf.beginExecuteHoldActionTimer() +// }) +// self.holdActionTimer = holdActionTimer +// RunLoop.main.add(holdActionTimer, forMode: .common) +// } +// +// return super.beginTracking(touch, with: event) +// } - private func beginExecuteHoldActionTimer() { - self.holdActionTimer?.invalidate() - let holdActionTimer = Timer(timeInterval: 0.1, repeats: true, block: { [weak self] _ in - guard let strongSelf = self else { - return - } - strongSelf.component?.holdAction?() - }) - self.holdActionTimer = holdActionTimer - RunLoop.main.add(holdActionTimer, forMode: .common) - } +// private func beginExecuteHoldActionTimer() { +// self.holdActionTimer?.invalidate() +// let holdActionTimer = Timer(timeInterval: 0.1, repeats: true, block: { [weak self] _ in +// guard let strongSelf = self else { +// return +// } +// strongSelf.component?.holdAction?() +// }) +// self.holdActionTimer = holdActionTimer +// RunLoop.main.add(holdActionTimer, forMode: .common) +// } - override public func endTracking(_ touch: UITouch?, with event: UIEvent?) { - self.currentIsHighlighted = false - - self.holdActionTimer?.invalidate() - self.holdActionTimer = nil - - super.endTracking(touch, with: event) - } - - override public func cancelTracking(with event: UIEvent?) { - self.currentIsHighlighted = false - - self.holdActionTimer?.invalidate() - self.holdActionTimer = nil - - super.cancelTracking(with: event) - } +// override public func endTracking(_ touch: UITouch?, with event: UIEvent?) { +// self.currentIsHighlighted = false +// +// self.holdActionTimer?.invalidate() +// self.holdActionTimer = nil +// +// super.endTracking(touch, with: event) +// } +// +// override public func cancelTracking(with event: UIEvent?) { +// self.currentIsHighlighted = false +// +// self.holdActionTimer?.invalidate() +// self.holdActionTimer = nil +// +// super.cancelTracking(with: event) +// } - private func updateAlpha(transition: ComponentTransition) { - let alpha: CGFloat = self.currentIsHighlighted ? 0.6 : 1.0 - transition.setAlpha(view: self.iconView, alpha: alpha) - } +// private func updateAlpha(transition: ComponentTransition) { +// let alpha: CGFloat = self.currentIsHighlighted ? 0.6 : 1.0 +// transition.setAlpha(view: self.iconView, alpha: alpha) +// } func update(component: EntityKeyboardBottomPanelButton, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { if self.component?.icon != component.icon { @@ -153,15 +162,21 @@ final class EntityKeyboardBottomPanelButton: Component { self.component = component - self.iconView.tintColor = component.color + self.iconView.tintColor = component.theme.chat.inputPanel.panelControlColor - let size = CGSize(width: 38.0, height: 38.0) + let size = CGSize(width: 40.0, height: 40.0) if let image = self.iconView.image { let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size) self.iconView.frame = iconFrame } + let tintColor: GlassBackgroundView.TintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) + self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: tintColor, isInteractive: true, transition: transition) + + self.buttonView.frame = CGRect(origin: .zero, size: size) + return size } } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift index 5db313c3..9a480929 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift @@ -8,6 +8,9 @@ import TelegramCore import ComponentDisplayAdapters import BundleIconComponent import GlassBackgroundComponent +import EdgeEffect +import LiquidLens +import TabSelectionRecognizer private final class BottomPanelIconComponent: Component { let title: String @@ -57,22 +60,15 @@ private final class BottomPanelIconComponent: Component { super.init(frame: frame) self.addSubview(self.contentView) - self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.component?.action() - } - } - func update(component: BottomPanelIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { if self.component?.title != component.title { - let text = NSAttributedString(string: component.title, font: Font.medium(15.0), textColor: .white) + let text = NSAttributedString(string: component.title, font: Font.medium(14.0), textColor: .white) let textBounds = text.boundingRect(with: CGSize(width: 120.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) self.contentView.image = generateImage(CGSize(width: ceil(textBounds.width), height: ceil(textBounds.height)), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -89,19 +85,9 @@ private final class BottomPanelIconComponent: Component { let textSize = self.contentView.image?.size ?? CGSize() let size = CGSize(width: textSize.width + textInset * 2.0, height: 28.0) - let color = component.theme.chat.inputPanel.inputControlColor + self.contentView.tintColor = component.theme.chat.inputPanel.panelControlColor - if self.contentView.tintColor != color { - if !transition.animation.isImmediate { - UIView.animate(withDuration: 0.15, delay: 0.0, options: [], animations: { - self.contentView.tintColor = color - }, completion: nil) - } else { - self.contentView.tintColor = color - } - } - - transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: (size.height - textSize.height) / 2.0 - 1.0), size: textSize)) + transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: (size.height - textSize.height) / 2.0), size: textSize)) return size } @@ -163,17 +149,27 @@ final class EntityKeyboardBottomPanelComponent: Component { private var leftAccessoryButton: AccessoryButtonView? private var rightAccessoryButton: AccessoryButtonView? - private var iconViews: [AnyHashable: ComponentHostView] = [:] - private var highlightedIconBackgroundView: UIView - private var highlightedTintIconBackgroundView: UIView + private let edgeEffectView: EdgeEffectView + private let backgroundContainer: GlassBackgroundContainerView + private let liquidLensView: LiquidLensView + private var itemViews: [AnyHashable: ComponentHostView] = [:] + private var selectedItemViews: [AnyHashable: ComponentHostView] = [:] + + private var tabSelectionRecognizer: TabSelectionRecognizer? + private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)? + let tintContentMask: UIView private var component: EntityKeyboardBottomPanelComponent? + private var state: EmptyComponentState? + private var environment: PagerComponentPanelEnvironment? override init(frame: CGRect) { self.tintContentMask = UIView() + self.edgeEffectView = EdgeEffectView() + self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true, customBlurRadius: 10.0) self.separatorView = UIView() @@ -182,51 +178,102 @@ final class EntityKeyboardBottomPanelComponent: Component { self.tintSeparatorView.isUserInteractionEnabled = false self.tintSeparatorView.backgroundColor = UIColor(white: 0.0, alpha: 0.7) - self.tintContentMask.addSubview(self.tintSeparatorView) - - self.highlightedIconBackgroundView = UIView() - self.highlightedIconBackgroundView.isUserInteractionEnabled = false - self.highlightedIconBackgroundView.layer.cornerRadius = 10.0 - self.highlightedIconBackgroundView.clipsToBounds = true - - self.highlightedTintIconBackgroundView = UIView() - self.highlightedTintIconBackgroundView.isUserInteractionEnabled = false - self.highlightedTintIconBackgroundView.layer.cornerRadius = 10.0 - self.highlightedTintIconBackgroundView.clipsToBounds = true - self.highlightedTintIconBackgroundView.backgroundColor = UIColor(white: 0.0, alpha: 0.1) - - self.tintContentMask.addSubview(self.highlightedTintIconBackgroundView) - + self.backgroundContainer = GlassBackgroundContainerView() + self.liquidLensView = LiquidLensView(kind: .externalContainer) + super.init(frame: frame) - self.addSubview(self.backgroundView) - self.addSubview(self.highlightedIconBackgroundView) - self.addSubview(self.separatorView) + self.addSubview(self.edgeEffectView) + + self.addSubview(self.backgroundContainer) + self.backgroundContainer.contentView.addSubview(self.liquidLensView) + + let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:))) + self.tabSelectionRecognizer = tabSelectionRecognizer + self.liquidLensView.addGestureRecognizer(tabSelectionRecognizer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + private func item(at point: CGPoint) -> AnyHashable? { + var closestItem: (AnyHashable, CGFloat)? + for (id, itemView) in self.itemViews { + if itemView.frame.contains(point) { + return id + } else { + let distance = abs(point.x - itemView.center.x) + if let closestItemValue = closestItem { + if closestItemValue.1 > distance { + closestItem = (id, distance) + } + } else { + closestItem = (id, distance) + } + } + } + return closestItem?.0 + } + + @objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) { + guard let environment = self.environment else { + return + } + let location = recognizer.location(in: self.liquidLensView.contentView) + switch recognizer.state { + case .began: + if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] { + let startX = itemView.frame.minX - 4.0 + self.selectionGestureState = (startX, startX, itemId) + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) + } + case .changed: + if var selectionGestureState = self.selectionGestureState { + selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x + if let itemId = self.item(at: location) { + selectionGestureState.itemId = itemId + } + self.selectionGestureState = selectionGestureState + self.state?.updated(transition: .immediate, isLocal: true) + } + case .ended, .cancelled: + if let selectionGestureState = self.selectionGestureState { + self.selectionGestureState = nil + if case .ended = recognizer.state { + guard let item = environment.contentIcons.first(where: { $0.id == selectionGestureState.itemId }) else { + return + } + environment.navigateToContentId(item.id) + } + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) + } + default: + break + } + } + func update(component: EntityKeyboardBottomPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { if self.component?.theme !== component.theme { self.separatorView.backgroundColor = component.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5) self.backgroundView.updateColor(color: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(1.0), transition: .immediate) - self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor } let intrinsicHeight: CGFloat = 34.0 - let height = intrinsicHeight + component.containerInsets.bottom + let height = intrinsicHeight + component.containerInsets.bottom + 20.0 let accessoryButtonOffset: CGFloat if component.containerInsets.bottom > 0.0 { - accessoryButtonOffset = 2.0 + accessoryButtonOffset = 0.0 } else { accessoryButtonOffset = -2.0 } + self.component = component + self.state = state let panelEnvironment = environment[PagerComponentPanelEnvironment.self].value + self.environment = panelEnvironment let activeContentId = panelEnvironment.activeContentId var leftAccessoryButtonComponent: AnyComponentWithIdentity? @@ -257,7 +304,7 @@ final class EntityKeyboardBottomPanelComponent: Component { environment: {}, containerSize: CGSize(width: .greatestFiniteMagnitude, height: intrinsicHeight) ) - let leftAccessoryButtonFrame = CGRect(origin: CGPoint(x: component.containerInsets.left + 2.0, y: accessoryButtonOffset), size: leftAccessoryButtonSize) + let leftAccessoryButtonFrame = CGRect(origin: CGPoint(x: component.containerInsets.left + 18.0, y: accessoryButtonOffset), size: leftAccessoryButtonSize) leftAccessoryButtonTransition.setFrame(view: leftAccessoryButton.view, frame: leftAccessoryButtonFrame) if let leftAccessoryButtonView = leftAccessoryButton.view.componentView as? PagerTopPanelView { if leftAccessoryButtonView.tintContentMask.superview == nil { @@ -328,7 +375,7 @@ final class EntityKeyboardBottomPanelComponent: Component { containerSize: CGSize(width: .greatestFiniteMagnitude, height: intrinsicHeight) ) - let rightAccessoryButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.containerInsets.right - 2.0 - rightAccessoryButtonSize.width, y: accessoryButtonOffset), size: rightAccessoryButtonSize) + let rightAccessoryButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.containerInsets.right - 18.0 - rightAccessoryButtonSize.width, y: accessoryButtonOffset), size: rightAccessoryButtonSize) rightAccessoryButtonTransition.setFrame(view: rightAccessoryButton.view, frame: rightAccessoryButtonFrame) if let rightAccessoryButtonView = rightAccessoryButton.view.componentView as? PagerTopPanelView { if rightAccessoryButtonView.tintContentMask.superview == nil { @@ -374,23 +421,32 @@ final class EntityKeyboardBottomPanelComponent: Component { var iconInfos: [AnyHashable: (size: CGSize, transition: ComponentTransition)] = [:] var iconTotalSize = CGSize() - let iconSpacing: CGFloat = 4.0 + let iconSpacing: CGFloat = 0.0 let navigateToContentId = panelEnvironment.navigateToContentId + var lensSelection: (x: CGFloat, width: CGFloat) = (0.0, 0.0) + if panelEnvironment.contentIcons.count > 1 { for icon in panelEnvironment.contentIcons { validIconIds.append(icon.id) var iconTransition = transition let iconView: ComponentHostView - if let current = self.iconViews[icon.id] { + let selectedIconView: ComponentHostView + if let current = self.itemViews[icon.id], let currentSelected = self.selectedItemViews[icon.id] { iconView = current + selectedIconView = currentSelected } else { iconTransition = .immediate iconView = ComponentHostView() - self.iconViews[icon.id] = iconView - self.addSubview(iconView) + iconView.isUserInteractionEnabled = false + selectedIconView = ComponentHostView() + selectedIconView.isUserInteractionEnabled = false + self.itemViews[icon.id] = iconView + self.selectedItemViews[icon.id] = selectedIconView + self.liquidLensView.contentView.addSubview(iconView) + self.liquidLensView.selectedContentView.addSubview(selectedIconView) } let iconSize = iconView.update( @@ -407,65 +463,70 @@ final class EntityKeyboardBottomPanelComponent: Component { containerSize: CGSize(width: 28.0, height: 28.0) ) + let _ = selectedIconView.update( + transition: iconTransition, + component: AnyComponent(BottomPanelIconComponent( + title: icon.title, + isHighlighted: icon.id == activeContentId, + theme: component.theme, + action: { + navigateToContentId(icon.id) + } + )), + environment: {}, + containerSize: CGSize(width: 28.0, height: 28.0) + ) + iconInfos[icon.id] = (size: iconSize, transition: iconTransition) if !iconTotalSize.width.isZero { - iconTotalSize.width += iconSpacing + iconTotalSize.width += iconSpacing - 8.0 } iconTotalSize.width += iconSize.width iconTotalSize.height = max(iconTotalSize.height, iconSize.height) } } - - var nextIconOrigin = CGPoint(x: floor((availableSize.width - iconTotalSize.width) / 2.0), y: floor((intrinsicHeight - iconTotalSize.height) / 2.0)) - if component.containerInsets.bottom > 0.0 { - nextIconOrigin.y += 3.0 - } + + let tabsSize = CGSize(width: iconTotalSize.width, height: 40.0) + var nextIconOrigin = CGPoint(x: floor((tabsSize.width - iconTotalSize.width) / 2.0), y: floor((tabsSize.height - iconTotalSize.height) / 2.0)) + + transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: .zero, size: availableSize)) + self.backgroundContainer.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition) if panelEnvironment.contentIcons.count > 1 { for icon in panelEnvironment.contentIcons { - guard let iconInfo = iconInfos[icon.id], let iconView = self.iconViews[icon.id] else { + guard let iconInfo = iconInfos[icon.id], let iconView = self.itemViews[icon.id], let selectedIconView = self.selectedItemViews[icon.id] else { continue } let iconFrame = CGRect(origin: nextIconOrigin, size: iconInfo.size) iconInfo.transition.setFrame(view: iconView, frame: iconFrame, completion: nil) - - if let iconView = iconView.componentView as? BottomPanelIconComponent.View { - if iconView.tintMaskContainer.superview == nil { - self.tintContentMask.addSubview(iconView.tintMaskContainer) - } - iconInfo.transition.setFrame(view: iconView.tintMaskContainer, frame: iconFrame, completion: nil) - } + iconInfo.transition.setFrame(view: selectedIconView, frame: iconFrame, completion: nil) if let activeContentId = activeContentId, activeContentId == icon.id { - self.highlightedIconBackgroundView.isHidden = false - self.highlightedTintIconBackgroundView.isHidden = false - transition.setFrame(view: self.highlightedIconBackgroundView, frame: iconFrame) - transition.setFrame(view: self.highlightedTintIconBackgroundView, frame: iconFrame) - - let cornerRadius: CGFloat = min(iconFrame.width, iconFrame.height) / 2.0 - transition.setCornerRadius(layer: self.highlightedIconBackgroundView.layer, cornerRadius: cornerRadius) - transition.setCornerRadius(layer: self.highlightedTintIconBackgroundView.layer, cornerRadius: cornerRadius) + lensSelection = (iconFrame.origin.x, iconFrame.width) } - - nextIconOrigin.x += iconInfo.size.width + iconSpacing + nextIconOrigin.x += iconInfo.size.width + iconSpacing - 8.0 } } - - if activeContentId == nil { - self.highlightedIconBackgroundView.isHidden = true + + if let selectionGestureState = self.selectionGestureState { + lensSelection = (selectionGestureState.currentX, lensSelection.width) } + transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - tabsSize.width) / 2.0), y: 0.0), size: tabsSize)) + self.liquidLensView.update(size: CGSize(width: tabsSize.width, height: tabsSize.height), selectionOrigin: CGPoint(x: max(0.0, min(tabsSize.width - lensSelection.width, lensSelection.x)), y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: tabsSize.height), inset: 3.0, isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: activeContentId == nil, transition: transition) + var removedIconViewIds: [AnyHashable] = [] - for (id, iconView) in self.iconViews { + for (id, iconView) in self.itemViews { if !validIconIds.contains(id) { removedIconViewIds.append(id) iconView.removeFromSuperview() } } + for id in removedIconViewIds { - self.iconViews.removeValue(forKey: id) + self.itemViews.removeValue(forKey: id) } transition.setFrame(view: self.separatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: UIScreenPixel))) @@ -473,8 +534,11 @@ final class EntityKeyboardBottomPanelComponent: Component { transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: height))) //self.backgroundView.update(size: CGSize(width: availableSize.width, height: height), transition: transition.containedViewLayoutTransition) - - self.component = component + + let edgeEffectHeight: CGFloat = 80.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: height - edgeEffectHeight), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: component.theme.chat.inputMediaPanel.backgroundColor.withMultipliedAlpha(0.8), rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, 50.0), transition: transition) return CGSize(width: availableSize.width, height: height) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift index a7d0dfc9..83c09abb 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift @@ -9,15 +9,18 @@ import Postbox public final class EntityKeyboardTopContainerPanelEnvironment: Equatable { let isContentInFocus: Bool + let height: CGFloat let visibilityFractionUpdated: ActionSlot<(CGFloat, ComponentTransition)> let isExpandedUpdated: (Bool, ComponentTransition) -> Void init( isContentInFocus: Bool, + height: CGFloat, visibilityFractionUpdated: ActionSlot<(CGFloat, ComponentTransition)>, isExpandedUpdated: @escaping (Bool, ComponentTransition) -> Void ) { self.isContentInFocus = isContentInFocus + self.height = height self.visibilityFractionUpdated = visibilityFractionUpdated self.isExpandedUpdated = isExpandedUpdated } @@ -26,6 +29,9 @@ public final class EntityKeyboardTopContainerPanelEnvironment: Equatable { if lhs.isContentInFocus != rhs.isContentInFocus { return false } + if lhs.height != rhs.height { + return false + } if lhs.visibilityFractionUpdated !== rhs.visibilityFractionUpdated { return false } @@ -39,17 +45,20 @@ final class EntityKeyboardTopContainerPanelComponent: Component { let theme: PresentationTheme let overflowHeight: CGFloat let topInset: CGFloat + let height: CGFloat let displayBackground: EntityKeyboardComponent.DisplayTopPanelBackground init( theme: PresentationTheme, overflowHeight: CGFloat, topInset: CGFloat, + height: CGFloat, displayBackground: EntityKeyboardComponent.DisplayTopPanelBackground ) { self.theme = theme self.overflowHeight = overflowHeight self.topInset = topInset + self.height = height self.displayBackground = displayBackground } @@ -60,6 +69,9 @@ final class EntityKeyboardTopContainerPanelComponent: Component { if lhs.overflowHeight != rhs.overflowHeight { return false } + if lhs.height != rhs.height { + return false + } if lhs.topInset != rhs.topInset { return false } @@ -105,7 +117,7 @@ final class EntityKeyboardTopContainerPanelComponent: Component { } func update(component: EntityKeyboardTopContainerPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - let intrinsicHeight: CGFloat = 34.0 + let intrinsicHeight: CGFloat = component.height let height = intrinsicHeight + component.topInset let panelEnvironment = environment[PagerComponentPanelEnvironment.self].value @@ -165,6 +177,7 @@ final class EntityKeyboardTopContainerPanelComponent: Component { environment: { EntityKeyboardTopContainerPanelEnvironment( isContentInFocus: panelEnvironment.isContentInFocus, + height: intrinsicHeight, visibilityFractionUpdated: panelView.visibilityFractionUpdated, isExpandedUpdated: { [weak self] isExpanded, transition in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift index d6c9379c..a28088ad 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift @@ -2009,7 +2009,7 @@ public final class EntityKeyboardTopPanelComponent: Component { let panelEnvironment = environment[EntityKeyboardTopContainerPanelEnvironment.self].value self.environment = panelEnvironment - let isExpanded = availableSize.height > 34.0 + let isExpanded = availableSize.height > panelEnvironment.height let wasExpanded = self.isExpanded self.isExpanded = isExpanded diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift index 761c842b..177ed05c 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift @@ -116,7 +116,7 @@ private final class SheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 08000283..0a0b5a40 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -1012,7 +1012,7 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer { isHiddenUpdatedImpl?(isHidden) }, openPremium: { openPremiumImpl?() - }), navigationBarAppearance: .transparent) + }), navigationBarAppearance: .default) let presentationData = context.sharedContext.currentPresentationData.with { $0 } let title: String diff --git a/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift index bad7a166..c570354d 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift @@ -23,7 +23,6 @@ public final class GiftCompositionComponent: Component { public fileprivate(set) var previewSymbol: StarGift.UniqueGift.Attribute? public init() { - self.previewPatternColor = nil } } diff --git a/submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView/Sources/GiftLoadingShimmerView.swift b/submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView/Sources/GiftLoadingShimmerView.swift index 55967358..07ecd244 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView/Sources/GiftLoadingShimmerView.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView/Sources/GiftLoadingShimmerView.swift @@ -164,7 +164,7 @@ public final class GiftLoadingShimmerView: UIView { } } - var currentY: CGFloat = 39.0 + 7.0 + var currentY: CGFloat = 52.0 + 7.0 var rowIndex: Int = 0 let optionSpacing: CGFloat = 10.0 diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD index 558f8cfb..089497fb 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD @@ -46,6 +46,9 @@ swift_library( "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent", + "//submodules/TelegramUI/Components/AvatarComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftAuctionTransferController.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftAuctionTransferController.swift index fde4bd99..3344febc 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftAuctionTransferController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftAuctionTransferController.swift @@ -3,238 +3,20 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox +import ComponentFlow import TelegramCore import TelegramPresentationData -import TelegramUIPreferences import AccountContext import AppBundle -import AvatarNode -import Markdown +import AlertComponent +import AlertTransferHeaderComponent +import AvatarComponent -private final class GiftAuctionTransferAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - private let title: String - private let text: String - - private let titleNode: ASTextNode - private let textNode: ASTextNode - private let avatarNode: AvatarNode - private let arrowNode: ASImageNode - private let secondAvatarNode: AvatarNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, fromPeer: EnginePeer, toPeer: EnginePeer, title: String, text: String, actions: [TextAlertAction]) { - self.strings = strings - self.title = title - self.text = text - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 0 - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) - - self.arrowNode = ASImageNode() - self.arrowNode.displaysAsynchronously = false - self.arrowNode.displayWithoutProcessing = true - - self.secondAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - self.addSubnode(self.avatarNode) - self.addSubnode(self.arrowNode) - self.addSubnode(self.secondAvatarNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - - self.avatarNode.setPeer(context: context, theme: ptheme, peer: fromPeer) - self.secondAvatarNode.setPeer(context: context, theme: ptheme, peer: toPeer) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - linkAttribute: { url in - return ("URL", url) - } - ), textAlignment: .center) - self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let avatarSize = CGSize(width: 60.0, height: 60.0) - self.avatarNode.updateSize(size: avatarSize) - - let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize) - transition.updateFrame(node: self.avatarNode, frame: avatarFrame) - - if let arrowImage = self.arrowNode.image { - let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) - transition.updateFrame(node: self.arrowNode, frame: arrowFrame) - } - - let secondAvatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize) - transition.updateFrame(node: self.secondAvatarNode, frame: secondAvatarFrame) - - origin.y += avatarSize.height + 10.0 - - let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 4.0 - - let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 10.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 16.0 + insets.top + insets.bottom) - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} - -func giftAuctionTransferController(context: AccountContext, fromPeer: EnginePeer, toPeer: EnginePeer, commit: @escaping () -> Void) -> AlertController { +func giftAuctionTransferController(context: AccountContext, fromPeer: EnginePeer, toPeer: EnginePeer, commit: @escaping () -> Void) -> AlertScreen { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings + let title = strings.Gift_AuctionTransfer_Title let text: String if fromPeer.id == context.account.peerId { text = strings.Gift_AuctionTransfer_TextFromYourself(toPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string @@ -243,25 +25,53 @@ func giftAuctionTransferController(context: AccountContext, fromPeer: EnginePeer } else { text = strings.Gift_AuctionTransfer_Text(fromPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), toPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string } - - var dismissImpl: ((Bool) -> Void)? - var contentNode: GiftAuctionTransferAlertContentNode? - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - }), TextAlertAction(type: .defaultAction, title: strings.Gift_AuctionTransfer_Change, action: { - dismissImpl?(true) - commit() - })] - contentNode = GiftAuctionTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, fromPeer: fromPeer, toPeer: toPeer, title: strings.Gift_AuctionTransfer_Title, text: text, actions: actions) + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertTransferHeaderComponent( + fromComponent: AnyComponentWithIdentity(id: "fromPeer", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: fromPeer + ) + )), + toComponent: AnyComponentWithIdentity(id: "toPeer", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: toPeer + ) + )), + type: .transfer + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + let alertController = AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false), + content: content, + actions: [ + .init(title: strings.Gift_AuctionTransfer_Change, type: .default, action: { + commit() + }), + .init(title: strings.Common_Cancel) + ] + ) + return alertController } diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 71760422..377a6833 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -34,6 +34,7 @@ final class GiftOptionsScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let starsContext: StarsContext let peerId: EnginePeer.Id let premiumOptions: [CachedPremiumGiftOption] @@ -42,6 +43,7 @@ final class GiftOptionsScreenComponent: Component { init( context: AccountContext, + overNavigationContainer: UIView, starsContext: StarsContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption], @@ -49,6 +51,7 @@ final class GiftOptionsScreenComponent: Component { completion: (() -> Void)? ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.starsContext = starsContext self.peerId = peerId self.premiumOptions = premiumOptions @@ -366,7 +369,7 @@ final class GiftOptionsScreenComponent: Component { return } - if gift.flags.contains(.isAuction) { + if gift.flags.contains(.isAuction) && !((gift.availability?.resale ?? 0) > 0 && component.peerId != component.context.account.peerId) { guard let giftAuctionsManager = component.context.giftAuctionsManager else { return } @@ -416,6 +419,7 @@ final class GiftOptionsScreenComponent: Component { let giftController = component.context.sharedContext.makeGiftAuctionViewScreen( context: component.context, auctionContext: auctionContext, + peerId: component.peerId, completion: { [weak mainController] acquiredGifts, upgradeAttributes in if component.peerId == context.account.peerId, let upgradeAttributes, let navigationController = mainController?.navigationController as? NavigationController { let controller = context.sharedContext.makeGiftAuctionWearPreviewScreen(context: context, auctionContext: auctionContext, acquiredGifts: acquiredGifts, attributes: upgradeAttributes, completion: { @@ -988,7 +992,7 @@ final class GiftOptionsScreenComponent: Component { controller.present(alertController, in: .current) dismissAlertImpl = { [weak alertController] in - alertController?.dismissAnimated() + alertController?.dismiss() } } @@ -1084,6 +1088,9 @@ final class GiftOptionsScreenComponent: Component { guard let self, let component = self.component, let controller = controller(), let navigationController = controller.navigationController as? NavigationController else { return } + guard component.peerId != component.context.account.peerId else { + return + } let _ = (component.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId) ) @@ -1141,18 +1148,18 @@ final class GiftOptionsScreenComponent: Component { } if isGlass { - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let cancelButtonSize = self.cancelButton.update( transition: transition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in @@ -1919,6 +1926,8 @@ final class GiftOptionsScreenComponent: Component { open class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScreenProtocol { private let context: AccountContext + private let overNavigationContainer: UIView + public var parentController: () -> ViewController? = { return nil } @@ -1933,8 +1942,11 @@ open class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScree ) { self.context = context + self.overNavigationContainer = SparseContainerView() + super.init(context: context, component: GiftOptionsScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, starsContext: starsContext, peerId: peerId, premiumOptions: premiumOptions, @@ -1951,6 +1963,10 @@ open class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScree } componentView.scrollToTop() } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift index 01753181..f6c7f11a 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -193,7 +193,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { self.containerNode = ASDisplayNode() self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.clipsToBounds = true self.isUserInteractionEnabled = false diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index b4ac9a97..dc774f1c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -622,7 +622,9 @@ private final class GiftSetupScreenComponent: Component { } } - starsContext.load(force: true) + Queue.mainQueue().after(2.5) { + starsContext.load(force: true) + } }, error: { [weak self] error in guard let self, let controller = self.environment?.controller() else { return @@ -768,7 +770,6 @@ private final class GiftSetupScreenComponent: Component { pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, @@ -1118,14 +1119,14 @@ private final class GiftSetupScreenComponent: Component { let closeButtonSize = self.closeButton.update( transition: .immediate, component: AnyComponent(GlassBarButtonComponent( - size: CGSize(width: 40.0, height: 40.0), - backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, isDark: environment.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -1136,7 +1137,7 @@ private final class GiftSetupScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: CGSize(width: 44.0, height: 44.0) ) let closeButtonFrame = CGRect(origin: CGPoint(x: rawSideInset + 16.0, y: 16.0), size: closeButtonSize) if let closeButtonView = self.closeButton.view { diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD index 26cd12ad..16f3589b 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD @@ -46,6 +46,8 @@ swift_library( "//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/TextFieldComponent", "//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/FilterSelectorComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/FilterSelectorComponent.swift index f282b150..14f0697c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/FilterSelectorComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/FilterSelectorComponent.swift @@ -9,21 +9,10 @@ import BundleIconComponent import TextFormat import AccountContext import LottieComponent +import TelegramPresentationData +import GlassBackgroundComponent public final class FilterSelectorComponent: Component { - public struct Colors: Equatable { - public var foreground: UIColor - public var background: UIColor - - public init( - foreground: UIColor, - background: UIColor - ) { - self.foreground = foreground - self.background = background - } - } - public struct Item: Equatable { public var id: AnyHashable public var index: Int @@ -51,18 +40,18 @@ public final class FilterSelectorComponent: Component { } public let context: AccountContext? - public let colors: Colors + public let theme: PresentationTheme public let items: [Item] public let selectedItemId: AnyHashable? public init( context: AccountContext? = nil, - colors: Colors, + theme: PresentationTheme, items: [Item], selectedItemId: AnyHashable? ) { self.context = context - self.colors = colors + self.theme = theme self.items = items self.selectedItemId = selectedItemId } @@ -71,7 +60,7 @@ public final class FilterSelectorComponent: Component { if lhs.context !== rhs.context { return false } - if lhs.colors != rhs.colors { + if lhs.theme !== rhs.theme { return false } if lhs.items != rhs.items { @@ -133,11 +122,11 @@ public final class FilterSelectorComponent: Component { self.component = component self.state = state - let baseHeight: CGFloat = 28.0 + let baseHeight: CGFloat = 36.0 var spacing: CGFloat = 6.0 - let itemFont = Font.semibold(14.0) + let itemFont = Font.medium(14.0) let allowScroll = true var innerContentWidth: CGFloat = 0.0 @@ -162,25 +151,19 @@ public final class FilterSelectorComponent: Component { let itemSize = itemView.title.update( transition: transition, - component: AnyComponent(PlainButtonComponent( - content: AnyComponent(ItemComponent( - context: component.context, - index: item.index, - iconName: item.iconName, - text: item.title, - font: itemFont, - color: component.colors.foreground, - backgroundColor: component.colors.background, - isSelected: itemId == component.selectedItemId - )), - effectAlignment: .center, - minSize: nil, + component: AnyComponent(ItemComponent( + context: component.context, + index: item.index, + iconName: item.iconName, + text: item.title, + font: itemFont, + theme: component.theme, + isSelected: itemId == component.selectedItemId, action: { [weak itemView] in if let view = itemView?.title.view { item.action(view) } - }, - animateScale: false + } )), environment: {}, containerSize: CGSize(width: 200.0, height: 100.0) @@ -261,9 +244,9 @@ private final class ItemComponent: Component { let iconName: String? let text: String let font: UIFont - let color: UIColor - let backgroundColor: UIColor + let theme: PresentationTheme let isSelected: Bool + let action: () -> Void init( context: AccountContext?, @@ -271,18 +254,18 @@ private final class ItemComponent: Component { iconName: String?, text: String, font: UIFont, - color: UIColor, - backgroundColor: UIColor, - isSelected: Bool + theme: PresentationTheme, + isSelected: Bool, + action: @escaping () -> Void ) { self.context = context self.index = index self.iconName = iconName self.text = text self.font = font - self.color = color - self.backgroundColor = backgroundColor + self.theme = theme self.isSelected = isSelected + self.action = action } static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool { @@ -301,10 +284,7 @@ private final class ItemComponent: Component { if lhs.font != rhs.font { return false } - if lhs.color != rhs.color { - return false - } - if lhs.backgroundColor != rhs.backgroundColor { + if lhs.theme !== rhs.theme { return false } if lhs.isSelected != rhs.isSelected { @@ -317,7 +297,7 @@ private final class ItemComponent: Component { private var component: ItemComponent? private weak var state: EmptyComponentState? - private let background = ComponentView() + private let backgroundView: GlassBackgroundView private let title = ComponentView() private let icon = ComponentView() @@ -327,13 +307,24 @@ private final class ItemComponent: Component { private let playOnce = ActionSlot() override init(frame: CGRect) { + self.backgroundView = GlassBackgroundView() + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.backgroundView.contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.component?.action() + } + } + func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let previousComponent = self.component self.component = component @@ -342,7 +333,7 @@ private final class ItemComponent: Component { var animateTitleInDirection: CGFloat? if let previousComponent, previousComponent.text != component.text, !transition.animation.isImmediate, let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) { snapshotView.frame = titleView.frame - self.addSubview(snapshotView) + self.backgroundView.contentView.addSubview(snapshotView) var direction: CGFloat = 1.0 if previousComponent.index < component.index { @@ -357,7 +348,7 @@ private final class ItemComponent: Component { animateTitleInDirection = direction } - let attributedTitle = NSAttributedString(string: component.text, font: component.font, textColor: component.color) + let attributedTitle = NSAttributedString(string: component.text, font: component.font, textColor: component.theme.chat.inputPanel.panelControlColor) let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( @@ -374,7 +365,7 @@ private final class ItemComponent: Component { transition: transition, component: AnyComponent(LottieComponent( content: LottieComponent.AppBundleContent(name: animationName), - color: component.color, + color: component.theme.chat.inputPanel.panelControlColor, playOnce: self.playOnce )), environment: {}, @@ -406,29 +397,14 @@ private final class ItemComponent: Component { } let spacing: CGFloat = 4.0 let totalWidth = titleSize.width + animationSize.width + spacing - let size = CGSize(width: totalWidth + leftPadding + padding, height: 28.0) + let size = CGSize(width: totalWidth + leftPadding + padding, height:36.0) - let backgroundSize = self.background.update( - transition: transition, - component: AnyComponent(RoundedRectangle( - color: component.backgroundColor, - cornerRadius: 14.0 - )), - environment: {}, - containerSize: size - ) - - if let backgroundView = self.background.view { - if backgroundView.superview == nil { - self.addSubview(backgroundView) - } - transition.setPosition(view: backgroundView, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0)) - transition.setBounds(view: backgroundView, bounds: CGRect(origin: CGPoint(), size: backgroundSize)) - } + self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) if let titleView = self.title.view { if titleView.superview == nil { - self.addSubview(titleView) + self.backgroundView.contentView.addSubview(titleView) } let titlePosition: CGPoint if let _ = component.iconName { @@ -446,7 +422,7 @@ private final class ItemComponent: Component { if let iconView = self.icon.view { if iconView.superview == nil { - self.addSubview(iconView) + self.backgroundView.contentView.addSubview(iconView) } let iconPosition: CGPoint if let _ = component.iconName { diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift index f8fcc34c..313b5508 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift @@ -27,6 +27,8 @@ import UndoUI import ContextUI import LottieComponent import GiftLoadingShimmerView +import EdgeEffect +import GlassBackgroundComponent private let minimumCountToDisplayFilters = 18 @@ -34,17 +36,20 @@ final class GiftStoreScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let starsContext: StarsContext let peerId: EnginePeer.Id let gift: StarGift.Gift init( context: AccountContext, + overNavigationContainer: UIView, starsContext: StarsContext, peerId: EnginePeer.Id, gift: StarGift.Gift ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.starsContext = starsContext self.peerId = peerId self.gift = gift @@ -77,11 +82,11 @@ final class GiftStoreScreenComponent: Component { private let emptyResultsTitle = ComponentView() private let clearFilters = ComponentView() - private let topPanel = ComponentView() - private let topSeparator = ComponentView() + private let edgeEffectView: EdgeEffectView private let cancelButton = ComponentView() private let sortButton = ComponentView() - + + private let balanceBackgroundView: GlassBackgroundView private let balanceTitle = ComponentView() private let balanceValue = ComponentView() private let balanceIcon = ComponentView() @@ -106,6 +111,8 @@ final class GiftStoreScreenComponent: Component { private var environment: EnvironmentType? override init(frame: CGRect) { + self.balanceBackgroundView = GlassBackgroundView() + self.scrollView = ScrollView() self.scrollView.showsVerticalScrollIndicator = true self.scrollView.showsHorizontalScrollIndicator = false @@ -120,12 +127,16 @@ final class GiftStoreScreenComponent: Component { self.loadingView = GiftLoadingShimmerView() + self.edgeEffectView = EdgeEffectView() + super.init(frame: frame) self.scrollView.delegate = self self.addSubview(self.scrollView) self.addSubview(self.loadingView) + self.addSubview(self.edgeEffectView) + self.scrollView.layer.addSublayer(self.topOverscrollLayer) } @@ -169,15 +180,8 @@ final class GiftStoreScreenComponent: Component { let availableWidth = self.scrollView.bounds.width let availableHeight = self.scrollView.bounds.height - let contentOffset = self.scrollView.contentOffset.y - - let topPanelAlpha = min(20.0, max(0.0, contentOffset)) / 20.0 - if let topPanelView = self.topPanel.view, let topSeparator = self.topSeparator.view { - transition.setAlpha(view: topPanelView, alpha: topPanelAlpha) - transition.setAlpha(view: topSeparator, alpha: topPanelAlpha) - } - var topInset = environment.navigationHeight + 39.0 + var topInset = environment.navigationHeight + 53.0 if let initialCount = self.initialCount, initialCount < minimumCountToDisplayFilters { topInset = environment.navigationHeight } @@ -889,41 +893,15 @@ final class GiftStoreScreenComponent: Component { var contentHeight: CGFloat = 0.0 contentHeight += environment.navigationHeight - var topPanelHeight = environment.navigationHeight + 39.0 + var topPanelHeight = environment.navigationHeight + 53.0 if let initialCount = self.initialCount, initialCount < minimumCountToDisplayFilters { topPanelHeight = environment.navigationHeight } - - let topPanelSize = self.topPanel.update( - transition: transition, - component: AnyComponent(BlurredBackgroundComponent( - color: theme.rootController.navigationBar.blurredBackgroundColor - )), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: topPanelHeight) - ) - let topSeparatorSize = self.topSeparator.update( - transition: transition, - component: AnyComponent(Rectangle( - color: theme.rootController.navigationBar.separatorColor - )), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: UIScreenPixel) - ) - let topPanelFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: topPanelSize.height)) - let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height)) - if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view { - if topPanelView.superview == nil { - topPanelView.alpha = 0.0 - topSeparatorView.alpha = 0.0 - - self.addSubview(topPanelView) - self.addSubview(topSeparatorView) - } - transition.setFrame(view: topPanelView, frame: topPanelFrame) - transition.setFrame(view: topSeparatorView, frame: topSeparatorFrame) - } + let edgeEffectHeight: CGFloat = topPanelHeight + 8.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, blur: true, rect: edgeEffectFrame, edge: .top, edgeSize: min(30, edgeEffectFrame.height), transition: transition) let balanceTitleSize = self.balanceTitle.update( transition: .immediate, @@ -960,26 +938,41 @@ final class GiftStoreScreenComponent: Component { containerSize: availableSize ) - if let balanceTitleView = self.balanceTitle.view, let balanceValueView = self.balanceValue.view, let balanceIconView = self.balanceIcon.view { - if balanceTitleView.superview == nil { - self.addSubview(balanceTitleView) - self.addSubview(balanceValueView) - self.addSubview(balanceIconView) - } - let navigationHeight = environment.navigationHeight - environment.statusBarHeight - let topBalanceOriginY = environment.statusBarHeight + (navigationHeight - balanceTitleSize.height - balanceValueSize.height) / 2.0 - balanceTitleView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceTitleSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height / 2.0) - balanceTitleView.bounds = CGRect(origin: .zero, size: balanceTitleSize) - balanceValueView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceValueSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0) - balanceValueView.bounds = CGRect(origin: .zero, size: balanceValueSize) - balanceIconView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceValueSize.width - balanceIconSize.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0 - UIScreenPixel) - balanceIconView.bounds = CGRect(origin: .zero, size: balanceIconSize) + if self.balanceBackgroundView.superview == nil { + component.overNavigationContainer.addSubview(self.balanceBackgroundView) } var topInset: CGFloat = 0.0 if environment.statusBarHeight > 0.0 { topInset = environment.statusBarHeight - 6.0 } + + if let balanceTitleView = self.balanceTitle.view, let balanceValueView = self.balanceValue.view, let balanceIconView = self.balanceIcon.view { + if balanceTitleView.superview == nil { + self.balanceBackgroundView.contentView.addSubview(balanceTitleView) + self.balanceBackgroundView.contentView.addSubview(balanceValueView) + self.balanceBackgroundView.contentView.addSubview(balanceIconView) + } + + let topBalanceOriginY = (44.0 - balanceTitleSize.height - balanceValueSize.height) / 2.0 + + let balanceSideInset: CGFloat = 12.0 + var balanceBackgroundSize = CGSize(width: balanceTitleSize.width + balanceSideInset * 2.0, height: 44.0) + balanceBackgroundSize.width = max(balanceBackgroundSize.width, balanceValueSize.width + balanceIconSize.width + 2.0 + balanceSideInset * 2.0) + + let balanceBackgroundFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - balanceBackgroundSize.width, y: environment.navigationHeight - 60.0 + 2.0 + floor((60.0 - 44.0) * 0.5)), size: balanceBackgroundSize) + + transition.setFrame(view: self.balanceBackgroundView, frame: balanceBackgroundFrame) + self.balanceBackgroundView.update(size: balanceBackgroundFrame.size, cornerRadius: balanceBackgroundFrame.height * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: environment.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: transition) + + balanceTitleView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceTitleSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height / 2.0) + balanceTitleView.bounds = CGRect(origin: .zero, size: balanceTitleSize) + balanceValueView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceValueSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0) + balanceValueView.bounds = CGRect(origin: .zero, size: balanceValueSize) + balanceIconView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceValueSize.width - balanceIconSize.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0 - UIScreenPixel) + balanceIconView.bounds = CGRect(origin: .zero, size: balanceIconSize) + } + let titleSize = self.title.update( transition: transition, component: AnyComponent(MultilineTextComponent( @@ -991,9 +984,9 @@ final class GiftStoreScreenComponent: Component { ) if let titleView = self.title.view { if titleView.superview == nil { - self.addSubview(titleView) + component.overNavigationContainer.addSubview(titleView) } - transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: topInset + 10.0), size: titleSize)) + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: topInset + 22.0), size: titleSize)) } let effectiveCount: Int32 @@ -1018,10 +1011,10 @@ final class GiftStoreScreenComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width - headerSideInset * 2.0, height: 100.0) ) - let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) / 2.0), y: topInset + 31.0), size: subtitleSize) + let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) / 2.0), y: topInset + 43.0), size: subtitleSize) if let subtitleView = self.subtitle.view { if subtitleView.superview == nil { - self.addSubview(subtitleView) + component.overNavigationContainer.addSubview(subtitleView) } transition.setFrame(view: subtitleView, frame: subtitleFrame) } @@ -1144,10 +1137,7 @@ final class GiftStoreScreenComponent: Component { transition: transition, component: AnyComponent(FilterSelectorComponent( context: component.context, - colors: FilterSelectorComponent.Colors( - foreground: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.65), - background: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15) - ), + theme: theme, items: filterItems, selectedItemId: self.selectedFilterId )), @@ -1157,9 +1147,9 @@ final class GiftStoreScreenComponent: Component { if let filterSelectorView = self.filterSelector.view { if filterSelectorView.superview == nil { filterSelectorView.alpha = 0.0 - self.addSubview(filterSelectorView) + component.overNavigationContainer.addSubview(filterSelectorView) } - transition.setFrame(view: filterSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - filterSize.width) / 2.0), y: topInset + 56.0), size: filterSize)) + transition.setFrame(view: filterSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - filterSize.width) / 2.0), y: topInset + 60.0 + 18.0), size: filterSize)) if let initialCount = self.initialCount, initialCount >= minimumCountToDisplayFilters { loadingTransition.setAlpha(view: filterSelectorView, alpha: 1.0) @@ -1281,6 +1271,8 @@ final class GiftStoreScreenComponent: Component { public class GiftStoreScreen: ViewControllerComponentContainer { private let context: AccountContext + private let overNavigationContainer: UIView + public var parentController: () -> ViewController? = { return nil } @@ -1293,8 +1285,11 @@ public class GiftStoreScreen: ViewControllerComponentContainer { ) { self.context = context + self.overNavigationContainer = SparseContainerView() + super.init(context: context, component: GiftStoreScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, starsContext: starsContext, peerId: peerId, gift: gift @@ -1308,6 +1303,10 @@ public class GiftStoreScreen: ViewControllerComponentContainer { } componentView.scrollToTop() } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD index a5d6d5dc..5d86b40c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -46,7 +46,6 @@ swift_library( "//submodules/MoreButtonNode", "//submodules/TelegramUI/Components/EmojiStatusComponent", "//submodules/PasswordSetupUI", - "//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController", "//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent", "//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent", "//submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen", @@ -64,6 +63,13 @@ swift_library( "//submodules/TelegramUI/Components/SegmentControlComponent", "//submodules/TelegramUI/Components/Gifts/GiftRemainingCountComponent", "//submodules/TelegramUI/Components/Gifts/InfoParagraphComponent", + "//submodules/TelegramUI/Components/Gifts/TableComponent", + "//submodules/TelegramUI/Components/Gifts/PeerTableCellComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AvatarComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertTableComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionAcquiredScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionAcquiredScreen.swift index 574e2a4d..bbf99fe9 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionAcquiredScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionAcquiredScreen.swift @@ -19,6 +19,8 @@ import TelegramStringFormatting import GlassBarButtonComponent import GiftItemComponent import EdgeEffect +import TableComponent +import PeerTableCellComponent private final class GiftAuctionAcquiredScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -373,7 +375,7 @@ private final class GiftAuctionAcquiredScreenComponent: Component { title: environment.strings.Gift_Acquired_Recipient, component: AnyComponent(Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: environment.theme, strings: environment.strings, @@ -472,7 +474,7 @@ private final class GiftAuctionAcquiredScreenComponent: Component { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionActiveBidsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionActiveBidsScreen.swift index 1904b870..c7004ea0 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionActiveBidsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionActiveBidsScreen.swift @@ -372,7 +372,7 @@ private final class GiftAuctionActiveBidsScreenComponent: Component { if self.backgroundHandleView.image == nil { self.backgroundHandleView.image = generateStretchableFilledCircleImage(diameter: 5.0, color: .white)?.withRenderingMode(.alwaysTemplate) } - self.backgroundHandleView.tintColor = environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(environment.theme.overallDarkAppearance ? 0.2 : 0.07) + self.backgroundHandleView.tintColor = theme.list.itemPrimaryTextColor.withMultipliedAlpha(theme.overallDarkAppearance ? 0.2 : 0.07) let backgroundHandleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - 36.0) * 0.5), y: 5.0), size: CGSize(width: 36.0, height: 5.0)) if self.backgroundHandleView.superview == nil { self.navigationBarContainer.addSubview(self.backgroundHandleView) @@ -383,13 +383,13 @@ private final class GiftAuctionActiveBidsScreenComponent: Component { transition: .immediate, component: AnyComponent(GlassBarButtonComponent( size: CGSize(width: 40.0, height: 40.0), - backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, isDark: environment.theme.overallDarkAppearance, state: .generic, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -413,14 +413,13 @@ private final class GiftAuctionActiveBidsScreenComponent: Component { let containerInset: CGFloat = environment.statusBarHeight + 10.0 contentHeight += environment.safeInsets.bottom - var initialContentHeight = contentHeight let clippingY: CGFloat let titleText: String = environment.strings.Gift_ActiveAuctions_Title(Int32(self.auctionStates.count)) let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor)) )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) @@ -434,12 +433,12 @@ private final class GiftAuctionActiveBidsScreenComponent: Component { transition.setFrame(view: titleView, frame: titleFrame) } - initialContentHeight = contentHeight + let initialContentHeight = contentHeight let edgeEffectHeight: CGFloat = 80.0 let edgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: fillingSize, height: edgeEffectHeight)) transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame) - self.topEdgeEffectView.update(content: environment.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + self.topEdgeEffectView.update(content: theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) if self.topEdgeEffectView.superview == nil { self.navigationBarContainer.insertSubview(self.topEdgeEffectView, at: 0) } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift index 9863ae0d..d65fbb44 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift @@ -18,7 +18,6 @@ import RoundedRectWithTailPath import AvatarNode import BundleIconComponent import TextFormat -import CheckComponent import ContextUI import StarsBalanceOverlayComponent import StoryLiveChatMessageComponent @@ -369,17 +368,20 @@ private final class PeerPlaceComponent: Component { let theme: PresentationTheme let color: UIColor let place: Int32? + let placeIsApproximate: Bool let groupingSeparator: String init( theme: PresentationTheme, color: UIColor, place: Int32?, + placeIsApproximate: Bool, groupingSeparator: String ) { self.theme = theme self.color = color self.place = place + self.placeIsApproximate = placeIsApproximate self.groupingSeparator = groupingSeparator } @@ -393,6 +395,9 @@ private final class PeerPlaceComponent: Component { if lhs.place != rhs.place { return false } + if lhs.placeIsApproximate != rhs.placeIsApproximate { + return false + } if lhs.groupingSeparator != rhs.groupingSeparator { return false } @@ -455,7 +460,7 @@ private final class PeerPlaceComponent: Component { var placeString: String if let place = component.place { placeString = presentationStringsFormattedNumber(place, component.groupingSeparator) - if place >= 100 { + if component.placeIsApproximate { placeString = "\(compactNumericCountString(Int(place), decimalSeparator: ".", showDecimalPart: false))+" } } else { @@ -501,6 +506,7 @@ private final class PeerComponent: Component { let groupingSeparator: String let peer: EnginePeer let place: Int32 + let placeIsApproximate: Bool let amount: Int64 let status: Status? let isLast: Bool @@ -512,6 +518,7 @@ private final class PeerComponent: Component { groupingSeparator: String, peer: EnginePeer, place: Int32, + placeIsApproximate: Bool, amount: Int64, status: Status? = nil, isLast: Bool, @@ -522,6 +529,7 @@ private final class PeerComponent: Component { self.groupingSeparator = groupingSeparator self.peer = peer self.place = place + self.placeIsApproximate = placeIsApproximate self.amount = amount self.status = status self.isLast = isLast @@ -541,6 +549,9 @@ private final class PeerComponent: Component { if lhs.place != rhs.place { return false } + if lhs.placeIsApproximate != rhs.placeIsApproximate { + return false + } if lhs.amount != rhs.amount { return false } @@ -623,8 +634,17 @@ private final class PeerComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let placeSize = self.place.update( transition: .immediate, - component: AnyComponent(PeerPlaceComponent(theme: component.theme, color: color, place: component.status == .returned ? nil : component.place, groupingSeparator: presentationData.dateTimeFormat.groupingSeparator)), - environment: {}, + component: AnyComponent( + PeerPlaceComponent( + theme: component.theme, + color: color, + place: component.status == .returned ? nil : component.place, + placeIsApproximate: component.placeIsApproximate, + groupingSeparator: presentationData.dateTimeFormat.groupingSeparator + ) + ), + environment: { + }, containerSize: CGSize(width: 40.0, height: 40.0) ) let placeFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - placeSize.height) / 2.0)), size: placeSize) @@ -1616,8 +1636,10 @@ private final class GiftAuctionBidScreenComponent: Component { ), in: .current ) - - component.context.starsContext?.load(force: true) + + Queue.mainQueue().after(2.5) { + component.context.starsContext?.load(force: true) + } }, error: { [weak self] _ in guard let self else { return @@ -2061,7 +2083,7 @@ private final class GiftAuctionBidScreenComponent: Component { if case .finished = auctionState?.auctionState, let controller = self.environment?.controller() { if let navigationController = controller.navigationController as? NavigationController { controller.dismiss() - let auctionController = context.sharedContext.makeGiftAuctionViewScreen(context: context, auctionContext: auctionContext, completion: { _, _ in }) + let auctionController = context.sharedContext.makeGiftAuctionViewScreen(context: context, auctionContext: auctionContext, peerId: nil, completion: { _, _ in }) navigationController.pushViewController(auctionController) } } @@ -2213,8 +2235,9 @@ private final class GiftAuctionBidScreenComponent: Component { isBiddingUp = false } - place = giftAuctionState.getPlace(myBid: myBidAmount, myBidDate: myBidDate) ?? 1 - + let placeAndIsApproximate = giftAuctionState.getPlace(myBid: myBidAmount, myBidDate: myBidDate) ?? (1, false) + place = placeAndIsApproximate.place + var bidTitle: String var bidTitleColor: UIColor var bidStatus: PeerComponent.Status? @@ -2247,7 +2270,18 @@ private final class GiftAuctionBidScreenComponent: Component { if let peer = self.peersMap[component.context.account.peerId] { myBidTitleComponent = AnyComponent(PeerHeaderComponent(color: bidTitleColor, dateTimeFormat: environment.dateTimeFormat, title: bidTitle, giftTitle: giftTitle, giftNumber: giftNumber)) - myBidComponent = AnyComponent(PeerComponent(context: component.context, theme: environment.theme, groupingSeparator: environment.dateTimeFormat.groupingSeparator, peer: peer, place: place, amount: myBidAmount, status: bidStatus, isLast: true, action: nil)) + myBidComponent = AnyComponent(PeerComponent( + context: component.context, + theme: environment.theme, + groupingSeparator: environment.dateTimeFormat.groupingSeparator, + peer: peer, + place: place, + placeIsApproximate: placeAndIsApproximate.isApproximate, + amount: myBidAmount, + status: bidStatus, + isLast: true, + action: nil + )) } var i: Int32 = 1 @@ -2259,7 +2293,17 @@ private final class GiftAuctionBidScreenComponent: Component { break } } - topBidsComponents.append((peer.id, AnyComponent(PeerComponent(context: component.context, theme: environment.theme, groupingSeparator: environment.dateTimeFormat.groupingSeparator, peer: peer, place: i, amount: bid, isLast: i == topBidders.count, action: nil)))) + topBidsComponents.append((peer.id, AnyComponent(PeerComponent( + context: component.context, + theme: environment.theme, + groupingSeparator: environment.dateTimeFormat.groupingSeparator, + peer: peer, + place: i, + placeIsApproximate: false, + amount: bid, + isLast: i == topBidders.count, + action: nil + )))) i += 1 } @@ -2645,7 +2689,7 @@ private final class GiftAuctionBidScreenComponent: Component { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -2678,7 +2722,7 @@ private final class GiftAuctionBidScreenComponent: Component { content: LottieComponent.AppBundleContent( name: "anim_morewide" ), - color: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor, + color: environment.theme.chat.inputPanel.panelControlColor, size: CGSize(width: 34.0, height: 34.0), playOnce: self.moreButtonPlayOnce ) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionCustomBidController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionCustomBidController.swift index d9cf921a..5c7abf83 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionCustomBidController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionCustomBidController.swift @@ -8,347 +8,287 @@ import TelegramPresentationData import AccountContext import UrlEscaping import ComponentFlow +import AlertComponent import StarsWithdrawalScreen -private final class GiftAuctionCustomBidAlertContentNode: AlertContentNode { - private let theme: PresentationTheme - private let strings: PresentationStrings - private let dateTimeFormat: PresentationDateTimeFormat - private let title: String - private let text: String - private let placeholder: String - private let minValue: Int64 - fileprivate var value: Int64 +func giftAuctionCustomBidController( + context: AccountContext, + title: String, + text: String, + placeholder: String, + action: String, + minValue: Int64, + value: Int64, + apply: @escaping (Int64) -> Void, + cancel: @escaping () -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings - private let titleNode: ASTextNode - private let textNode: ASTextNode - private let backgroundView = UIImageView() - let amountField = ComponentView() - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let disposable = MetaDisposable() - - private var validLayout: CGSize? - - private let hapticFeedback = HapticFeedback() - - var complete: (() -> Void)? { - didSet { - //self.inputFieldNode.complete = self.complete - } - } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, actions: [TextAlertAction], title: String, text: String, placeholder: String, minValue: Int64, value: Int64) { - self.theme = ptheme - self.strings = strings - self.dateTimeFormat = dateTimeFormat - self.title = title - self.text = text - self.placeholder = placeholder - self.minValue = minValue - self.value = value + let inputState = AlertAmountFieldComponent.ExternalState() - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 2 - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 8 - -// self.inputFieldNode = GiftAuctionCustomBidInputFieldNode(theme: ptheme, placeholder: placeholder) -// self.inputFieldNode.text = "\(value)" - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - -// self.addSubnode(self.inputFieldNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - } - - deinit { - self.disposable.dispose() - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) - - let hadValidLayout = self.validLayout != nil - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - let spacing: CGFloat = 5.0 - - let titleSize = self.titleNode.measure(measureSize) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 4.0 - - let textSize = self.textNode.measure(measureSize) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 6.0 + spacing - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) - - var contentWidth = max(titleSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - let fieldWidth = resultWidth - 18.0 - let amountFieldSize = self.amountField.update( - transition: .immediate, - component: AnyComponent( - AmountFieldComponent( - textColor: self.theme.list.itemPrimaryTextColor, - secondaryColor: self.theme.list.itemSecondaryTextColor, - placeholderColor: self.theme.list.itemPlaceholderTextColor, - accentColor: self.theme.list.itemAccentColor, - value: self.value, - minValue: self.minValue, - forceMinValue: false, - allowZero: false, - maxValue: nil, - placeholderText: self.placeholder, - textFieldOffset: CGPoint(x: -4.0, y: -1.0), - labelText: nil, - currency: .stars, - dateTimeFormat: self.dateTimeFormat, - amountUpdated: { [weak self] value in - guard let self else { - return - } - if let value { - self.value = value - } - }, - tag: nil - ) - ), - environment: {}, - containerSize: CGSize(width: fieldWidth, height: 44.0) + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: title) ) - var amountFieldFrame = CGRect(origin: CGPoint(x: floor((resultWidth - fieldWidth) / 2.0), y: origin.y - 2.0), size: amountFieldSize) - if let amountFieldView = self.amountField.view { - if amountFieldView.superview == nil { - amountFieldView.clipsToBounds = true - self.backgroundView.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - - self.view.addSubview(self.backgroundView) - self.view.addSubview(amountFieldView) - } - self.backgroundView.frame = amountFieldFrame.insetBy(dx: 7.0, dy: 9.0) - amountFieldFrame.size.width -= 14.0 - amountFieldView.frame = amountFieldFrame - } - - let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + amountFieldSize.height + actionsHeight + insets.top + insets.bottom + 3.0) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + + var applyImpl: (() -> Void)? + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertAmountFieldComponent( + context: context, + initialValue: value, + minValue: minValue, + maxValue: nil, + placeholder: placeholder, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 + ) + ) + )) + + let alertController = AlertScreen( + context: context, + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel, action: { + cancel() + }), + .init(title: action, type: .default, action: { + applyImpl?() + }, autoDismiss: false) + ] + ) + applyImpl = { + if let value = inputState.value, value >= minValue { + apply(value) + } else { + inputState.resetToMinValue() + inputState.animateError() + } + } + return alertController +} + +private final class AlertAmountFieldComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + public class ExternalState { + public fileprivate(set) var value: Int64? + public fileprivate(set) var animateError: () -> Void = {} + public fileprivate(set) var activateInput: () -> Void = {} + public fileprivate(set) var resetToMinValue: () -> Void = {} + fileprivate let valuePromise = ValuePromise(nil) + public var valueSignal: Signal { + return self.valuePromise.get() } - if !hadValidLayout { + public init() { + } + } + + let context: AccountContext + let initialValue: Int64? + let minValue: Int64? + let maxValue: Int64? + let placeholder: String + let isInitiallyFocused: Bool + let externalState: ExternalState + let returnKeyAction: (() -> Void)? + + public init( + context: AccountContext, + initialValue: Int64? = nil, + minValue: Int64? = nil, + maxValue: Int64? = nil, + placeholder: String, + isInitiallyFocused: Bool = false, + externalState: ExternalState, + returnKeyAction: (() -> Void)? = nil + ) { + self.context = context + self.initialValue = initialValue + self.minValue = minValue + self.maxValue = maxValue + self.placeholder = placeholder + self.isInitiallyFocused = isInitiallyFocused + self.externalState = externalState + self.returnKeyAction = returnKeyAction + } + + public static func ==(lhs: AlertAmountFieldComponent, rhs: AlertAmountFieldComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.initialValue != rhs.initialValue { + return false + } + if lhs.minValue != rhs.minValue { + return false + } + if lhs.maxValue != rhs.maxValue { + return false + } + if lhs.placeholder != rhs.placeholder { + return false + } + if lhs.isInitiallyFocused != rhs.isInitiallyFocused { + return false + } + return true + } + + public final class View: UIView, UITextFieldDelegate { + private let background = ComponentView() + private let amountField = ComponentView() + + private var currentValue: Int64? + + private var component: AlertAmountFieldComponent? + private weak var state: EmptyComponentState? + + private var isUpdating = false + + func activateInput() { if let amountFieldView = self.amountField.view as? AmountFieldComponent.View { amountFieldView.activateInput() - Queue.mainQueue().justDispatch { - amountFieldView.selectAll() - } } } - return resultSize - } - - func animateError() { - if let amountFieldView = self.amountField.view as? AmountFieldComponent.View { - self.value = self.minValue - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - amountFieldView.resetValue() + func resetToMinValue() { + self.currentValue = self.component?.minValue + self.state?.updated() - amountFieldView.animateError() - amountFieldView.selectAll() + if let amountFieldView = self.amountField.view as? AmountFieldComponent.View { + amountFieldView.resetValue() + amountFieldView.selectAll() + } + } + + func animateError() { + if let amountFieldView = self.amountField.view as? AmountFieldComponent.View { + amountFieldView.animateError() + } + } + + func update(component: AlertAmountFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + if self.component == nil { + self.currentValue = component.initialValue + + component.externalState.animateError = { [weak self] in + self?.animateError() + } + component.externalState.activateInput = { [weak self] in + self?.activateInput() + } + component.externalState.resetToMinValue = { [weak self] in + self?.resetToMinValue() + } + } + + let isFirstTime = self.component == nil + + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let topInset: CGFloat = 15.0 + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let amountFieldSize = self.amountField.update( + transition: .immediate, + component: AnyComponent( + AmountFieldComponent( + textColor: environment.theme.actionSheet.primaryTextColor, + secondaryColor: environment.theme.actionSheet.secondaryTextColor, + placeholderColor: environment.theme.actionSheet.inputPlaceholderColor, + accentColor: environment.theme.actionSheet.controlAccentColor, + value: self.currentValue, + minValue: component.minValue, + forceMinValue: false, + allowZero: false, + maxValue: nil, + placeholderText: component.placeholder, + textFieldOffset: CGPoint(x: -4.0, y: -1.0), + labelText: nil, + currency: .stars, + dateTimeFormat: presentationData.dateTimeFormat, + amountUpdated: { [weak self] value in + guard let self else { + return + } + self.currentValue = value + component.externalState.value = value + component.externalState.valuePromise.set(value) + }, + tag: nil + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 44.0) + ) + var amountFieldFrame = CGRect(origin: CGPoint(x: -16.0, y: topInset - 1.0 + UIScreenPixel), size: amountFieldSize) + if let amountFieldView = self.amountField.view { + if amountFieldView.superview == nil { + amountFieldView.clipsToBounds = true + self.addSubview(amountFieldView) + } + amountFieldFrame.size.width -= 14.0 + amountFieldView.frame = amountFieldFrame + } + + let backgroundPadding: CGFloat = 14.0 + let size = CGSize(width: availableSize.width, height: 50.0) + + let backgroundSize = self.background.update( + transition: transition, + component: AnyComponent( + FilledRoundedRectangleComponent(color: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1), cornerRadius: .value(25.0), smoothCorners: false) + ), + environment: {}, + containerSize: CGSize(width: size.width + backgroundPadding * 2.0, height: size.height) + ) + let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - backgroundSize.width) / 2.0), y: topInset ), size: backgroundSize) + if let backgroundView = self.background.view { + if backgroundView.superview == nil { + self.addSubview(backgroundView) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + + if isFirstTime && component.isInitiallyFocused { + self.activateInput() + } + + return CGSize(width: availableSize.width, height: size.height + topInset) } } - func deactivateInput() { - if let amountFieldView = self.amountField.view as? AmountFieldComponent.View { - amountFieldView.deactivateInput() - } + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } - -func giftAuctionCustomBidController(context: AccountContext, title: String, text: String, placeholder: String, action: String, minValue: Int64, value: Int64, apply: @escaping (Int64) -> Void, cancel: @escaping () -> Void) -> AlertController { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - var dismissImpl: ((Bool) -> Void)? - var applyImpl: (() -> Void)? - - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - cancel() - }), TextAlertAction(type: .defaultAction, title: action, action: { - applyImpl?() - })] - - let contentNode = GiftAuctionCustomBidAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, actions: actions, title: title, text: text, placeholder: placeholder, minValue: minValue, value: value) - contentNode.complete = { - applyImpl?() - } - applyImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - let value = contentNode.value - if value < minValue { - contentNode.animateError() - } else { - dismissImpl?(true) - apply(contentNode.value) - } - } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - }) - controller.dismissed = { byTapOutside in - presentationDataDisposable.dispose() - if byTapOutside { - cancel() - } - } - dismissImpl = { [weak controller, weak contentNode] animated in - contentNode?.deactivateInput() - let _ = contentNode - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller -} diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift index e4086acc..b4e39c8b 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift @@ -256,7 +256,7 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { [weak state] _ in @@ -296,8 +296,7 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { style: .glass, color: theme.list.itemCheckColors.fillColor, foreground: theme.list.itemCheckColors.foregroundColor, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) ), content: AnyComponentWithIdentity( id: AnyHashable(0), diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift index 04a2bb12..9f6a5f72 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift @@ -28,23 +28,27 @@ import ButtonComponent import UndoUI import LottieComponent import AnimatedTextComponent +import TableComponent private final class GiftAuctionViewSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext let auctionContext: GiftAuctionContext + let peerId: EnginePeer.Id? let animateOut: ActionSlot> let getController: () -> ViewController? init( context: AccountContext, auctionContext: GiftAuctionContext, + peerId: EnginePeer.Id?, animateOut: ActionSlot>, getController: @escaping () -> ViewController? ) { self.context = context self.auctionContext = auctionContext + self.peerId = peerId self.animateOut = animateOut self.getController = getController } @@ -61,6 +65,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { private let context: AccountContext private let auctionContext: GiftAuctionContext + private let peerId: EnginePeer.Id? private let animateOut: ActionSlot> private let getController: () -> ViewController? @@ -92,11 +97,13 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { init( context: AccountContext, auctionContext: GiftAuctionContext, + peerId: EnginePeer.Id?, animateOut: ActionSlot>, getController: @escaping () -> ViewController? ) { self.context = context self.auctionContext = auctionContext + self.peerId = peerId self.animateOut = animateOut self.getController = getController @@ -398,7 +405,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { } let storeController = self.context.sharedContext.makeGiftStoreController( context: self.context, - peerId: self.context.account.peerId, + peerId: self.peerId ?? self.context.account.peerId, gift: gift ) controller.push(storeController) @@ -472,7 +479,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { } func makeState() -> State { - return State(context: self.context, auctionContext: self.auctionContext, animateOut: self.animateOut, getController: self.getController) + return State(context: self.context, auctionContext: self.auctionContext, peerId: self.peerId, animateOut: self.animateOut, getController: self.getController) } static var body: Body { @@ -1075,7 +1082,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { guard let state, let attributes = state.giftUpgradeAttributes else { return } - let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsPreviewScreen(context: component.context, gift: .generic(gift), attributes: attributes) + let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsScreen(context: component.context, gift: .generic(gift), attributes: attributes, selectedAttributes: nil, focusedAttribute: nil) environment.controller()?.push(variantsController) }, animateScale: false), availableSize: CGSize(width: context.availableSize.width - 64.0, height: context.availableSize.height), @@ -1276,13 +1283,16 @@ final class GiftAuctionViewSheetComponent: CombinedComponent { let context: AccountContext let auctionContext: GiftAuctionContext + let peerId: EnginePeer.Id? init( context: AccountContext, - auctionContext: GiftAuctionContext + auctionContext: GiftAuctionContext, + peerId: EnginePeer.Id? ) { self.context = context self.auctionContext = auctionContext + self.peerId = peerId } static func ==(lhs: GiftAuctionViewSheetComponent, rhs: GiftAuctionViewSheetComponent) -> Bool { @@ -1307,6 +1317,7 @@ final class GiftAuctionViewSheetComponent: CombinedComponent { content: AnyComponent(GiftAuctionViewSheetContent( context: context.component.context, auctionContext: context.component.auctionContext, + peerId: context.component.peerId, animateOut: animateOut, getController: controller )), @@ -1391,6 +1402,7 @@ public final class GiftAuctionViewScreen: ViewControllerComponentContainer { public init( context: AccountContext, auctionContext: GiftAuctionContext, + peerId: EnginePeer.Id?, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void ) { self.completion = completion @@ -1399,7 +1411,8 @@ public final class GiftAuctionViewScreen: ViewControllerComponentContainer { context: context, component: GiftAuctionViewSheetComponent( context: context, - auctionContext: auctionContext + auctionContext: auctionContext, + peerId: peerId ), navigationBarAppearance: .none, statusBarStyle: .ignore, diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionWearPreviewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionWearPreviewScreen.swift index 26014f36..8198689c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionWearPreviewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionWearPreviewScreen.swift @@ -26,6 +26,7 @@ import GiftAnimationComponent import GlassBarButtonComponent import GiftRemainingCountComponent import AnimatedTextComponent +import AvatarComponent private final class GiftAuctionWearPreviewSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -199,7 +200,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent { return { context in let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let component = context.component - let theme = environment.theme + let theme = environment.theme.withModalBlocksBackground() let strings = environment.strings let nameDisplayOrder = component.context.sharedContext.currentPresentationData.with { $0 }.nameDisplayOrder let controller = environment.controller diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift index 7f9a368d..7b7409f4 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift @@ -3,478 +3,32 @@ import UIKit import AsyncDisplayKit import Display import ComponentFlow +import SwiftSignalKit import Postbox import TelegramCore import TelegramPresentationData -import TelegramUIPreferences import AccountContext import AppBundle -import AvatarNode -import Markdown import GiftItemComponent -import ChatMessagePaymentAlertController -import ActivityIndicator import TooltipUI import MultilineTextComponent -import BalancedTextComponent +import BundleIconComponent import TelegramStringFormatting - -private final class GiftOfferAlertContentNode: AlertContentNode { - private let context: AccountContext - private let strings: PresentationStrings - private var presentationTheme: PresentationTheme - private let title: String - private let text: String - private let amount: CurrencyAmount - private let gift: StarGift.UniqueGift - - private let titleNode: ASTextNode - private let giftView = ComponentView() - private let textNode: ASTextNode - private let arrowNode: ASImageNode - private let avatarNode: AvatarNode - private let tableView = ComponentView() - private let valueDelta = ComponentView() - - private let modelButtonTag = GenericComponentViewTag() - private let backdropButtonTag = GenericComponentViewTag() - private let symbolButtonTag = GenericComponentViewTag() - - fileprivate var getController: () -> ViewController? = { return nil} - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var activityIndicator: ActivityIndicator? - - private var validLayout: CGSize? - - var inProgress = false { - didSet { - if let size = self.validLayout { - let _ = self.updateLayout(size: size, transition: .immediate) - } - } - } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init( - context: AccountContext, - theme: AlertControllerTheme, - ptheme: PresentationTheme, - strings: PresentationStrings, - gift: StarGift.UniqueGift, - peer: EnginePeer, - title: String, - text: String, - amount: CurrencyAmount, - actions: [TextAlertAction] - ) { - self.context = context - self.strings = strings - self.presentationTheme = ptheme - self.title = title - self.text = text - self.amount = amount - self.gift = gift - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 0 - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.arrowNode = ASImageNode() - self.arrowNode.displaysAsynchronously = false - self.arrowNode.displayWithoutProcessing = true - - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - self.addSubnode(self.arrowNode) - self.addSubnode(self.avatarNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - - self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor) - self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - linkAttribute: { url in - return ("URL", url) - } - ), textAlignment: .center) - self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor.withAlphaComponent(0.9)) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - fileprivate func dismissAllTooltips() { - guard let controller = self.getController() else { - return - } - controller.window?.forEachController({ controller in - if let controller = controller as? TooltipScreen { - controller.dismiss(inPlace: false) - } - }) - controller.forEachController({ controller in - if let controller = controller as? TooltipScreen { - controller.dismiss(inPlace: false) - } - return true - }) - } - - func showAttributeInfo(tag: Any, text: String) { - guard let controller = self.getController() else { - return - } - self.dismissAllTooltips() - - guard let sourceView = self.tableView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: controller.view) else { - return - } - - let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize()) - let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in - return .dismiss(consume: false) - }) - controller.present(tooltipController, in: .current) - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 310.0) - - let strings = self.strings - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let avatarSize = CGSize(width: 60.0, height: 60.0) - self.avatarNode.updateSize(size: avatarSize) - - let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize) - - let _ = self.giftView.update( - transition: .immediate, - component: AnyComponent( - GiftItemComponent( - context: self.context, - theme: self.presentationTheme, - strings: strings, - peer: nil, - subject: .uniqueGift(gift: self.gift, price: nil), - mode: .thumbnail - ) - ), - environment: {}, - containerSize: avatarSize - ) - if let view = self.giftView.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = giftFrame - } - - if let arrowImage = self.arrowNode.image { - let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) - transition.updateFrame(node: self.arrowNode, frame: arrowFrame) - } - - let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize) - transition.updateFrame(node: self.avatarNode, frame: avatarFrame) - - origin.y += avatarSize.height + 17.0 - - let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 5.0 - - let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 10.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - - let tableFont = Font.regular(15.0) - let tableTextColor = self.presentationTheme.list.itemPrimaryTextColor - - var tableItems: [TableComponent.Item] = [] - let order: [StarGift.UniqueGift.Attribute.AttributeType] = [ - .model, .pattern, .backdrop, .originalInfo - ] - - var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:] - for attribute in self.gift.attributes { - attributeMap[attribute.attributeType] = attribute - } - - for type in order { - if let attribute = attributeMap[type] { - let id: String? - let title: String? - let value: NSAttributedString - let percentage: Float? - let tag: AnyObject? - - switch attribute { - case let .model(name, _, rarity): - id = "model" - title = strings.Gift_Unique_Model - value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) - percentage = Float(rarity) * 0.1 - tag = self.modelButtonTag - case let .backdrop(name, _, _, _, _, _, rarity): - id = "backdrop" - title = strings.Gift_Unique_Backdrop - value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) - percentage = Float(rarity) * 0.1 - tag = self.backdropButtonTag - case let .pattern(name, _, rarity): - id = "pattern" - title = strings.Gift_Unique_Symbol - value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) - percentage = Float(rarity) * 0.1 - tag = self.symbolButtonTag - case .originalInfo: - continue - } - - var items: [AnyComponentWithIdentity] = [] - items.append( - AnyComponentWithIdentity( - id: AnyHashable(0), - component: AnyComponent( - MultilineTextComponent(text: .plain(value)) - ) - ) - ) - if let percentage, let tag { - items.append(AnyComponentWithIdentity( - id: AnyHashable(1), - component: AnyComponent(Button( - content: AnyComponent(ButtonContentComponent( - context: self.context, - text: formatPercentage(percentage), - color: self.presentationTheme.list.itemAccentColor - )), - action: { [weak self] in - self?.showAttributeInfo(tag: tag, text: strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string) - } - ).tagged(tag)) - )) - } - let itemComponent = AnyComponent( - HStack(items, spacing: 4.0) - ) - - tableItems.append(.init( - id: id, - title: title, - hasBackground: false, - component: itemComponent - )) - } - } - - let tableSize = self.tableView.update( - transition: .immediate, - component: AnyComponent( - TableComponent( - theme: self.presentationTheme, - items: tableItems, - semiTransparent: true - ) - ), - environment: {}, - containerSize: CGSize(width: contentWidth - 32.0, height: size.height) - ) - let tableFrame = CGRect(origin: CGPoint(x: 16.0, y: avatarSize.height + titleSize.height + textSize.height + 60.0), size: tableSize) - if let view = self.tableView.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = tableFrame - } - - var valueDeltaHeight: CGFloat = 0.0 - if let valueAmount = self.gift.valueUsdAmount { - let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) - - let usdRate: Double - switch self.amount.currency { - case .stars: - usdRate = Double(resaleConfiguration.usdWithdrawRate) / 1000.0 / 100.0 - case .ton: - usdRate = Double(resaleConfiguration.tonUsdRate) / 1000.0 / 1000000.0 - } - let offerUsdValue = Double(self.amount.amount.value) * usdRate - let giftUsdValue = Double(valueAmount) / 100.0 - - let fraction = giftUsdValue / offerUsdValue - let percentage = Int(fraction * 100) - 100 - - if percentage > 20 { - let textColor = self.presentationTheme.list.itemDestructiveColor - let markdownAttributes = MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: textColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: textColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: textColor), - linkAttribute: { url in - return ("URL", url) - } - ) - let valueDeltaSize = self.valueDelta.update( - transition: .immediate, - component: AnyComponent( - BalancedTextComponent( - text: .markdown(text: strings.Chat_GiftPurchaseOffer_AcceptConfirmation_BadValue("\(percentage)%").string, attributes: markdownAttributes), - horizontalAlignment: .center, - maximumNumberOfLines: 0 - ) - ), - environment: {}, - containerSize: CGSize(width: contentWidth - 32.0, height: size.height) - ) - let valueDeltaFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - valueDeltaSize.width) / 2.0), y: avatarSize.height + titleSize.height + textSize.height + 73.0 + tableSize.height), size: valueDeltaSize) - if let view = self.valueDelta.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = valueDeltaFrame - } - valueDeltaHeight += valueDeltaSize.height + 10.0 - } - } - - let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + tableSize.height + actionsHeight + valueDeltaHeight + 40.0 + insets.top + insets.bottom) - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - do { - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - do { - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - do { - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if self.inProgress { - let activityIndicator: ActivityIndicator - if let current = self.activityIndicator { - activityIndicator = current - } else { - activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false)) - self.addSubnode(activityIndicator) - } - - if let actionNode = self.actionNodes.first { - actionNode.isUserInteractionEnabled = false - actionNode.isHidden = false - - let indicatorSize = CGSize(width: 22.0, height: 22.0) - transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize)) - } - } - - return resultSize - } -} +import AlertComponent +import TableComponent +import AvatarComponent +import AlertTransferHeaderComponent +import AlertTableComponent public func giftOfferAlertController( context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)?, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void -) -> AlertController { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } +) -> ViewController { + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings let title = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Title @@ -485,7 +39,7 @@ public func giftOfferAlertController( case .stars: priceString = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text_Stars(Int32(clamping: amount.amount.value)) case .ton: - priceString = "\(amount.amount) TON" + priceString = formatTonAmountText(amount.amount.value, dateTimeFormat: presentationData.dateTimeFormat) + " TON" } let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) @@ -502,27 +56,205 @@ public func giftOfferAlertController( let giftTitle = "\(gift.title) #\(formatCollectibleNumber(gift.number, dateTimeFormat: presentationData.dateTimeFormat))" let text = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text(giftTitle, peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), priceString, finalPriceString).string - var contentNode: GiftOfferAlertContentNode? - var dismissImpl: ((Bool) -> Void)? - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { [weak contentNode] in - contentNode?.inProgress = true - commit() - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - })] + let tableFont = Font.regular(15.0) + let tableTextColor = presentationData.theme.list.itemPrimaryTextColor - contentNode = GiftOfferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, amount: amount, actions: actions) + let modelButtonTag = GenericComponentViewTag() + let backdropButtonTag = GenericComponentViewTag() + let symbolButtonTag = GenericComponentViewTag() + var showAttributeInfoImpl: ((Any, String) -> Void)? - let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: nil, chatPeerId: context.account.peerId, showBalance: false) - contentNode?.getController = { [weak controller] in - return controller + var tableItems: [TableComponent.Item] = [] + let order: [StarGift.UniqueGift.Attribute.AttributeType] = [ + .model, .pattern, .backdrop, .originalInfo + ] + + var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:] + for attribute in gift.attributes { + attributeMap[attribute.attributeType] = attribute } - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() + + for type in order { + if let attribute = attributeMap[type] { + let id: String? + let title: String? + let value: NSAttributedString + let percentage: Float? + let tag: AnyObject? + + switch attribute { + case let .model(name, _, rarity): + id = "model" + title = strings.Gift_Unique_Model + value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) + percentage = Float(rarity) * 0.1 + tag = modelButtonTag + case let .backdrop(name, _, _, _, _, _, rarity): + id = "backdrop" + title = strings.Gift_Unique_Backdrop + value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) + percentage = Float(rarity) * 0.1 + tag = backdropButtonTag + case let .pattern(name, _, rarity): + id = "pattern" + title = strings.Gift_Unique_Symbol + value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) + percentage = Float(rarity) * 0.1 + tag = symbolButtonTag + case .originalInfo: + continue + } + + var items: [AnyComponentWithIdentity] = [] + items.append( + AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent( + MultilineTextComponent(text: .plain(value)) + ) + ) + ) + if let percentage, let tag { + items.append(AnyComponentWithIdentity( + id: AnyHashable(1), + component: AnyComponent(Button( + content: AnyComponent(ButtonContentComponent( + context: context, + text: formatPercentage(percentage), + color: presentationData.theme.list.itemAccentColor + )), + action: { + showAttributeInfoImpl?(tag, strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string) + } + ).tagged(tag)) + )) + } + let itemComponent = AnyComponent( + HStack(items, spacing: 4.0) + ) + + tableItems.append(.init( + id: id, + title: title, + hasBackground: false, + component: itemComponent + )) } } - return controller + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertTransferHeaderComponent( + fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent( + GiftItemComponent( + context: context, + theme: presentationData.theme, + strings: strings, + peer: nil, + subject: .uniqueGift(gift: gift, price: nil), + mode: .thumbnail + ) + )), + toComponent: AnyComponentWithIdentity(id: "avatar", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: peer + ) + )), + type: .transfer + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + content.append(AnyComponentWithIdentity( + id: "table", + component: AnyComponent( + AlertTableComponent(items: tableItems) + ) + )) + + if let valueAmount = gift.valueUsdAmount { + let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + + let usdRate: Double + switch amount.currency { + case .stars: + usdRate = Double(resaleConfiguration.usdWithdrawRate) / 1000.0 / 100.0 + case .ton: + usdRate = Double(resaleConfiguration.tonUsdRate) / 1000.0 / 1000000.0 + } + let offerUsdValue = Double(amount.amount.value) * usdRate + let giftUsdValue = Double(valueAmount) / 100.0 + + let fraction = giftUsdValue / offerUsdValue + let percentage = Int(fraction * 100) - 100 + + if percentage > 20 { + let warningText = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_BadValue("\(percentage)%").string + content.append(AnyComponentWithIdentity( + id: "warning", + component: AnyComponent( + AlertTextComponent(content: .plain(warningText), color: .destructive, style: .plain(.small)) + ) + )) + } + } + + let updatedPresentationDataSignal = updatedPresentationData?.signal ?? context.sharedContext.presentationData + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false), + content: content, + actions: [ + .init(title: buttonText, type: .default, action: { + commit() + }), + .init(title: strings.Common_Cancel) + ], + updatedPresentationData: (initial: presentationData, signal: updatedPresentationDataSignal) + ) + + var dismissAllTooltipsImpl: (() -> Void)? + showAttributeInfoImpl = { [weak alertController] tag, text in + dismissAllTooltipsImpl?() + guard let alertController, let sourceView = alertController.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: alertController.view) else { + return + } + + let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize()) + let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in + return .dismiss(consume: false) + }) + alertController.present(tooltipController, in: .current) + } + dismissAllTooltipsImpl = { [weak alertController] in + guard let alertController else { + return + } + alertController.window?.forEachController({ controller in + if let controller = controller as? TooltipScreen { + controller.dismiss(inPlace: false) + } + }) + alertController.forEachController({ controller in + if let controller = controller as? TooltipScreen { + controller.dismiss(inPlace: false) + } + return true + }) + } + return alertController } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftPurchaseAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftPurchaseAlertController.swift index 805bf3bf..d2cc73af 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftPurchaseAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftPurchaseAlertController.swift @@ -11,440 +11,16 @@ import TelegramUIPreferences import AccountContext import AppBundle import AvatarNode -import Markdown import GiftItemComponent import ChatMessagePaymentAlertController -import ActivityIndicator import TabSelectorComponent import BundleIconComponent import MultilineTextComponent import TelegramStringFormatting import TooltipUI - -private final class GiftPurchaseAlertContentNode: AlertContentNode { - private let context: AccountContext - private let strings: PresentationStrings - private var presentationTheme: PresentationTheme - private let gift: StarGift.UniqueGift - private let peer: EnginePeer - - fileprivate var currency: CurrencyAmount.Currency - - fileprivate let header = ComponentView() - private let title = ComponentView() - private let text = ComponentView() - private let giftView = ComponentView() - private let arrow = ComponentView() - private let avatarNode: AvatarNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var activityIndicator: ActivityIndicator? - - private var validLayout: CGSize? - - var inProgress = false { - didSet { - if let size = self.validLayout { - let _ = self.updateLayout(size: size, transition: .immediate) - } - } - } - - var updatedCurrency: (CurrencyAmount.Currency) -> Void = { _ in } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init( - context: AccountContext, - theme: AlertControllerTheme, - presentationTheme: PresentationTheme, - strings: PresentationStrings, - gift: StarGift.UniqueGift, - peer: EnginePeer, - actions: [TextAlertAction] - ) { - self.context = context - self.strings = strings - self.presentationTheme = presentationTheme - self.gift = gift - self.peer = peer - - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - self.currency = self.gift.resellForTonOnly ? .ton : .stars - - super.init() - - self.addSubnode(self.avatarNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - - self.avatarNode.setPeer(context: context, theme: presentationTheme, peer: peer) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - func requestUpdate(transition: ContainedViewLayoutTransition) { - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: transition) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let containerSize = size - var size = size - size.width = min(size.width, 270.0) - - var origin = CGPoint(x: 0.0, y: 20.0) - if self.gift.resellForTonOnly { - let headerSize = self.header.update( - transition: .immediate, - component: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString(string: self.strings.Gift_Buy_AcceptsTonOnly, font: Font.regular(13.0), textColor: self.presentationTheme.actionSheet.secondaryTextColor)), - horizontalAlignment: .center, - maximumNumberOfLines: 2 - ) - ), - environment: {}, - containerSize: CGSize(width: size.width - 32.0, height: size.height) - ) - - let headerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - headerSize.width) / 2.0), y: origin.y), size: headerSize) - if let view = self.header.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = headerFrame - } - origin.y += headerSize.height + 17.0 - } else { - origin.y -= 4.0 - - let headerSize = self.header.update( - transition: ComponentTransition(transition), - component: AnyComponent(TabSelectorComponent( - colors: TabSelectorComponent.Colors( - foreground: self.presentationTheme.list.itemSecondaryTextColor, - selection: self.presentationTheme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15), - simple: true - ), - theme: self.presentationTheme, - customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(14.0), - spacing: 10.0 - ), - items: [ - TabSelectorComponent.Item( - id: AnyHashable(0), - content: .text(self.strings.Gift_Buy_PayInStars) - ), - TabSelectorComponent.Item( - id: AnyHashable(1), - content: .text(self.strings.Gift_Buy_PayInTon) - ) - ], - selectedId: self.currency == .ton ? AnyHashable(1) : AnyHashable(0), - setSelectedId: { [weak self] id in - guard let self else { - return - } - let currency: CurrencyAmount.Currency - if id == AnyHashable(0) { - currency = .stars - } else { - currency = .ton - } - if self.currency != currency { - self.currency = currency - self.updatedCurrency(currency) - self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring)) - } - } - )), - environment: {}, - containerSize: CGSize(width: containerSize.width - 16.0 * 2.0, height: 100.0) - ) - - size.width = min(containerSize.width, max(270.0, headerSize.width + 32.0)) - - let headerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - headerSize.width) / 2.0), y: origin.y), size: headerSize) - if let view = self.header.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = headerFrame - } - origin.y += headerSize.height + 17.0 - } - - self.validLayout = size - - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - - var resellPrice: CurrencyAmount? - if let actionNode = self.actionNodes.first { - switch self.currency { - case .stars: - if let resellAmount = self.gift.resellAmounts?.first(where: { $0.currency == .stars }) { - resellPrice = resellAmount - actionNode.action = TextAlertAction(type: .defaultAction, title: self.strings.Gift_Buy_Confirm_BuyFor(Int32(resellAmount.amount.value)), action: actionNode.action.action) - } - case .ton: - if let resellAmount = self.gift.resellAmounts?.first(where: { $0.currency == .ton }) { - resellPrice = resellAmount - let valueString = formatTonAmountText(resellAmount.amount.value, dateTimeFormat: presentationData.dateTimeFormat) - actionNode.action = TextAlertAction(type: .defaultAction, title: self.strings.Gift_Buy_Confirm_BuyForTon(valueString).string, action: actionNode.action.action) - } - } - } - - let avatarSize = CGSize(width: 60.0, height: 60.0) - self.avatarNode.updateSize(size: avatarSize) - - let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize) - - let _ = self.giftView.update( - transition: .immediate, - component: AnyComponent( - GiftItemComponent( - context: self.context, - theme: self.presentationTheme, - strings: self.strings, - peer: nil, - subject: .uniqueGift(gift: self.gift, price: nil), - mode: .thumbnail - ) - ), - environment: {}, - containerSize: avatarSize - ) - if let view = self.giftView.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = giftFrame - } - - let arrowSize = self.arrow.update( - transition: .immediate, - component: AnyComponent(BundleIconComponent(name: "Peer Info/AlertArrow", tintColor: self.presentationTheme.actionSheet.secondaryTextColor)), - environment: {}, - containerSize: size - ) - let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowSize.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowSize.height) / 2.0)), size: arrowSize) - if let view = self.arrow.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = arrowFrame - } - - let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize) - transition.updateFrame(node: self.avatarNode, frame: avatarFrame) - origin.y += avatarSize.height + 17.0 - - let titleSize = self.title.update( - transition: .immediate, - component: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString(string: self.strings.Gift_Buy_Confirm_Title, font: Font.semibold(17.0), textColor: self.presentationTheme.actionSheet.primaryTextColor)), - horizontalAlignment: .center - ) - ), - environment: { - }, - containerSize: CGSize(width: size.width - 32.0, height: size.height) - ) - let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize) - if let view = self.title.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = titleFrame - } - origin.y += titleSize.height + 5.0 - - let giftTitle = "\(self.gift.title) #\(presentationStringsFormattedNumber(self.gift.number, presentationData.dateTimeFormat.groupingSeparator))" - - let priceString: String - if let resellPrice { - switch resellPrice.currency { - case .stars: - priceString = self.strings.Gift_Buy_Confirm_Text_Stars(Int32(clamping: resellPrice.amount.value)) - case .ton: - priceString = "**\(formatTonAmountText(resellPrice.amount.value, dateTimeFormat: presentationData.dateTimeFormat)) TON**" - } - } else { - priceString = "" - } - - let text: String - if self.peer.id == self.context.account.peerId { - text = self.strings.Gift_Buy_Confirm_Text(giftTitle, priceString).string - } else { - text = self.strings.Gift_Buy_Confirm_GiftText(giftTitle, priceString, self.peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string - } - - let textSize = self.text.update( - transition: .immediate, - component: AnyComponent( - MultilineTextComponent( - text: .markdown(text: text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: self.presentationTheme.actionSheet.primaryTextColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: self.presentationTheme.actionSheet.primaryTextColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: self.presentationTheme.actionSheet.primaryTextColor), - linkAttribute: { url in - return ("URL", url) - } - )), - horizontalAlignment: .center, - maximumNumberOfLines: 0 - ) - ), - environment: { - }, - containerSize: CGSize(width: size.width - 32.0, height: size.height) - ) - let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize) - if let view = self.text.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = textFrame - } - origin.y += textSize.height + 10.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - - let resultSize = CGSize(width: contentWidth, height: origin.y + actionsHeight - 26.0 + insets.top + insets.bottom) - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - //let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - /*switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical:*/ - do { - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - /*switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical:*/ - do { - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - /*switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical:*/ - do { - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if self.inProgress { - let activityIndicator: ActivityIndicator - if let current = self.activityIndicator { - activityIndicator = current - } else { - activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false)) - self.addSubnode(activityIndicator) - } - - if let actionNode = self.actionNodes.first { - actionNode.isUserInteractionEnabled = false - actionNode.isHidden = false - - let indicatorSize = CGSize(width: 22.0, height: 22.0) - transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize)) - } - } - - return resultSize - } -} +import AlertComponent +import AlertTransferHeaderComponent +import AvatarComponent public func giftPurchaseAlertController( context: AccountContext, @@ -454,68 +30,256 @@ public func giftPurchaseAlertController( navigationController: NavigationController?, commit: @escaping (CurrencyAmount.Currency) -> Void, dismissed: @escaping () -> Void -) -> AlertController { +) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings + + let currencyPromise = ValuePromise(.stars) + if gift.resellForTonOnly { + currencyPromise.set(.ton) + } + + let contentSignal = currencyPromise.get() + |> map { currency in + var content: [AnyComponentWithIdentity] = [] + if gift.resellForTonOnly { + content.append(AnyComponentWithIdentity( + id: "tonOnly", + component: AnyComponent( + AlertTextComponent( + content: .plain(strings.Gift_Buy_AcceptsTonOnly), + alignment: .center, + color: .secondary, + style: .plain(.small), + insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 8.0, right: 0.0) + ) + ) + )) + } else { + content.append(AnyComponentWithIdentity( + id: "currency", + component: AnyComponent( + AlertCurrencyComponent( + currency: currency, + updatedCurrency: { currency in + currencyPromise.set(currency) + } + ) + ) + )) + } + + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertTransferHeaderComponent( + fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent( + GiftItemComponent( + context: context, + theme: presentationData.theme, + strings: strings, + peer: nil, + subject: .uniqueGift(gift: gift, price: nil), + mode: .thumbnail + ) + )), + toComponent: AnyComponentWithIdentity(id: "avatar", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: peer + ) + )), + type: .transfer + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Gift_Buy_Confirm_Title) + ) + )) - var contentNode: GiftPurchaseAlertContentNode? - var dismissImpl: ((Bool) -> Void)? - var commitImpl: (() -> Void)? - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: "", action: { - commitImpl?() - dismissImpl?(true) - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - })] + let giftTitle = "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))" + var priceString = "" + switch currency { + case .stars: + if let resellAmount = gift.resellAmounts?.first(where: { $0.currency == .stars }) { + priceString = strings.Gift_Buy_Confirm_Text_Stars(Int32(clamping: resellAmount.amount.value)) + } + case .ton: + if let resellAmount = gift.resellAmounts?.first(where: { $0.currency == .ton }) { + priceString = "**\(formatTonAmountText(resellAmount.amount.value, dateTimeFormat: presentationData.dateTimeFormat)) TON**" + } + } - contentNode = GiftPurchaseAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), presentationTheme: presentationData.theme, strings: strings, gift: gift, peer: peer, actions: actions) + let text: String + if peer.id == context.account.peerId { + text = strings.Gift_Buy_Confirm_Text(giftTitle, priceString).string + } else { + text = strings.Gift_Buy_Confirm_GiftText(giftTitle, priceString, peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string + } + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + return content + } - let controller = ChatMessagePaymentAlertController( + let actionsSignal = currencyPromise.get() + |> map { currency in + var actions: [AlertScreen.Action] = [] + var buyString = "" + switch currency { + case .stars: + if let resellAmount = gift.resellAmounts?.first(where: { $0.currency == .stars }) { + buyString = strings.Gift_Buy_Confirm_BuyFor(Int32(resellAmount.amount.value)) + } + case .ton: + if let resellAmount = gift.resellAmounts?.first(where: { $0.currency == .ton }) { + buyString = strings.Gift_Buy_Confirm_BuyForTon(formatTonAmountText(resellAmount.amount.value, dateTimeFormat: presentationData.dateTimeFormat)).string + } + } + actions.append(.init(id: "buy", title: buyString, type: .default, action: { + commit(currency) + })) + actions.append(.init(title: strings.Common_Cancel)) + return actions + } + + let alertController = ChatMessagePaymentAlertController( context: context, presentationData: presentationData, - contentNode: contentNode!, + updatedPresentationData: (presentationData, context.sharedContext.presentationData), + configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false), + contentSignal: contentSignal, + actionsSignal: actionsSignal, navigationController: navigationController, chatPeerId: context.account.peerId, showBalance: true, - currency: gift.resellForTonOnly ? .ton : .stars, + currencySignal: currencyPromise.get(), animateBalanceOverlay: animateBalanceOverlay ) - controller.dismissed = { _ in + alertController.dismissed = { _ in dismissed() } - - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - commitImpl = { [weak contentNode] in - contentNode?.inProgress = true - commit(contentNode?.currency ?? .stars) - } + +// if !gift.resellForTonOnly { +// Queue.mainQueue().after(0.3) { +// if let headerView = contentNode?.header.view as? TabSelectorComponent.View { +// let absoluteFrame = headerView.convert(headerView.bounds, to: nil) +// var originX = absoluteFrame.width * 0.75 +// if let itemFrame = headerView.frameForItem(AnyHashable(1)) { +// originX = itemFrame.midX +// } +// let location = CGRect(origin: CGPoint(x: absoluteFrame.minX + floor(originX), y: absoluteFrame.minY - 8.0), size: CGSize()) +// let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: presentationData.strings.Gift_Buy_PayInTon_Tooltip), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in +// return .dismiss(consume: false) +// }) +// controller.present(tooltipController, in: .window(.root)) +// } +// } +// } - contentNode?.updatedCurrency = { [weak controller] currency in - controller?.currency = currency - } - - if !gift.resellForTonOnly { - Queue.mainQueue().after(0.3) { - if let headerView = contentNode?.header.view as? TabSelectorComponent.View { - let absoluteFrame = headerView.convert(headerView.bounds, to: nil) - var originX = absoluteFrame.width * 0.75 - if let itemFrame = headerView.frameForItem(AnyHashable(1)) { - originX = itemFrame.midX - } - let location = CGRect(origin: CGPoint(x: absoluteFrame.minX + floor(originX), y: absoluteFrame.minY - 8.0), size: CGSize()) - let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: presentationData.strings.Gift_Buy_PayInTon_Tooltip), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in - return .dismiss(consume: false) - }) - controller.present(tooltipController, in: .window(.root)) - } - } - } - - return controller + return alertController +} + +private final class AlertCurrencyComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + let currency: CurrencyAmount.Currency + let updatedCurrency: (CurrencyAmount.Currency) -> Void + + public init( + currency: CurrencyAmount.Currency, + updatedCurrency: @escaping (CurrencyAmount.Currency) -> Void + ) { + self.currency = currency + self.updatedCurrency = updatedCurrency + } + + public static func ==(lhs: AlertCurrencyComponent, rhs: AlertCurrencyComponent) -> Bool { + if lhs.currency != rhs.currency { + return false + } + return true + } + + final class View: UIView { + private let header = ComponentView() + + private var component: AlertCurrencyComponent? + private weak var state: EmptyComponentState? + + func update(component: AlertCurrencyComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let headerSize = self.header.update( + transition: transition, + component: AnyComponent(TabSelectorComponent( + colors: TabSelectorComponent.Colors( + foreground: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.35), + selection: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1), + simple: true + ), + theme: environment.theme, + customLayout: TabSelectorComponent.CustomLayout( + font: Font.medium(14.0), + spacing: 10.0 + ), + items: [ + TabSelectorComponent.Item( + id: AnyHashable(0), + content: .text(environment.strings.Gift_Buy_PayInStars) + ), + TabSelectorComponent.Item( + id: AnyHashable(1), + content: .text(environment.strings.Gift_Buy_PayInTon) + ) + ], + selectedId: component.currency == .ton ? AnyHashable(1) : AnyHashable(0), + setSelectedId: { [weak self] id in + guard let self, let component = self.component else { + return + } + let currency: CurrencyAmount.Currency + if id == AnyHashable(0) { + currency = .stars + } else { + currency = .ton + } + if currency != component.currency { + component.updatedCurrency(currency) + } + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width + 54.0, height: 100.0) + ) + + let headerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - headerSize.width) / 2.0), y: 0.0), size: headerSize) + if let view = self.header.view { + if view.superview == nil { + self.addSubview(view) + } + view.frame = headerFrame + } + + return CGSize(width: availableSize.width, height: headerSize.height + 12.0) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift index a2e99d51..f461e20c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift @@ -9,298 +9,10 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext import AppBundle -import Markdown import ChatMessagePaymentAlertController -import ActivityIndicator -import MultilineTextWithEntitiesComponent import TelegramStringFormatting import TextFormat - -private final class GiftRemoveInfoAlertContentNode: AlertContentNode { - private let context: AccountContext - private let strings: PresentationStrings - private var presentationTheme: PresentationTheme - private let title: String - private let text: String - private let gift: StarGift.UniqueGift - private let peers: [EnginePeer.Id: EnginePeer] - - private let titleNode: ASTextNode - private let textNode: ASTextNode - private let infoBackgroundNode: ASDisplayNode - private let infoView = ComponentView() - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var activityIndicator: ActivityIndicator? - - private var validLayout: CGSize? - - var inProgress = false { - didSet { - if let size = self.validLayout { - let _ = self.updateLayout(size: size, transition: .immediate) - } - } - } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init( - context: AccountContext, - theme: AlertControllerTheme, - ptheme: PresentationTheme, - strings: PresentationStrings, - gift: StarGift.UniqueGift, - peers: [EnginePeer.Id: EnginePeer], - title: String, - text: String, - actions: [TextAlertAction] - ) { - self.context = context - self.strings = strings - self.presentationTheme = ptheme - self.title = title - self.text = text - self.gift = gift - self.peers = peers - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 0 - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.infoBackgroundNode = ASDisplayNode() - self.infoBackgroundNode.backgroundColor = ptheme.overallDarkAppearance ? ptheme.list.itemModalBlocksBackgroundColor : ptheme.list.itemPrimaryTextColor.withAlphaComponent(0.04) - self.infoBackgroundNode.cornerRadius = 10.0 - self.infoBackgroundNode.displaysAsynchronously = false - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - - self.addSubnode(self.infoBackgroundNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor) - self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - linkAttribute: { url in - return ("URL", url) - } - ), textAlignment: .center) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 310.0) - - let strings = self.strings - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 5.0 - - let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 10.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - - var infoSize: CGSize = .zero - for attribute in self.gift.attributes { - if case let .originalInfo(senderPeerId, recipientPeerId, date, text, entities) = attribute { - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - - let tableFont = Font.regular(13.0) - let tableBoldFont = Font.semibold(13.0) - let tableItalicFont = Font.italic(13.0) - let tableBoldItalicFont = Font.semiboldItalic(13.0) - let tableMonospaceFont = Font.monospace(13.0) - - let tableTextColor = self.presentationTheme.list.itemPrimaryTextColor - let tableLinkColor = self.presentationTheme.list.itemAccentColor - - let senderName = senderPeerId.flatMap { self.peers[$0]?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) } - let recipientName = self.peers[recipientPeerId]?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? "" - - let dateString = stringForMediumDate(timestamp: date, strings: strings, dateTimeFormat: presentationData.dateTimeFormat, withTime: false) - let value: NSAttributedString - if let text { - let attributedText = stringWithAppliedEntities(text, entities: entities ?? [], baseColor: tableTextColor, linkColor: tableLinkColor, baseFont: tableFont, linkFont: tableFont, boldFont: tableBoldFont, italicFont: tableItalicFont, boldItalicFont: tableBoldItalicFont, fixedFont: tableMonospaceFont, blockQuoteFont: tableFont, message: nil) - - let format = senderName != nil ? presentationData.strings.Gift_Unique_OriginalInfoSenderWithText(senderName!, recipientName, dateString, "") : presentationData.strings.Gift_Unique_OriginalInfoWithText(recipientName, dateString, "") - let string = NSMutableAttributedString(string: format.string, font: tableFont, textColor: tableTextColor) - string.replaceCharacters(in: format.ranges[format.ranges.count - 1].range, with: attributedText) - if let _ = senderPeerId { - string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) - string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[1].range) - } else { - string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) - } - value = string - } else { - let format = senderName != nil ? presentationData.strings.Gift_Unique_OriginalInfoSender(senderName!, recipientName, dateString) : presentationData.strings.Gift_Unique_OriginalInfo(recipientName, dateString) - let string = NSMutableAttributedString(string: format.string, font: tableFont, textColor: tableTextColor) - if let _ = senderPeerId { - string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) - string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[1].range) - } else { - string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) - } - - value = string - } - - infoSize = self.infoView.update( - transition: .immediate, - component: AnyComponent( - MultilineTextWithEntitiesComponent( - context: self.context, - animationCache: self.context.animationCache, - animationRenderer: self.context.animationRenderer, - placeholderColor: self.presentationTheme.list.mediaPlaceholderColor, - text: .plain(value), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - handleSpoilers: true - ) - ), - environment: {}, - containerSize: CGSize(width: contentWidth - 64.0, height: size.height) - ) - let infoFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - infoSize.width) / 2.0), y: titleSize.height + textSize.height + 54.0), size: infoSize) - if let view = self.infoView.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = infoFrame - } - self.infoBackgroundNode.frame = infoFrame.insetBy(dx: -12.0, dy: -12.0) - - break - } - } - - let resultSize = CGSize(width: contentWidth, height: titleSize.height + textSize.height + infoSize.height + actionsHeight + 46.0 + insets.top + insets.bottom) - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - do { - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - do { - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - do { - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if self.inProgress { - let activityIndicator: ActivityIndicator - if let current = self.activityIndicator { - activityIndicator = current - } else { - activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false)) - self.addSubnode(activityIndicator) - } - - if let actionNode = self.actionNodes.first { - actionNode.isHidden = true - - let indicatorSize = CGSize(width: 22.0, height: 22.0) - transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize)) - } - } - - return resultSize - } -} +import AlertComponent public func giftRemoveInfoAlertController( context: AccountContext, @@ -309,32 +21,89 @@ public func giftRemoveInfoAlertController( removeInfoStars: Int64, navigationController: NavigationController?, commit: @escaping () -> Void -) -> AlertController { +) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings - let title = strings.Gift_RemoveDetails_Title - let text = strings.Gift_RemoveDetails_Text - let buttonText = strings.Gift_RemoveDetails_Action(" $ \(presentationStringsFormattedNumber(Int32(clamping: removeInfoStars), presentationData.dateTimeFormat.groupingSeparator))").string - - var contentNode: GiftRemoveInfoAlertContentNode? - var dismissImpl: ((Bool) -> Void)? - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { - dismissImpl?(true) - commit() - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - })] + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Gift_RemoveDetails_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.Gift_RemoveDetails_Text)) + ) + )) - contentNode = GiftRemoveInfoAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peers: peers, title: title, text: text, actions: actions) - - let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: navigationController, chatPeerId: context.account.peerId, showBalance: removeInfoStars > 0) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() + for attribute in gift.attributes { + if case let .originalInfo(senderPeerId, recipientPeerId, date, text, entities) = attribute { + let textColor = presentationData.theme.actionSheet.primaryTextColor + let linkColor = presentationData.theme.actionSheet.controlAccentColor + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + let italicTextFont = Font.italic(15.0) + let boldItalicTextFont = Font.with(size: 15.0, weight: .semibold, traits: .italic) + let fixedTextFont = Font.monospace(15.0) + + let senderName = senderPeerId.flatMap { peers[$0]?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) } + let recipientName = peers[recipientPeerId]?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? "" + + let dateString = stringForMediumDate(timestamp: date, strings: strings, dateTimeFormat: presentationData.dateTimeFormat, withTime: false) + let value: NSAttributedString + if let text { + let attributedText = stringWithAppliedEntities(text, entities: entities ?? [], baseColor: textColor, linkColor: linkColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: italicTextFont, boldItalicFont: boldItalicTextFont, fixedFont: fixedTextFont, blockQuoteFont: textFont, message: nil) + + let format = senderName != nil ? strings.Gift_Unique_OriginalInfoSenderWithText(senderName!, recipientName, dateString, "") : strings.Gift_Unique_OriginalInfoWithText(recipientName, dateString, "") + let string = NSMutableAttributedString(string: format.string, font: textFont, textColor: textColor) + string.replaceCharacters(in: format.ranges[format.ranges.count - 1].range, with: attributedText) + if let _ = senderPeerId { + string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[0].range) + string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[1].range) + } else { + string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[0].range) + } + value = string + } else { + let format = senderName != nil ? strings.Gift_Unique_OriginalInfoSender(senderName!, recipientName, dateString) : strings.Gift_Unique_OriginalInfo(recipientName, dateString) + let string = NSMutableAttributedString(string: format.string, font: textFont, textColor: textColor) + if let _ = senderPeerId { + string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[0].range) + string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[1].range) + } else { + string.addAttribute(.foregroundColor, value: linkColor, range: format.ranges[0].range) + } + + value = string + } + + content.append(AnyComponentWithIdentity( + id: "info", + component: AnyComponent( + AlertTextComponent(content: .attributed(value), style: .background(.small)) + ) + )) } } - return controller + + let alertController = ChatMessagePaymentAlertController( + context: context, + presentationData: presentationData, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + content: content, + actions: [ + .init(title: strings.Gift_RemoveDetails_Action(" $ \(presentationStringsFormattedNumber(Int32(clamping: removeInfoStars), presentationData.dateTimeFormat.groupingSeparator))").string, type: .default, action: { + commit() + }), + .init(title: strings.Common_Cancel) + ], + navigationController: navigationController, + chatPeerId: context.account.peerId, + showBalance: removeInfoStars > 0 + ) + return alertController } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift index a58d8bf5..f625cc31 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift @@ -6,421 +6,18 @@ import ComponentFlow import Postbox import TelegramCore import TelegramPresentationData -import TelegramUIPreferences import AccountContext import AppBundle -import AvatarNode -import Markdown import GiftItemComponent import ChatMessagePaymentAlertController -import ActivityIndicator import TooltipUI import MultilineTextComponent import TelegramStringFormatting - -private final class GiftTransferAlertContentNode: AlertContentNode { - private let context: AccountContext - private let strings: PresentationStrings - private var presentationTheme: PresentationTheme - private let title: String - private let text: String - private let gift: StarGift.UniqueGift - - private let titleNode: ASTextNode - private let giftView = ComponentView() - private let textNode: ASTextNode - private let arrowNode: ASImageNode - private let avatarNode: AvatarNode - private let tableView = ComponentView() - - private let modelButtonTag = GenericComponentViewTag() - private let backdropButtonTag = GenericComponentViewTag() - private let symbolButtonTag = GenericComponentViewTag() - - fileprivate var getController: () -> ViewController? = { return nil} - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var activityIndicator: ActivityIndicator? - - private var validLayout: CGSize? - - var inProgress = false { - didSet { - if let size = self.validLayout { - let _ = self.updateLayout(size: size, transition: .immediate) - } - } - } - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init( - context: AccountContext, - theme: AlertControllerTheme, - ptheme: PresentationTheme, - strings: PresentationStrings, - gift: StarGift.UniqueGift, - peer: EnginePeer, - title: String, - text: String, - actions: [TextAlertAction] - ) { - self.context = context - self.strings = strings - self.presentationTheme = ptheme - self.title = title - self.text = text - self.gift = gift - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 0 - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.arrowNode = ASImageNode() - self.arrowNode.displaysAsynchronously = false - self.arrowNode.displayWithoutProcessing = true - - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - self.addSubnode(self.arrowNode) - self.addSubnode(self.avatarNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - - self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor) - self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - linkAttribute: { url in - return ("URL", url) - } - ), textAlignment: .center) - self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor.withAlphaComponent(0.9)) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - fileprivate func dismissAllTooltips() { - guard let controller = self.getController() else { - return - } - controller.window?.forEachController({ controller in - if let controller = controller as? TooltipScreen { - controller.dismiss(inPlace: false) - } - }) - controller.forEachController({ controller in - if let controller = controller as? TooltipScreen { - controller.dismiss(inPlace: false) - } - return true - }) - } - - func showAttributeInfo(tag: Any, text: String) { - guard let controller = self.getController() else { - return - } - self.dismissAllTooltips() - - guard let sourceView = self.tableView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: controller.view) else { - return - } - - let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize()) - let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in - return .dismiss(consume: false) - }) - controller.present(tooltipController, in: .current) - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 310.0) - - let strings = self.strings - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let avatarSize = CGSize(width: 60.0, height: 60.0) - self.avatarNode.updateSize(size: avatarSize) - - let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize) - - let _ = self.giftView.update( - transition: .immediate, - component: AnyComponent( - GiftItemComponent( - context: self.context, - theme: self.presentationTheme, - strings: strings, - peer: nil, - subject: .uniqueGift(gift: self.gift, price: nil), - mode: .thumbnail - ) - ), - environment: {}, - containerSize: avatarSize - ) - if let view = self.giftView.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = giftFrame - } - - if let arrowImage = self.arrowNode.image { - let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) - transition.updateFrame(node: self.arrowNode, frame: arrowFrame) - } - - let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize) - transition.updateFrame(node: self.avatarNode, frame: avatarFrame) - - origin.y += avatarSize.height + 17.0 - - let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 5.0 - - let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 10.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - - let tableFont = Font.regular(15.0) - let tableTextColor = self.presentationTheme.list.itemPrimaryTextColor - - var tableItems: [TableComponent.Item] = [] - let order: [StarGift.UniqueGift.Attribute.AttributeType] = [ - .model, .pattern, .backdrop, .originalInfo - ] - - var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:] - for attribute in self.gift.attributes { - attributeMap[attribute.attributeType] = attribute - } - - for type in order { - if let attribute = attributeMap[type] { - let id: String? - let title: String? - let value: NSAttributedString - let percentage: Float? - let tag: AnyObject? - - switch attribute { - case let .model(name, _, rarity): - id = "model" - title = strings.Gift_Unique_Model - value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) - percentage = Float(rarity) * 0.1 - tag = self.modelButtonTag - case let .backdrop(name, _, _, _, _, _, rarity): - id = "backdrop" - title = strings.Gift_Unique_Backdrop - value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) - percentage = Float(rarity) * 0.1 - tag = self.backdropButtonTag - case let .pattern(name, _, rarity): - id = "pattern" - title = strings.Gift_Unique_Symbol - value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) - percentage = Float(rarity) * 0.1 - tag = self.symbolButtonTag - case .originalInfo: - continue - } - - var items: [AnyComponentWithIdentity] = [] - items.append( - AnyComponentWithIdentity( - id: AnyHashable(0), - component: AnyComponent( - MultilineTextComponent(text: .plain(value)) - ) - ) - ) - if let percentage, let tag { - items.append(AnyComponentWithIdentity( - id: AnyHashable(1), - component: AnyComponent(Button( - content: AnyComponent(ButtonContentComponent( - context: self.context, - text: formatPercentage(percentage), - color: self.presentationTheme.list.itemAccentColor - )), - action: { [weak self] in - self?.showAttributeInfo(tag: tag, text: strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string) - } - ).tagged(tag)) - )) - } - let itemComponent = AnyComponent( - HStack(items, spacing: 4.0) - ) - - tableItems.append(.init( - id: id, - title: title, - hasBackground: false, - component: itemComponent - )) - } - } - - if let valueAmount = self.gift.valueAmount, let valueCurrency = self.gift.valueCurrency { - tableItems.append(.init( - id: "fiatValue", - title: strings.Gift_Unique_Value, - component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: "~\(formatCurrencyAmount(valueAmount, currency: valueCurrency))", font: tableFont, textColor: tableTextColor))) - ), - insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0) - )) - } - - let tableSize = self.tableView.update( - transition: .immediate, - component: AnyComponent( - TableComponent( - theme: self.presentationTheme, - items: tableItems - ) - ), - environment: {}, - containerSize: CGSize(width: contentWidth - 32.0, height: size.height) - ) - let tableFrame = CGRect(origin: CGPoint(x: 16.0, y: avatarSize.height + titleSize.height + textSize.height + 60.0), size: tableSize) - if let view = self.tableView.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = tableFrame - } - - let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + tableSize.height + actionsHeight + 40.0 + insets.top + insets.bottom) - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - do { - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - do { - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - do { - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if self.inProgress { - let activityIndicator: ActivityIndicator - if let current = self.activityIndicator { - activityIndicator = current - } else { - activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false)) - self.addSubnode(activityIndicator) - } - - if let actionNode = self.actionNodes.first { - actionNode.isUserInteractionEnabled = false - actionNode.isHidden = false - - let indicatorSize = CGSize(width: 22.0, height: 22.0) - transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize)) - } - } - - return resultSize - } -} +import AlertComponent +import TableComponent +import AvatarComponent +import AlertTransferHeaderComponent +import AlertTableComponent public func giftTransferAlertController( context: AccountContext, @@ -429,7 +26,7 @@ public func giftTransferAlertController( transferStars: Int64, navigationController: NavigationController?, commit: @escaping () -> Void -) -> AlertController { +) -> AlertScreen { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings @@ -444,27 +41,181 @@ public func giftTransferAlertController( buttonText = strings.Gift_Transfer_Confirmation_TransferFree } - var contentNode: GiftTransferAlertContentNode? - var dismissImpl: ((Bool) -> Void)? - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { [weak contentNode] in - contentNode?.inProgress = true - commit() - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - })] + let tableFont = Font.regular(15.0) + let tableTextColor = presentationData.theme.list.itemPrimaryTextColor - contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions) + let modelButtonTag = GenericComponentViewTag() + let backdropButtonTag = GenericComponentViewTag() + let symbolButtonTag = GenericComponentViewTag() + var showAttributeInfoImpl: ((Any, String) -> Void)? - let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: navigationController, chatPeerId: context.account.peerId, showBalance: transferStars > 0) - contentNode?.getController = { [weak controller] in - return controller + var tableItems: [TableComponent.Item] = [] + let order: [StarGift.UniqueGift.Attribute.AttributeType] = [ + .model, .pattern, .backdrop, .originalInfo + ] + + var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:] + for attribute in gift.attributes { + attributeMap[attribute.attributeType] = attribute } - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() + + for type in order { + if let attribute = attributeMap[type] { + let id: String? + let title: String? + let value: NSAttributedString + let percentage: Float? + let tag: AnyObject? + + switch attribute { + case let .model(name, _, rarity): + id = "model" + title = strings.Gift_Unique_Model + value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) + percentage = Float(rarity) * 0.1 + tag = modelButtonTag + case let .backdrop(name, _, _, _, _, _, rarity): + id = "backdrop" + title = strings.Gift_Unique_Backdrop + value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) + percentage = Float(rarity) * 0.1 + tag = backdropButtonTag + case let .pattern(name, _, rarity): + id = "pattern" + title = strings.Gift_Unique_Symbol + value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor) + percentage = Float(rarity) * 0.1 + tag = symbolButtonTag + case .originalInfo: + continue + } + + var items: [AnyComponentWithIdentity] = [] + items.append( + AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent( + MultilineTextComponent(text: .plain(value)) + ) + ) + ) + if let percentage, let tag { + items.append(AnyComponentWithIdentity( + id: AnyHashable(1), + component: AnyComponent(Button( + content: AnyComponent(ButtonContentComponent( + context: context, + text: formatPercentage(percentage), + color: presentationData.theme.list.itemAccentColor + )), + action: { + showAttributeInfoImpl?(tag, strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string) + } + ).tagged(tag)) + )) + } + let itemComponent = AnyComponent( + HStack(items, spacing: 4.0) + ) + + tableItems.append(.init( + id: id, + title: title, + hasBackground: false, + component: itemComponent + )) } } - return controller + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertTransferHeaderComponent( + fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent( + GiftItemComponent( + context: context, + theme: presentationData.theme, + strings: strings, + peer: nil, + subject: .uniqueGift(gift: gift, price: nil), + mode: .thumbnail + ) + )), + toComponent: AnyComponentWithIdentity(id: "avatar", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: peer + ) + )), + type: .transfer + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + content.append(AnyComponentWithIdentity( + id: "table", + component: AnyComponent( + AlertTableComponent(items: tableItems) + ) + )) + + let alertController = ChatMessagePaymentAlertController( + context: context, + presentationData: presentationData, + configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false), + content: content, + actions: [ + .init(title: buttonText, type: .default, action: { + commit() + }), + .init(title: strings.Common_Cancel) + ], + navigationController: navigationController, + chatPeerId: context.account.peerId, + showBalance: transferStars > 0 + ) + + var dismissAllTooltipsImpl: (() -> Void)? + showAttributeInfoImpl = { [weak alertController] tag, text in + dismissAllTooltipsImpl?() + guard let alertController, let sourceView = alertController.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: alertController.view) else { + return + } + + let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize()) + let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in + return .dismiss(consume: false) + }) + alertController.present(tooltipController, in: .current) + } + dismissAllTooltipsImpl = { [weak alertController] in + guard let alertController else { + return + } + alertController.window?.forEachController({ controller in + if let controller = controller as? TooltipScreen { + controller.dismiss(inPlace: false) + } + }) + alertController.forEachController({ controller in + if let controller = controller as? TooltipScreen { + controller.dismiss(inPlace: false) + } + return true + }) + } + return alertController } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUnpinScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUnpinScreen.swift index 43a1b832..761d7f8d 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUnpinScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUnpinScreen.swift @@ -97,7 +97,7 @@ private final class SheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift index d9218330..e3c7712f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift @@ -17,6 +17,7 @@ import LottieComponent import ProfileLevelRatingBarComponent import TextFormat import TelegramStringFormatting +import TableComponent private final class GiftUpgradeCostScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeVariantsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeVariantsScreen.swift new file mode 100644 index 00000000..b0d45858 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeVariantsScreen.swift @@ -0,0 +1,1526 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import TelegramPresentationData +import ComponentFlow +import AccountContext +import ViewControllerComponent +import TelegramCore +import SwiftSignalKit +import Display +import MultilineTextComponent +import MultilineTextWithEntitiesComponent +import ButtonComponent +import PlainButtonComponent +import Markdown +import BundleIconComponent +import TextFormat +import TelegramStringFormatting +import GlassBarButtonComponent +import GiftItemComponent +import EdgeEffect +import AnimatedTextComponent +import SegmentControlComponent +import GiftAnimationComponent +import GlassBackgroundComponent + +private final class GiftUpgradeVariantsScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let gift: StarGift + let attributes: [StarGift.UniqueGift.Attribute] + let selectedAttributes: [StarGift.UniqueGift.Attribute]? + let focusedAttribute: StarGift.UniqueGift.Attribute? + + init( + context: AccountContext, + gift: StarGift, + attributes: [StarGift.UniqueGift.Attribute], + selectedAttributes: [StarGift.UniqueGift.Attribute]?, + focusedAttribute: StarGift.UniqueGift.Attribute? + ) { + self.context = context + self.gift = gift + self.attributes = attributes + self.selectedAttributes = selectedAttributes + self.focusedAttribute = focusedAttribute + } + + static func ==(lhs: GiftUpgradeVariantsScreenComponent, rhs: GiftUpgradeVariantsScreenComponent) -> Bool { + return true + } + + private struct ItemLayout: Equatable { + var containerSize: CGSize + var containerInset: CGFloat + var containerCornerRadius: CGFloat + var bottomInset: CGFloat + var topInset: CGFloat + + init(containerSize: CGSize, containerInset: CGFloat, containerCornerRadius: CGFloat, bottomInset: CGFloat, topInset: CGFloat) { + self.containerSize = containerSize + self.containerInset = containerInset + self.containerCornerRadius = containerCornerRadius + self.bottomInset = bottomInset + self.topInset = topInset + } + } + + enum SelectedSection { + case models + case backdrops + case symbols + } + + private final class ScrollView: UIScrollView { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + } + + final class View: UIView, UIScrollViewDelegate { + private let dimView: UIView + private let containerView: UIView + private let backgroundLayer: SimpleLayer + private let navigationBarContainer: SparseContainerView + private let closeGlassContainerView: GlassBackgroundContainerView + private let playbackGlassContainerView: GlassBackgroundContainerView + private let scrollView: ScrollView + private let scrollContentClippingView: SparseContainerView + private let scrollContentView: UIView + + private let backgroundHandleView: UIImageView + + private let header = ComponentView() + private let closeButton = ComponentView() + private let playbackButton = ComponentView() + + private let title = ComponentView() + private let subtitle = ComponentView() + + private var attributeInfos: [ComponentView] = [] + + private let topEdgeSolidView = UIView() + private let topEdgeEffectView: EdgeEffectView + private let segmentControl = ComponentView() + private let descriptionText = ComponentView() + + private var giftItems: [AnyHashable: ComponentView] = [:] + + private var selectedSection: SelectedSection = .models + + private let giftCompositionExternalState = GiftCompositionComponent.ExternalState() + + private var isPlaying = true + private var showRandomizeTip = false + private var previewTimer: SwiftSignalKit.Timer? + private var previewModelIndex: Int = 0 + private var previewBackdropIndex: Int = 0 + private var previewSymbolIndex: Int = 0 + + private var previewModels: [StarGift.UniqueGift.Attribute] = [] + private var previewBackdrops: [StarGift.UniqueGift.Attribute] = [] + private var previewSymbols: [StarGift.UniqueGift.Attribute] = [] + + private var selectedModel: StarGift.UniqueGift.Attribute? + private var selectedBackdrop: StarGift.UniqueGift.Attribute? + private var selectedSymbol: StarGift.UniqueGift.Attribute? + + private var modelCount: Int32 = 0 + private var backdropCount: Int32 = 0 + private var symbolCount: Int32 = 0 + + private var ignoreScrolling: Bool = false + + private var component: GiftUpgradeVariantsScreenComponent? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + private var environment: ViewControllerComponentContainer.Environment? + private var itemLayout: ItemLayout? + + override init(frame: CGRect) { + self.dimView = UIView() + self.containerView = UIView() + + self.containerView.clipsToBounds = true + self.containerView.layer.cornerRadius = 40.0 + self.containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] + + self.backgroundLayer = SimpleLayer() + self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + self.backgroundLayer.cornerRadius = 40.0 + + self.backgroundHandleView = UIImageView() + + self.navigationBarContainer = SparseContainerView() + + self.topEdgeEffectView = EdgeEffectView() + self.topEdgeEffectView.alpha = 0.0 + + self.closeGlassContainerView = GlassBackgroundContainerView() + self.playbackGlassContainerView = GlassBackgroundContainerView() + + self.scrollView = ScrollView() + + self.scrollContentClippingView = SparseContainerView() + self.scrollContentClippingView.clipsToBounds = true + + self.scrollContentView = UIView() + + super.init(frame: frame) + + self.addSubview(self.dimView) + self.addSubview(self.containerView) + self.containerView.layer.addSublayer(self.backgroundLayer) + + self.scrollView.delaysContentTouches = true + self.scrollView.canCancelContentTouches = true + self.scrollView.clipsToBounds = false + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = true + self.scrollView.scrollsToTop = false + self.scrollView.delegate = self + self.scrollView.clipsToBounds = true + + self.containerView.addSubview(self.scrollContentClippingView) + self.scrollContentClippingView.addSubview(self.scrollView) + + self.scrollView.addSubview(self.scrollContentView) + + self.containerView.addSubview(self.navigationBarContainer) + + self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateScrolling(transition: .immediate) + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.bounds.contains(point) { + return nil + } + if !self.backgroundLayer.frame.contains(point) { + return self.dimView + } + + if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) { + return result + } + let result = super.hitTest(point, with: event) + return result + } + + @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + guard let environment = self.environment, let controller = environment.controller() else { + return + } + controller.dismiss() + } + } + + private func updateScrolling(transition: ComponentTransition) { + guard let itemLayout = self.itemLayout else { + return + } + var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset + topOffset = max(0.0, topOffset) + transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0)) + + transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset)) + + var topOffsetFraction = self.scrollView.bounds.minY / 100.0 + topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) + + self.topEdgeEffectView.alpha = max(0.0, min(1.0, self.scrollView.bounds.minY / 8.0)) + + let minScale: CGFloat = (itemLayout.containerSize.width - 6.0 * 2.0) / itemLayout.containerSize.width + let minScaledTranslation: CGFloat = (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0 + let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius + + let scale = minScale * (1.0 - topOffsetFraction) + 1.0 * topOffsetFraction + let scaledTranslation = minScaledTranslation * (1.0 - topOffsetFraction) + let scaledCornerRadius = minScaledCornerRadius * (1.0 - topOffsetFraction) + itemLayout.containerCornerRadius * topOffsetFraction + + var containerTransform = CATransform3DIdentity + containerTransform = CATransform3DTranslate(containerTransform, 0.0, scaledTranslation, 0.0) + containerTransform = CATransform3DScale(containerTransform, scale, scale, scale) + transition.setTransform(view: self.containerView, transform: containerTransform) + transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: scaledCornerRadius) + + self.updateItems(transition: transition) + } + + func animateIn() { + self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY + self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + + func animateOut(completion: @escaping () -> Void) { + let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY + + self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + } + + private func previewTimerTick() { + guard !self.previewModels.isEmpty else { return } + self.previewModelIndex = (self.previewModelIndex + 1) % self.previewModels.count + + let previousSymbolIndex = self.previewSymbolIndex + var randomSymbolIndex = previousSymbolIndex + while randomSymbolIndex == previousSymbolIndex && !self.previewSymbols.isEmpty { + randomSymbolIndex = Int.random(in: 0 ..< self.previewSymbols.count) + } + if !self.previewSymbols.isEmpty { self.previewSymbolIndex = randomSymbolIndex } + + let previousBackdropIndex = self.previewBackdropIndex + var randomBackdropIndex = previousBackdropIndex + while randomBackdropIndex == previousBackdropIndex && !self.previewBackdrops.isEmpty { + randomBackdropIndex = Int.random(in: 0 ..< self.previewBackdrops.count) + } + if !self.previewBackdrops.isEmpty { self.previewBackdropIndex = randomBackdropIndex } + + self.state?.updated(transition: .easeInOut(duration: 0.25)) + } + + private func updateTimer() { + if self.isPlaying { + self.previewTimer = SwiftSignalKit.Timer(timeout: 3.0, repeat: true, completion: { [weak self] in + guard let self else { + return + } + self.previewTimerTick() + }, queue: Queue.mainQueue()) + self.previewTimer?.start() + } else { + self.previewTimer?.invalidate() + self.previewTimer = nil + } + } + + private var effectiveGifts: [[StarGift.UniqueGift.Attribute]] = [] + private func updateEffectiveGifts(attributes: [StarGift.UniqueGift.Attribute]) { + var effectiveGifts: [[StarGift.UniqueGift.Attribute]] = [] + switch self.selectedSection { + case .models: + let models = Array(attributes.filter({ attribute in + if case .model = attribute { + return true + } else { + return false + } + })) + for model in models { + effectiveGifts.append([model]) + } + case .backdrops: + let selectedModel = self.selectedModel ?? self.previewModels[self.previewModelIndex] + let selectedSymbol = self.selectedSymbol ?? self.previewSymbols[self.previewSymbolIndex] + let backdrops = Array(attributes.filter({ attribute in + if case .backdrop = attribute { + return true + } else { + return false + } + })) + for backdrop in backdrops { + effectiveGifts.append([ + selectedModel, + backdrop, + selectedSymbol + ]) + } + case .symbols: + let selectedBackdrop = self.selectedBackdrop ?? self.previewBackdrops[self.previewBackdropIndex] + let symbols = Array(attributes.filter({ attribute in + if case .pattern = attribute { + return true + } else { + return false + } + })) + for symbol in symbols { + effectiveGifts.append([ + selectedBackdrop, + symbol + ]) + } + } + self.effectiveGifts = effectiveGifts + } + + private func updateItems(transition: ComponentTransition) { + guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else { + return + } + + let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -10.0) + + let fillingSize: CGFloat + if case .regular = environment.metrics.widthClass { + fillingSize = min(itemLayout.containerSize.width, 414.0) - environment.safeInsets.left * 2.0 + } else { + fillingSize = min(itemLayout.containerSize.width, environment.deviceMetrics.screenSize.width) - environment.safeInsets.left * 2.0 + } + + let rawSideInset: CGFloat = floor((itemLayout.containerSize.width - fillingSize) * 0.5) + let sideInset: CGFloat = rawSideInset + 16.0 + + let optionSpacing: CGFloat = 10.0 + let optionWidth = (fillingSize - 16.0 * 2.0 - optionSpacing * 2.0) / 3.0 + let optionSize = CGSize(width: optionWidth, height: 126.0) + + let topInset: CGFloat = 393.0 + + var validIds: [AnyHashable] = [] + var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + 9.0), size: optionSize) + + for attributeList in self.effectiveGifts { + var isVisible = false + if visibleBounds.intersects(itemFrame) { + isVisible = true + } + + var itemId = "" + var title = "" + var rarity: Int32 = 0 + + var modelAttribute: StarGift.UniqueGift.Attribute? + var backdropAttribute: StarGift.UniqueGift.Attribute? + var symbolAttribute: StarGift.UniqueGift.Attribute? + + switch self.selectedSection { + case .models: + itemId += "models_" + case .backdrops: + itemId += "backdrops_" + case .symbols: + itemId += "symbols_" + } + + var isSelected = false + for attribute in attributeList { + switch attribute { + case let .model(name, file, rarityValue): + itemId += "\(file.fileId.id)" + if self.selectedSection == .models { + title = name + rarity = rarityValue + modelAttribute = attribute + + if case let .model(_, selectedFile, _) = self.selectedModel { + isSelected = file.fileId == selectedFile.fileId + } else { + isSelected = false + } + } + case let .backdrop(name, id, _, _, _, _, rarityValue): + itemId += "\(id)" + if self.selectedSection == .backdrops { + title = name + rarity = rarityValue + backdropAttribute = attribute + + if case let .backdrop(_, selectedId, _, _, _, _, _) = self.selectedBackdrop { + isSelected = id == selectedId + } else { + isSelected = false + } + } + case let .pattern(name, file, rarityValue): + itemId += "\(file.fileId.id)" + if self.selectedSection == .symbols { + title = name + rarity = rarityValue + symbolAttribute = attribute + + if case let .pattern(_, selectedFile, _) = self.selectedSymbol { + isSelected = file.fileId == selectedFile.fileId + } else { + isSelected = false + } + } + default: + break + } + } + + if isVisible { + validIds.append(itemId) + + var itemTransition = transition + let visibleItem: ComponentView + if let current = self.giftItems[itemId] { + visibleItem = current + } else { + visibleItem = ComponentView() + if !transition.animation.isImmediate { + itemTransition = .immediate + } + self.giftItems[itemId] = visibleItem + } + + let subject: GiftItemComponent.Subject = .preview(attributes: attributeList, rarity: rarity) + let _ = visibleItem.update( + transition: itemTransition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + GiftItemComponent( + context: component.context, + theme: environment.theme, + strings: environment.strings, + peer: nil, + subject: subject, + title: title, + ribbon: nil, + isSelected: isSelected, + mode: .upgradePreview + ) + ), + effectAlignment: .center, + action: { [weak self] in + guard let self, let state = self.state else { + return + } + if self.isPlaying { + self.isPlaying = false + self.showRandomizeTip = true + Queue.mainQueue().after(2.0) { + if self.showRandomizeTip { + self.showRandomizeTip = false + self.state?.updated(transition: .easeInOut(duration: 0.25)) + } + } + } + + switch self.selectedSection { + case .models: + self.selectedModel = modelAttribute + case .backdrops: + self.selectedBackdrop = backdropAttribute + case .symbols: + self.selectedSymbol = symbolAttribute + } + + state.updated(transition: .easeInOut(duration: 0.25)) + }, + animateAlpha: false + ) + ), + environment: {}, + containerSize: optionSize + ) + if let itemView = visibleItem.view { + if itemView.superview == nil { + self.scrollContentView.addSubview(itemView) + + if !transition.animation.isImmediate { + itemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25) + itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } + itemTransition.setFrame(view: itemView, frame: itemFrame) + } + } + itemFrame.origin.x += itemFrame.width + optionSpacing + if itemFrame.maxX > rawSideInset + fillingSize { + itemFrame.origin.x = sideInset + itemFrame.origin.y += optionSize.height + optionSpacing + } + } + + var removeIds: [AnyHashable] = [] + for (id, item) in self.giftItems { + if !validIds.contains(id) { + removeIds.append(id) + if let itemView = item.view { + if !transition.animation.isImmediate { + itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false) + itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + itemView.removeFromSuperview() + }) + } else { + itemView.removeFromSuperview() + } + } + } + } + for id in removeIds { + self.giftItems.removeValue(forKey: id) + } + } + + func update(component: GiftUpgradeVariantsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + self.updateTimer() + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let themeUpdated = self.environment?.theme !== environment.theme + + let resetScrolling = self.scrollView.bounds.width != availableSize.width + + let fillingSize: CGFloat + if case .regular = environment.metrics.widthClass { + fillingSize = min(availableSize.width, 414.0) - environment.safeInsets.left * 2.0 + } else { + fillingSize = min(availableSize.width, environment.deviceMetrics.screenSize.width) - environment.safeInsets.left * 2.0 + } + let rawSideInset: CGFloat = floor((availableSize.width - fillingSize) * 0.5) + let sideInset: CGFloat = rawSideInset + 16.0 + + if self.component == nil { + var modelCount: Int32 = 0 + var backdropCount: Int32 = 0 + var symbolCount: Int32 = 0 + for attribute in component.attributes { + switch attribute { + case .model: + modelCount += 1 + case .backdrop: + backdropCount += 1 + case .pattern: + symbolCount += 1 + default: + break + } + } + self.modelCount = modelCount + self.backdropCount = backdropCount + self.symbolCount = symbolCount + + let randomModels = Array(component.attributes.filter({ attribute in + if case .model = attribute { + return true + } else { + return false + } + }).shuffled().prefix(15)) + self.previewModels = randomModels + + let randomBackdrops = Array(component.attributes.filter({ attribute in + if case .backdrop = attribute { + return true + } else { + return false + } + }).shuffled()) + self.previewBackdrops = randomBackdrops + + let randomSymbols = Array(component.attributes.filter({ attribute in + if case .pattern = attribute { + return true + } else { + return false + } + }).shuffled().prefix(15)) + self.previewSymbols = randomSymbols + + if let selectedAttributes = component.selectedAttributes { + self.isPlaying = false + for attribute in selectedAttributes { + switch attribute { + case .model: + self.selectedModel = attribute + case .pattern: + self.selectedSymbol = attribute + case .backdrop: + self.selectedBackdrop = attribute + default: + break + } + } + } + if let focusedAttribute = component.focusedAttribute { + switch focusedAttribute { + case .model: + self.selectedSection = .models + case .pattern: + self.selectedSection = .symbols + case .backdrop: + self.selectedSection = .backdrops + default: + break + } + } + + self.updateEffectiveGifts(attributes: component.attributes) + } + + self.component = component + self.state = state + self.environment = environment + + let theme = environment.theme.withModalBlocksBackground() + + if themeUpdated { + self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + self.backgroundLayer.backgroundColor = theme.list.blocksBackgroundColor.cgColor + } + + transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize)) + + var buttonColor: UIColor = .white.withAlphaComponent(0.1) + var secondaryTextColor: UIColor = .white.withAlphaComponent(0.4) + var badgeColor: UIColor = .white.withAlphaComponent(0.4) + + var attributes: [StarGift.UniqueGift.Attribute] = [] + if !self.previewModels.isEmpty { + if self.isPlaying { + attributes.append(self.previewModels[self.previewModelIndex]) + attributes.append(self.previewBackdrops[self.previewBackdropIndex]) + attributes.append(self.previewSymbols[self.previewSymbolIndex]) + } else { + if self.selectedModel == nil { + self.selectedModel = self.previewModels[self.previewModelIndex] + } + if self.selectedBackdrop == nil { + self.selectedBackdrop = self.previewBackdrops[self.previewBackdropIndex] + } + if self.selectedSymbol == nil { + self.selectedSymbol = self.previewSymbols[self.previewSymbolIndex] + } + if let model = self.selectedModel { + attributes.append(model) + } + if let backdrop = self.selectedBackdrop { + attributes.append(backdrop) + } + if let symbol = self.selectedSymbol { + attributes.append(symbol) + } + } + } + + if let backdropAttribute = attributes.first(where: { attribute in + if case .backdrop = attribute { + return true + } else { + return false + } + }), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute { + buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05) + + badgeColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05) + let outer = UIColor(rgb: UInt32(bitPattern: outerColor)) + if outer.lightness < 0.06 { + badgeColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.45) + } else if outer.lightness < 0.295 { + badgeColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.19) + } + secondaryTextColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3) + } + + var contentHeight: CGFloat = 0.0 + let headerSize = self.header.update( + transition: transition, + component: AnyComponent(GiftCompositionComponent( + context: component.context, + theme: environment.theme, + subject: .preview(attributes), + animationOffset: CGPoint(x: 0.0, y: 20.0), + animationScale: nil, + displayAnimationStars: false, + alwaysAnimateTransition: true, + revealedAttributes: Set(), + externalState: self.giftCompositionExternalState, + requestUpdate: { [weak state] transition in + state?.updated(transition: transition) + } + )), + environment: {}, + containerSize: CGSize(width: fillingSize, height: 300.0), + ) + let headerFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - headerSize.width) * 0.5), y: 0.0), size: headerSize) + if let headerView = self.header.view { + if headerView.superview == nil { + headerView.isUserInteractionEnabled = false + headerView.clipsToBounds = true + headerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + headerView.layer.cornerRadius = 38.0 + self.navigationBarContainer.addSubview(headerView) + } + transition.setFrame(view: headerView, frame: headerFrame) + } + + contentHeight += headerSize.height + + var titleText: String = "" + if case let .generic(gift) = component.gift { + titleText = gift.title ?? "" + } + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: titleText, font: Font.semibold(20.0), textColor: .white)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentHeight - 124.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.navigationBarContainer.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + var subtitleItems: [AnimatedTextComponent.Item] = [] + let subtitleString = self.isPlaying ? environment.strings.Gift_Variants_RandomTraits : environment.strings.Gift_Variants_SelectedTraits + let words = subtitleString.components(separatedBy: " ") + for i in 0 ..< words.count { + var text = words[i] + if i > 0 { + text = " \(text)" + } + subtitleItems.append(AnimatedTextComponent.Item(id: text.lowercased(), content: .text(text))) + } + + let subtitleSize = self.subtitle.update( + transition: .spring(duration: 0.2), + component: AnyComponent(AnimatedTextComponent( + font: Font.regular(14.0), + color: secondaryTextColor, + items: subtitleItems, + noDelay: true, + blur: true + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + ) + let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) * 0.5), y: contentHeight - 97.0), size: subtitleSize) + if let subtitleView = self.subtitle.view { + if subtitleView.superview == nil { + self.navigationBarContainer.addSubview(subtitleView) + } + transition.setFrame(view: subtitleView, frame: subtitleFrame) + } + + let attributeSpacing: CGFloat = 10.0 + let attributeWidth: CGFloat = floor((fillingSize - attributeSpacing * CGFloat(attributes.count - 1)) / CGFloat(attributes.count)) + let attributeHeight: CGFloat = 45.0 + + for i in 0 ..< attributes.count { + var attributeFrame = CGRect(origin: CGPoint(x: sideInset + CGFloat(i) * (attributeWidth + attributeSpacing), y: contentHeight - 60.0), size: CGSize(width: attributeWidth, height: attributeHeight)) + if i == attributes.count - 1 { + attributeFrame.size.width = max(0.0, availableSize.width - sideInset - attributeFrame.minX) + } + let attributeInfo: ComponentView + if self.attributeInfos.count > i { + attributeInfo = self.attributeInfos[i] + } else { + attributeInfo = ComponentView() + self.attributeInfos.append(attributeInfo) + } + let attribute = attributes[i] + let _ = attributeInfo.update( + transition: transition, + component: AnyComponent(AttributeInfoComponent( + strings: environment.strings, + backgroundColor: UIColor.white.withAlphaComponent(0.16), + secondaryTextColor: secondaryTextColor.mixedWith(.white, alpha: 0.3), + badgeColor: badgeColor, + attribute: attribute + )), + environment: {}, + containerSize: attributeFrame.size + ) + if let attributeInfoView = attributeInfo.view { + if attributeInfoView.superview == nil { + self.navigationBarContainer.addSubview(attributeInfoView) + } + transition.setFrame(view: attributeInfoView, frame: attributeFrame) + } + } + + let edgeEffectHeight: CGFloat = 44.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: contentHeight + 44.0), size: CGSize(width: fillingSize, height: edgeEffectHeight)) + let edgeSolidFrame = CGRect(origin: CGPoint(x: rawSideInset, y: contentHeight), size: CGSize(width: fillingSize, height: 44.0)) + transition.setFrame(view: self.topEdgeSolidView, frame: edgeSolidFrame) + transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame) + self.topEdgeSolidView.backgroundColor = theme.list.blocksBackgroundColor + self.topEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + + contentHeight += 16.0 + + let selectedId: AnyHashable + switch self.selectedSection { + case .models: + selectedId = AnyHashable(SelectedSection.models) + case .backdrops: + selectedId = AnyHashable(SelectedSection.backdrops) + case .symbols: + selectedId = AnyHashable(SelectedSection.symbols) + } + + let segmentedSize = self.segmentControl.update( + transition: transition, + component: AnyComponent(SegmentControlComponent( + theme: environment.theme, + items: [ + SegmentControlComponent.Item(id: AnyHashable(SelectedSection.models), title: environment.strings.Gift_Variants_Models), + SegmentControlComponent.Item(id: AnyHashable(SelectedSection.backdrops), title: environment.strings.Gift_Variants_Backdrops), + SegmentControlComponent.Item(id: AnyHashable(SelectedSection.symbols), title: environment.strings.Gift_Variants_Symbols) + ], + selectedId: selectedId, + action: { [weak self] id in + guard let self, let component = self.component, let id = id.base as? SelectedSection else { + return + } + self.selectedSection = id + self.isPlaying = false + + self.updateEffectiveGifts(attributes: component.attributes) + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))) + })), + environment: {}, + containerSize: CGSize(width: fillingSize - 8.0 * 2.0, height: 100.0) + ) + let segmentedControlFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - segmentedSize.width) * 0.5), y: contentHeight), size: segmentedSize) + if let segmentedControlComponentView = self.segmentControl.view { + if segmentedControlComponentView.superview == nil { + self.navigationBarContainer.addSubview(self.topEdgeSolidView) + self.navigationBarContainer.addSubview(self.topEdgeEffectView) + self.navigationBarContainer.addSubview(segmentedControlComponentView) + } + transition.setFrame(view: segmentedControlComponentView, frame: segmentedControlFrame) + } + contentHeight += segmentedSize.height + contentHeight += 18.0 + + let itemHeight: CGFloat = 126.0 + let itemSpacing: CGFloat = 10.0 + + let descriptionText: String + let itemCount: Int32 + switch self.selectedSection { + case .models: + descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Model(self.modelCount)).string + itemCount = self.modelCount + case .backdrops: + descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Backdrop(self.backdropCount)).string + itemCount = self.backdropCount + case .symbols: + descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Symbol(self.symbolCount)).string + itemCount = self.symbolCount + } + + let descriptionFont = Font.regular(13.0) + let descriptionBoldFont = Font.semibold(13.0) + let descriptionTextColor = theme.list.itemSecondaryTextColor + let descriptionMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionTextColor), bold: MarkdownAttributeSet(font: descriptionBoldFont, textColor: descriptionTextColor), link: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionTextColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + + let descriptionSize = self.descriptionText.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .markdown(text: descriptionText, attributes: descriptionMarkdownAttributes) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + ) + let descriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - descriptionSize.width) * 0.5), y: contentHeight), size: descriptionSize) + if let descriptionView = self.descriptionText.view { + if descriptionView.superview == nil { + self.scrollContentView.addSubview(descriptionView) + } + descriptionView.frame = descriptionFrame + } + contentHeight += descriptionSize.height + contentHeight += 26.0 + + contentHeight += (itemHeight + itemSpacing) * ceil(CGFloat(itemCount) / 3.0) + + if self.backgroundHandleView.image == nil { + self.backgroundHandleView.image = generateStretchableFilledCircleImage(diameter: 5.0, color: .white)?.withRenderingMode(.alwaysTemplate) + } + self.backgroundHandleView.tintColor = UIColor.white.withAlphaComponent(0.4) + let backgroundHandleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - 36.0) * 0.5), y: 5.0), size: CGSize(width: 36.0, height: 5.0)) + if self.backgroundHandleView.superview == nil { + self.navigationBarContainer.addSubview(self.backgroundHandleView) + } + transition.setFrame(view: self.backgroundHandleView, frame: backgroundHandleFrame) + + self.playbackGlassContainerView.update(size: CGSize(width: fillingSize, height: 64.0), isDark: false, transition: .immediate) + self.playbackGlassContainerView.frame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: fillingSize, height: 64.0)) + + self.closeGlassContainerView.update(size: CGSize(width: 64.0, height: 64.0), isDark: false, transition: .immediate) + self.closeGlassContainerView.frame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: 64.0, height: 64.0)) + + let closeButtonSize = self.closeButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: buttonColor, + isDark: false, + state: .tintedGlass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Back", + tintColor: .white + ) + )), + action: { [weak self] _ in + guard let self else { + return + } + self.environment?.controller()?.dismiss() + } + )), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + let closeButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: closeButtonSize) + if let closeButtonView = self.closeButton.view { + if closeButtonView.superview == nil { + self.navigationBarContainer.addSubview(self.playbackGlassContainerView) + self.navigationBarContainer.addSubview(self.closeGlassContainerView) + self.closeGlassContainerView.contentView.addSubview(closeButtonView) + } + transition.setFrame(view: closeButtonView, frame: closeButtonFrame) + } + + let playbackButtonSize = self.playbackButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: nil, + backgroundColor: buttonColor, + isDark: false, + state: .tintedGlass, + component: AnyComponentWithIdentity(id: "content", component: AnyComponent( + PlayButtonComponent(isPlay: !self.isPlaying, title: !self.isPlaying && self.showRandomizeTip ? environment.strings.Gift_Variants_Randomize : nil) + )), + action: { [weak self] _ in + guard let self else { + return + } + self.isPlaying = !self.isPlaying + + if !self.isPlaying { + self.showRandomizeTip = true + Queue.mainQueue().after(2.0) { + if self.showRandomizeTip { + self.showRandomizeTip = false + self.state?.updated(transition: .easeInOut(duration: 0.25)) + } + } + } else { + self.selectedModel = nil + self.selectedBackdrop = nil + self.selectedSymbol = nil + + self.showRandomizeTip = false + + self.previewTimerTick() + } + self.state?.updated(transition: .easeInOut(duration: 0.25)) + } + )), + environment: {}, + containerSize: CGSize(width: 160.0, height: 40.0) + ) + let playbackButtonFrame = CGRect(origin: CGPoint(x: fillingSize - 16.0 - playbackButtonSize.width, y: 16.0), size: playbackButtonSize) + if let playbackButtonView = self.playbackButton.view { + if playbackButtonView.superview == nil { + self.playbackGlassContainerView.contentView.addSubview(playbackButtonView) + } + transition.setFrame(view: playbackButtonView, frame: playbackButtonFrame) + } + + let containerInset: CGFloat = environment.statusBarHeight + 10.0 + contentHeight += environment.safeInsets.bottom + + var initialContentHeight = contentHeight + let clippingY: CGFloat + + initialContentHeight = contentHeight + + clippingY = availableSize.height + + let topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight) + + let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset) + + self.scrollContentClippingView.layer.cornerRadius = 38.0 + + self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, containerCornerRadius: environment.deviceMetrics.screenCornerRadius, bottomInset: environment.safeInsets.bottom, topInset: topInset) + + transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight))) + + transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) + transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: CGSize(width: fillingSize, height: availableSize.height))) + + let scrollClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: containerInset), size: CGSize(width: availableSize.width, height: clippingY - containerInset)) + transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center) + transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size)) + + self.ignoreScrolling = true + transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))) + let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight) + if contentSize != self.scrollView.contentSize { + self.scrollView.contentSize = contentSize + } + if resetScrolling { + self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize) + } + self.ignoreScrolling = false + self.updateScrolling(transition: transition) + + transition.setPosition(view: self.containerView, position: CGRect(origin: CGPoint(), size: availableSize).center) + transition.setBounds(view: self.containerView, bounds: CGRect(origin: CGPoint(), size: availableSize)) + + if let controller = environment.controller(), !controller.automaticallyControlPresentationContextLayout { + let bottomInset: CGFloat = contentHeight - 12.0 + + let layout = ContainerViewLayout( + size: availableSize, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0), + safeInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), + additionalInsets: .zero, + statusBarHeight: environment.statusBarHeight, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ) + controller.presentationContext.containerLayoutUpdated(layout, transition: transition.containedViewLayoutTransition) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public class GiftUpgradeVariantsScreen: ViewControllerComponentContainer { + private let context: AccountContext + + private var didPlayAppearAnimation: Bool = false + private var isDismissed: Bool = false + + public init( + context: AccountContext, + gift: StarGift, + attributes: [StarGift.UniqueGift.Attribute], + selectedAttributes: [StarGift.UniqueGift.Attribute]?, + focusedAttribute: StarGift.UniqueGift.Attribute? + ) { + self.context = context + + super.init(context: context, component: GiftUpgradeVariantsScreenComponent( + context: context, + gift: gift, + attributes: attributes, + selectedAttributes: selectedAttributes, + focusedAttribute: focusedAttribute + ), navigationBarAppearance: .none, theme: .default) + + self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + self.automaticallyControlPresentationContextLayout = false + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.view.disablesInteractiveModalDismiss = true + + if !self.didPlayAppearAnimation { + self.didPlayAppearAnimation = true + + if let componentView = self.node.hostView.componentView as? GiftUpgradeVariantsScreenComponent.View { + componentView.animateIn() + } + } + } + + override public func dismiss(completion: (() -> Void)? = nil) { + if !self.isDismissed { + self.isDismissed = true + + if let componentView = self.node.hostView.componentView as? GiftUpgradeVariantsScreenComponent.View { + componentView.animateOut(completion: { [weak self] in + completion?() + self?.dismiss(animated: false) + }) + } else { + self.dismiss(animated: false) + } + } + } +} + +private final class AttributeInfoComponent: Component { + let strings: PresentationStrings + let backgroundColor: UIColor + let secondaryTextColor: UIColor + let badgeColor: UIColor + let attribute: StarGift.UniqueGift.Attribute + + init( + strings: PresentationStrings, + backgroundColor: UIColor, + secondaryTextColor: UIColor, + badgeColor: UIColor, + attribute: StarGift.UniqueGift.Attribute + ) { + self.strings = strings + self.backgroundColor = backgroundColor + self.secondaryTextColor = secondaryTextColor + self.badgeColor = badgeColor + self.attribute = attribute + } + + static func ==(lhs: AttributeInfoComponent, rhs: AttributeInfoComponent) -> Bool { + if lhs.strings !== rhs.strings { + return false + } + if lhs.backgroundColor != rhs.backgroundColor { + return false + } + if lhs.secondaryTextColor != rhs.secondaryTextColor { + return false + } + if lhs.badgeColor != rhs.badgeColor { + return false + } + if lhs.attribute != rhs.attribute { + return false + } + return true + } + + final class View: UIView { + let background = SimpleLayer() + let title = ComponentView() + let subtitle = ComponentView() + + let badgeBackground = SimpleLayer() + let badge = ComponentView() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: AttributeInfoComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let backgroundFrame = CGRect(origin: CGPoint(), size: availableSize) + if self.background.superlayer == nil { + self.background.cornerRadius = 16.0 + self.background.cornerCurve = .continuous + self.layer.addSublayer(self.background) + + self.badgeBackground.cornerRadius = 9.5 + self.badgeBackground.cornerCurve = .continuous + self.layer.addSublayer(self.badgeBackground) + } + self.background.frame = backgroundFrame + transition.setBackgroundColor(layer: self.background, color: component.backgroundColor) + + let title: String + let subtitle: String + let rarity: Int32 + switch component.attribute { + case let .model(name, _, rarityValue): + title = name + subtitle = component.strings.Gift_Variants_Model + rarity = rarityValue + case let .backdrop(name, _, _, _, _, _, rarityValue): + title = name + subtitle = component.strings.Gift_Variants_Backdrop + rarity = rarityValue + case let .pattern(name, _, rarityValue): + title = name + subtitle = component.strings.Gift_Variants_Symbol + rarity = rarityValue + default: + title = "" + subtitle = "" + rarity = 0 + } + + let titleSize = self.title.update( + transition: .spring(duration: 0.2), + component: AnyComponent(AnimatedTextComponent( + font: Font.semibold(13.0), + color: UIColor.white, + items: [AnimatedTextComponent.Item(id: "title", content: .text(title))], + noDelay: true, + blur: true + )), + environment: {}, + containerSize: CGSize(width: backgroundFrame.size.width - 8.0, height: backgroundFrame.size.height) + ) + let subtitleSize = self.subtitle.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: subtitle, font: Font.regular(11.0), textColor: .white)), + tintColor: component.secondaryTextColor + )), + environment: {}, + containerSize: backgroundFrame.size + ) + + let spacing: CGFloat = 0.0 + let titleFrame = CGRect(origin: CGPoint(x: floor((backgroundFrame.width - titleSize.width) * 0.5), y: floor((backgroundFrame.height - titleSize.height - spacing - subtitleSize.height) * 0.5)), size: titleSize) + let subtitleFrame = CGRect(origin: CGPoint(x: floor((backgroundFrame.width - subtitleSize.width) * 0.5), y: titleFrame.maxY + spacing), size: subtitleSize) + + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + if let subtitleView = self.subtitle.view { + if subtitleView.superview == nil { + self.addSubview(subtitleView) + } + transition.setFrame(view: subtitleView, frame: subtitleFrame) + } + + func formatPercentage(_ value: Float) -> String { + return String(format: "%0.1f", value).replacingOccurrences(of: ".0", with: "").replacingOccurrences(of: ",0", with: "") + } + let percentage = Float(rarity) * 0.1 + + let badgeSize = self.badge.update( + transition: .spring(duration: 0.2), + component: AnyComponent(AnimatedTextComponent( + font: Font.with(size: 12.0, weight: .semibold, traits: .monospacedNumbers), + color: UIColor.white, + items: [ + AnimatedTextComponent.Item(id: "value", content: .text(formatPercentage(percentage))), + AnimatedTextComponent.Item(id: "percent", content: .text("%")), + ], + noDelay: true, + blur: true + )), + environment: {}, + containerSize: backgroundFrame.size + ) + let badgeFrame = CGRect(origin: CGPoint(x: backgroundFrame.width - badgeSize.width - 2.0, y: backgroundFrame.minY - 8.0), size: badgeSize) + if let badgeView = self.badge.view { + if badgeView.superview == nil { + self.addSubview(badgeView) + } + transition.setFrame(view: badgeView, frame: badgeFrame) + } + + let badgeBackgroundFrame = badgeFrame.insetBy(dx: -5.5, dy: -2.0) + transition.setFrame(layer: self.badgeBackground, frame: badgeBackgroundFrame) + transition.setBackgroundColor(layer: self.badgeBackground, color: component.badgeColor) + + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + + +private final class PlayButtonComponent: Component { + let isPlay: Bool + let title: String? + + public init( + isPlay: Bool, + title: String? + ) { + self.isPlay = isPlay + self.title = title + } + + static func ==(lhs: PlayButtonComponent, rhs: PlayButtonComponent) -> Bool { + if lhs.isPlay != rhs.isPlay { + return false + } + if lhs.title != rhs.title { + return false + } + return true + } + + final class View: UIView { + private var component: PlayButtonComponent? + private weak var componentState: EmptyComponentState? + + private let containerView = UIView() + private let titleContainerView = UIView() + private let title = ComponentView() + private let play = ComponentView() + private let pause = ComponentView() + + override init(frame: CGRect) { + super.init(frame: frame) + + self.containerView.clipsToBounds = true + self.containerView.layer.cornerRadius = 20.0 + self.addSubview(self.containerView) + + self.titleContainerView.clipsToBounds = true + self.containerView.addSubview(self.titleContainerView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: PlayButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.componentState = state + + var contentSize = CGSize(width: 15.0, height: 21.0) + + var titleSize = CGSize() + if let titleString = component.title { + titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: .white)))), + environment: {}, + containerSize: availableSize + ) + let titleFrame = CGRect(origin: CGPoint(x: 9.0, y: 10.0), size: titleSize) + if let titleView = self.title.view { + titleView.alpha = 1.0 + if titleView.superview == nil { + self.titleContainerView.addSubview(titleView) + transition.animateAlpha(view: titleView, from: 0.0, to: 1.0) + } + titleView.frame = titleFrame + } + contentSize.width += titleSize.width + 4.0 + } else if let titleView = self.title.view { + transition.setAlpha(view: titleView, alpha: 0.0, completion: { finished in + if finished { + titleView.removeFromSuperview() + } + }) + } + transition.setFrame(view: self.titleContainerView, frame: CGRect(origin: .zero, size: CGSize(width: titleSize.width + 14.0, height: 40.0))) + + if component.isPlay { + let iconSize = self.play.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent(name: "Media Gallery/PlayButton", tintColor: .white)), + environment: {}, + containerSize: availableSize + ) + let iconFrame = CGRect(origin: CGPoint(x: contentSize.width - iconSize.width + 21.0, y: 5.0), size: iconSize) + if let iconView = self.play.view { + iconView.alpha = 1.0 + if iconView.superview == nil { + self.containerView.addSubview(iconView) + transition.animateAlpha(view: iconView, from: 0.0, to: 1.0) + transition.animateScale(view: iconView, from: 0.01, to: 1.0) + } + transition.setFrame(view: iconView, frame: iconFrame) + } + } else if let iconView = self.play.view { + transition.setAlpha(view: iconView, alpha: 0.0, completion: { finished in + if finished { + iconView.removeFromSuperview() + } + }) + transition.animateScale(view: iconView, from: 1.0, to: 0.01) + } + + if !component.isPlay { + let iconSize = self.pause.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent(name: "Media Gallery/PictureInPicturePause", tintColor: .white)), + environment: {}, + containerSize: availableSize + ) + let iconFrame = CGRect(origin: CGPoint(x: contentSize.width - iconSize.width + 12.0 - UIScreenPixel, y: 13.0 - UIScreenPixel), size: iconSize) + if let iconView = self.pause.view { + iconView.alpha = 1.0 + if iconView.superview == nil { + self.containerView.addSubview(iconView) + transition.animateAlpha(view: iconView, from: 0.0, to: 1.0) + transition.animateScale(view: iconView, from: 0.01, to: 1.0) + } + transition.setFrame(view: iconView, frame: iconFrame) + } + } else if let iconView = self.pause.view { + transition.setAlpha(view: iconView, alpha: 0.0, completion: { finished in + if finished { + iconView.removeFromSuperview() + } + }) + transition.animateScale(view: iconView, from: 1.0, to: 0.01) + } + + let containerWidth: CGFloat = contentSize.width + 26.0 + let containerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((contentSize.width - containerWidth) / 2.0), y: floorToScreenPixels((contentSize.height - 40.0) / 2.0)), size: CGSize(width: containerWidth, height: 40.0)) + transition.setFrame(view: self.containerView, frame: containerFrame) + + return contentSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift index 55867118..c267b6f2 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift @@ -25,6 +25,7 @@ import GiftAnimationComponent import ContextUI import GiftItemComponent import GlassBarButtonComponent +import TableComponent private final class GiftValueSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -640,7 +641,7 @@ private final class GiftValueSheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { [weak state] _ in diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 514b6016..cd942adc 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -40,6 +40,10 @@ import ChatThemeScreen import ProfileLevelRatingBarComponent import AnimatedTextComponent import InfoParagraphComponent +import ChatMessagePaymentAlertController +import TableComponent +import PeerTableCellComponent +import AvatarComponent private final class GiftViewSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -120,6 +124,8 @@ private final class GiftViewSheetContent: CombinedComponent { let levelsDisposable = MetaDisposable() var nextGiftToUpgrade: ProfileGiftsContext.State.StarGift? + var giftVariantsDisposable = MetaDisposable() + var buyForm: BotPaymentForm? var buyFormDisposable: Disposable? var buyDisposable: Disposable? @@ -374,6 +380,7 @@ private final class GiftViewSheetContent: CombinedComponent { self.buyDisposable?.dispose() self.levelsDisposable.dispose() self.starsTopUpOptionsDisposable?.dispose() + self.giftVariantsDisposable.dispose() } func openPeer(_ peer: EnginePeer, gifts: Bool = false, dismiss: Bool = true) { @@ -625,7 +632,7 @@ private final class GiftViewSheetContent: CombinedComponent { controller?.dismissAnimated() if let navigationController { - Queue.mainQueue().after(0.5) { + Queue.mainQueue().after(2.5) { starsContext.load(force: true) let text: String @@ -730,7 +737,7 @@ private final class GiftViewSheetContent: CombinedComponent { guard let self else { return } - Queue.mainQueue().after(0.5) { + Queue.mainQueue().after(2.5) { starsContext?.load(force: true) } switch self.subject { @@ -1134,7 +1141,7 @@ private final class GiftViewSheetContent: CombinedComponent { guard let peerId = peerIds.first else { return .complete() } - Queue.mainQueue().after(1.5, { + Queue.mainQueue().after(2.5, { if transferStars > 0 { context.starsContext?.load(force: true) } @@ -1350,6 +1357,33 @@ private final class GiftViewSheetContent: CombinedComponent { }) } + + func openUpgradeVariants(attribute: StarGift.UniqueGift.Attribute? = nil) { + guard let controller = self.getController() as? GiftViewScreen, let arguments = self.subject.arguments else { + return + } + var selectedAttributes: [StarGift.UniqueGift.Attribute]? + if case let .unique(uniqueGift) = arguments.gift { + selectedAttributes = uniqueGift.attributes + } + + self.giftVariantsDisposable.set((self.context.engine.payments.getStarGiftUpgradeAttributes(giftId: arguments.gift.giftId) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] attributes in + guard let self, let attributes else { + return + } + let variantsController = self.context.sharedContext.makeGiftUpgradeVariantsScreen( + context: self.context, + gift: arguments.gift, + attributes: attributes, + selectedAttributes: selectedAttributes, + focusedAttribute: attribute + ) + controller.push(variantsController) + })) + } + func showAttributeInfo(tag: Any, text: String) { guard let controller = self.getController() as? GiftViewScreen else { return @@ -1821,7 +1855,7 @@ private final class GiftViewSheetContent: CombinedComponent { self.updated(transition: .spring(duration: 0.4)) - Queue.mainQueue().after(0.5) { + Queue.mainQueue().after(2.5) { switch finalPrice.currency { case .stars: context.starsContext?.load(force: true) @@ -2114,7 +2148,7 @@ private final class GiftViewSheetContent: CombinedComponent { self.subject = .profileGift(peerId, result) self.updated(transition: .spring(duration: 0.4)) - Queue.mainQueue().after(0.5) { + Queue.mainQueue().after(2.5) { starsContext?.load(force: true) } }) @@ -2233,7 +2267,7 @@ private final class GiftViewSheetContent: CombinedComponent { guard let self else { return } - Queue.mainQueue().after(0.5) { + Queue.mainQueue().after(2.5) { starsContext?.load(force: true) } @@ -2337,6 +2371,7 @@ private final class GiftViewSheetContent: CombinedComponent { guard let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, case let .peerId(ownerPeerId) = uniqueGift.owner, let controller = self.getController() else { return } + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: ownerPeerId)) |> deliverOnMainQueue).start(next: { [weak self] peer in guard let self, let peer else { @@ -2346,13 +2381,41 @@ private final class GiftViewSheetContent: CombinedComponent { guard let self else { return } - self.commitGiftBuyOffer(peer: peer, price: amount, duration: duration) + + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: peer.id)) + |> deliverOnMainQueue).start(next: { [weak self, weak controller] sendPaidMessageStars in + guard let self else { + return + } + let action: (Int64?) -> Void = { allowPaidStars in + self.commitGiftBuyOffer(peer: peer, price: amount, duration: duration, allowPaidStars: allowPaidStars) + } + if let sendPaidMessageStars, sendPaidMessageStars.value > 0 { + let alertController = chatMessagePaymentAlertController( + context: nil, + presentationData: presentationData, + updatedPresentationData: nil, + peers: [EngineRenderedPeer(peer: peer)], + count: 1, + amount: sendPaidMessageStars, + totalAmount: nil, + hasCheck: false, + navigationController: controller?.navigationController as? NavigationController, + completion: { _ in + action(sendPaidMessageStars.value) + } + ) + controller?.present(alertController, in: .window(.root)) + } else { + action(nil) + } + }) })) controller.push(buyController) }) } - func commitGiftBuyOffer(peer: EnginePeer, price: CurrencyAmount, duration: Int32) { + func commitGiftBuyOffer(peer: EnginePeer, price: CurrencyAmount, duration: Int32, allowPaidStars: Int64?) { guard let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { return } @@ -2362,13 +2425,13 @@ private final class GiftViewSheetContent: CombinedComponent { guard let self else { return } - self.upgradeDisposable = (context.engine.payments.sendStarGiftOffer(peerId: peer.id, slug: uniqueGift.slug, amount: price, duration: duration, allowPaidStars: nil) + self.upgradeDisposable = (context.engine.payments.sendStarGiftOffer(peerId: peer.id, slug: uniqueGift.slug, amount: price, duration: duration, allowPaidStars: allowPaidStars) |> deliverOnMainQueue).start(error: { _ in }, completed: { [weak self, weak starsContext] in guard let self else { return } - Queue.mainQueue().after(0.5) { + Queue.mainQueue().after(2.5) { starsContext?.load(force: true) } self.openPeer(peer, dismiss: true) @@ -2383,11 +2446,15 @@ private final class GiftViewSheetContent: CombinedComponent { guard let self, let controller = self.getController() else { return } + var finalStars = price.amount.value + if let allowPaidStars { + finalStars += allowPaidStars + } let purchaseController = context.sharedContext.makeStarsPurchaseScreen( context: context, starsContext: starsContext, options: options ?? [], - purpose: .starGiftOffer(requiredStars: price.amount.value), + purpose: .starGiftOffer(requiredStars: finalStars), targetPeerId: nil, customTheme: nil, completion: { [weak self, weak starsContext] stars in @@ -2411,7 +2478,7 @@ private final class GiftViewSheetContent: CombinedComponent { self.inProgress = false self.updated() - self.commitGiftBuyOffer(peer: peer, price: price, duration: duration) + self.commitGiftBuyOffer(peer: peer, price: price, duration: duration, allowPaidStars: allowPaidStars) } else { proceed() } @@ -2480,10 +2547,11 @@ private final class GiftViewSheetContent: CombinedComponent { let upgradeNextButton = Child(PlainButtonComponent.self) let upgradeTitle = Child(MultilineTextComponent.self) - let upgradeDescription = Child(BalancedTextComponent.self) + let upgradeDescription = Child(PlainButtonComponent.self) let upgradePerks = Child(List.self) let upgradeKeepName = Child(PlainButtonComponent.self) let upgradePriceButton = Child(PlainButtonComponent.self) + let variantsMeasureDescription = Child(MultilineTextComponent.self) let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: []) @@ -2687,10 +2755,10 @@ private final class GiftViewSheetContent: CombinedComponent { } headerSubject = .unique(state.justUpgraded ? state.upgradePreview?.attributes : nil, uniqueGift) } else if state.inUpgradePreview, let attributes = state.upgradePreview?.attributes { - headerHeight = 258.0 + headerHeight = 246.0 headerSubject = .preview(attributes) } else if case let .upgradePreview(attributes, _) = component.subject { - headerHeight = 258.0 + headerHeight = 246.0 headerSubject = .preview(attributes) } else if case let .wearPreview(_, attributes) = component.subject, let attributes { headerHeight = 200.0 @@ -2944,9 +3012,7 @@ private final class GiftViewSheetContent: CombinedComponent { originY += 16.0 } else if showUpgradePreview { let title: String - let description: String let uniqueText: String - let transferableText: String let tradableText: String if !incoming, case let .profileGift(peerId, _) = subject, let peer = state.peerMap[peerId] { var peerName = peer.compactDisplayTitle @@ -2954,9 +3020,7 @@ private final class GiftViewSheetContent: CombinedComponent { peerName = "\(peerName.prefix(22))โ€ฆ" } title = environment.strings.Gift_Upgrade_GiftTitle - description = environment.strings.Gift_Upgrade_GiftDescription(peerName).string uniqueText = strings.Gift_Upgrade_Unique_GiftDescription(peerName).string - transferableText = strings.Gift_Upgrade_Transferable_GiftDescription(peerName).string tradableText = strings.Gift_Upgrade_Tradable_GiftDescription(peerName).string } else if case let .upgradePreview(_, peerName) = component.subject { var peerName = peerName @@ -2964,15 +3028,11 @@ private final class GiftViewSheetContent: CombinedComponent { peerName = "\(peerName.prefix(22))โ€ฆ" } title = environment.strings.Gift_Upgrade_IncludeTitle - description = environment.strings.Gift_Upgrade_IncludeDescription(peerName).string uniqueText = strings.Gift_Upgrade_Unique_IncludeDescription - transferableText = strings.Gift_Upgrade_Transferable_IncludeDescription tradableText = strings.Gift_Upgrade_Tradable_IncludeDescription } else { title = environment.strings.Gift_Upgrade_Title - description = environment.strings.Gift_Upgrade_Description uniqueText = strings.Gift_Upgrade_Unique_Description - transferableText = strings.Gift_Upgrade_Transferable_Description tradableText = strings.Gift_Upgrade_Tradable_Description } @@ -2990,39 +3050,119 @@ private final class GiftViewSheetContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), transition: .immediate ) - let upgradeDescription = upgradeDescription.update( - component: BalancedTextComponent( - text: .plain(NSAttributedString( - string: description, - font: Font.regular(13.0), - textColor: .white, - paragraphAlignment: .center - )), - horizontalAlignment: .center, - maximumNumberOfLines: 5, - lineSpacing: 0.2, - tintColor: vibrantColor - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude), - transition: context.transition - ) - let spacing: CGFloat = 6.0 - let totalHeight: CGFloat = upgradeTitle.size.height + spacing + upgradeDescription.size.height - - headerComponents.append({ - context.add(upgradeTitle - .position(CGPoint(x: context.availableSize.width / 2.0, y: floor(212.0 - totalHeight / 2.0 + upgradeTitle.size.height / 2.0))) - .appear(.default(alpha: true)) - .disappear(.default(alpha: true)) + if case let .generic(gift) = component.subject.arguments?.gift, let upgradePreview = state.upgradePreview { + var variant1: GiftItemComponent.Subject = .starGift(gift: gift, price: "") + var variant2: GiftItemComponent.Subject = .starGift(gift: gift, price: "") + var variant3: GiftItemComponent.Subject = .starGift(gift: gift, price: "") + + var i = 0 + for attribute in upgradePreview.attributes { + if case .model = attribute { + switch i { + case 0: + variant1 = .preview(attributes: [attribute], rarity: 0) + case 1: + variant2 = .preview(attributes: [attribute], rarity: 0) + case 2: + variant3 = .preview(attributes: [attribute], rarity: 0) + default: + break + } + i += 1 + } + } + + var buttonColor: UIColor = UIColor.white.withAlphaComponent(0.16) + if let previewPatternColor = giftCompositionExternalState.previewPatternColor { + buttonColor = previewPatternColor + } + + let variantsMeasureDescription = variantsMeasureDescription.update( + component: MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Upgrade_ViewAllVariants, font: Font.semibold(13.0), textColor: .clear))), + availableSize: context.availableSize, + transition: .immediate + ) + context.add(variantsMeasureDescription + .position(CGPoint(x: -10000.0, y: -10000.0)) ) - context.add(upgradeDescription - .position(CGPoint(x: context.availableSize.width / 2.0, y: floor(212.0 + totalHeight / 2.0 - upgradeDescription.size.height / 2.0))) - .appear(.default(alpha: true)) - .disappear(.default(alpha: true)) + let upgradeDescription = upgradeDescription.update( + component: PlainButtonComponent( + content: AnyComponent( + ZStack([ + AnyComponentWithIdentity(id: "background", component: AnyComponent( + FilledRoundedRectangleComponent(color: buttonColor, cornerRadius: .minEdge, smoothCorners: false) + )), + AnyComponentWithIdentity(id: "label", component: AnyComponent(HStack([ + AnyComponentWithIdentity(id: "icon1", component: AnyComponent( + GiftItemComponent( + context: component.context, + theme: theme, + strings: strings, + peer: nil, + subject: variant1, + isPlaceholder: false, + mode: .tableIcon + ) + )), + AnyComponentWithIdentity(id: "icon2", component: AnyComponent( + GiftItemComponent( + context: component.context, + theme: theme, + strings: strings, + peer: nil, + subject: variant2, + isPlaceholder: false, + mode: .tableIcon + ) + )), + AnyComponentWithIdentity(id: "icon3", component: AnyComponent( + GiftItemComponent( + context: component.context, + theme: theme, + strings: strings, + peer: nil, + subject: variant3, + isPlaceholder: false, + mode: .tableIcon + ) + )), + AnyComponentWithIdentity(id: "text", component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Upgrade_ViewAllVariants, font: Font.semibold(13.0), textColor: .white))) + )), + AnyComponentWithIdentity(id: "arrow", component: AnyComponent( + BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: .white) + )) + ], spacing: 3.0))) + ]) + ), + action: { [weak state] in + state?.openUpgradeVariants() + }, + animateScale: false + ), + availableSize: CGSize(width: variantsMeasureDescription.size.width + 87.0, height: 24.0), + transition: context.transition ) - }) + + let spacing: CGFloat = 6.0 + let totalHeight: CGFloat = upgradeTitle.size.height + spacing + upgradeDescription.size.height + + headerComponents.append({ + context.add(upgradeTitle + .position(CGPoint(x: context.availableSize.width / 2.0, y: floor(194.0 - totalHeight / 2.0 + upgradeTitle.size.height / 2.0))) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + + context.add(upgradeDescription + .position(CGPoint(x: context.availableSize.width / 2.0, y: floor(198.0 + totalHeight / 2.0 - upgradeDescription.size.height / 2.0))) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + }) + } originY += 24.0 let textColor = theme.actionSheet.primaryTextColor @@ -3044,20 +3184,6 @@ private final class GiftViewSheetContent: CombinedComponent { )) ) ) - items.append( - AnyComponentWithIdentity( - id: "transferable", - component: AnyComponent(InfoParagraphComponent( - title: strings.Gift_Upgrade_Transferable_Title, - titleColor: textColor, - text: transferableText, - textColor: secondaryTextColor, - accentColor: linkColor, - iconName: "Premium/Collectible/Transferable", - iconColor: linkColor - )) - ) - ) items.append( AnyComponentWithIdentity( id: "tradable", @@ -3072,6 +3198,20 @@ private final class GiftViewSheetContent: CombinedComponent { )) ) ) + items.append( + AnyComponentWithIdentity( + id: "wearable", + component: AnyComponent(InfoParagraphComponent( + title: strings.Gift_Upgrade_Wearable_Title, + titleColor: textColor, + text: strings.Gift_Upgrade_Wearable_Text, + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Premium/Collectible/Wearable", + iconColor: linkColor + )) + ) + ) let perksSideInset = sideInset + 16.0 let upgradePerks = upgradePerks.update( @@ -3564,7 +3704,7 @@ private final class GiftViewSheetContent: CombinedComponent { id: AnyHashable(0), component: AnyComponent(Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, strings: strings, @@ -3597,7 +3737,7 @@ private final class GiftViewSheetContent: CombinedComponent { } else { ownerComponent = AnyComponent(Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, strings: strings, @@ -3648,7 +3788,7 @@ private final class GiftViewSheetContent: CombinedComponent { title: strings.Gift_Unique_Telegram, component: AnyComponent(Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, strings: strings, @@ -3678,7 +3818,7 @@ private final class GiftViewSheetContent: CombinedComponent { id: AnyHashable(0), component: AnyComponent(Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, strings: strings, @@ -3708,7 +3848,7 @@ private final class GiftViewSheetContent: CombinedComponent { } else { fromComponent = AnyComponent(Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, strings: strings, @@ -3733,7 +3873,7 @@ private final class GiftViewSheetContent: CombinedComponent { id: "from_anon", title: strings.Gift_View_From, component: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, strings: strings, @@ -4117,7 +4257,7 @@ private final class GiftViewSheetContent: CombinedComponent { color: theme.list.itemAccentColor )), action: { [weak state] in - state?.showAttributeInfo(tag: tag, text: strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string) + state?.openUpgradeVariants(attribute: attribute) } ).tagged(tag)) )) @@ -5164,42 +5304,50 @@ final class GiftViewSheetComponent: CombinedComponent { var headerContent: AnyComponent? if let arguments = context.component.subject.arguments, case .unique = arguments.gift, let fromPeerId = arguments.fromPeerId, var fromPeerName = arguments.fromPeerName, arguments.fromPeerId != context.component.context.account.peerId && !(arguments.fromPeerId?.isTelegramNotifications ?? false) { - let dateString = stringForMediumDate(timestamp: arguments.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat, withTime: false) - - if fromPeerName.count > 25 { - fromPeerName = "\(fromPeerName.prefix(25))โ€ฆ" + var showSenderInfo = false + if arguments.incoming { + showSenderInfo = true + } else if arguments.peerId == context.component.context.account.peerId { + showSenderInfo = true + } + if showSenderInfo { + let dateString = stringForMediumDate(timestamp: arguments.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat, withTime: false) + + if fromPeerName.count > 25 { + fromPeerName = "\(fromPeerName.prefix(25))โ€ฆ" + } + let rawString = environment.strings.Gift_View_SenderInfo(fromPeerName, dateString).string + let attributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: .white), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white), linkAttribute: { _ in return nil })) + + let context = context.component.context + headerContent = AnyComponent( + PlainButtonComponent(content: AnyComponent(HeaderContentComponent(attributedText: attributedString)), action: { + if let controller = controller(), let navigationController = controller.navigationController as? NavigationController { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: fromPeerId)) + |> deliverOnMainQueue).start(next: { [weak navigationController] peer in + guard let peer, let navigationController else { + return + } + context.sharedContext.navigateToChatController(NavigateToChatControllerParams( + navigationController: navigationController, + chatController: nil, + context: context, + chatLocation: .peer(peer), + subject: nil, + botStart: nil, + updateTextInputState: nil, + keepStack: .always, + useExisting: true, + purposefulAction: nil, + scrollToEndIfExists: false, + activateMessageSearch: nil, + animated: true + )) + }) + } + }) + ) } - let rawString = environment.strings.Gift_View_SenderInfo(fromPeerName, dateString).string - let attributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: .white), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white), linkAttribute: { _ in return nil })) - - let context = context.component.context - headerContent = AnyComponent( - PlainButtonComponent(content: AnyComponent(HeaderContentComponent(attributedText: attributedString)), action: { - if let controller = controller(), let navigationController = controller.navigationController as? NavigationController { - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: fromPeerId)) - |> deliverOnMainQueue).start(next: { [weak navigationController] peer in - guard let peer, let navigationController else { - return - } - context.sharedContext.navigateToChatController(NavigateToChatControllerParams( - navigationController: navigationController, - chatController: nil, - context: context, - chatLocation: .peer(peer), - subject: nil, - botStart: nil, - updateTextInputState: nil, - keepStack: .always, - useExisting: true, - purposefulAction: nil, - scrollToEndIfExists: false, - activateMessageSearch: nil, - animated: true - )) - }) - } - }) - ) } let sheet = sheet.update( @@ -5742,115 +5890,6 @@ func formatPercentage(_ value: Float) -> String { return String(format: "%0.1f", value).replacingOccurrences(of: ".0", with: "").replacingOccurrences(of: ",0", with: "") + "%" } -final class PeerCellComponent: Component { - let context: AccountContext - let theme: PresentationTheme - let strings: PresentationStrings - let peer: EnginePeer? - - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer?) { - self.context = context - self.theme = theme - self.strings = strings - self.peer = peer - } - - static func ==(lhs: PeerCellComponent, rhs: PeerCellComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.theme !== rhs.theme { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.peer != rhs.peer { - return false - } - return true - } - - final class View: UIView { - private let avatarNode: AvatarNode - private let text = ComponentView() - - private var component: PeerCellComponent? - private weak var state: EmptyComponentState? - - override init(frame: CGRect) { - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0)) - - super.init(frame: frame) - - self.addSubnode(self.avatarNode) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(component: PeerCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.component = component - self.state = state - - let avatarSize = CGSize(width: 22.0, height: 22.0) - let spacing: CGFloat = 6.0 - - var peerName: String - let avatarOverride: AvatarNodeImageOverride? - if let peerValue = component.peer { - peerName = peerValue.compactDisplayTitle - if peerName.count > 40 { - peerName = "\(peerName.prefix(40))โ€ฆ" - } - avatarOverride = nil - } else { - peerName = component.strings.Gift_View_HiddenName - avatarOverride = .anonymousSavedMessagesIcon(isColored: true) - } - - let avatarNaturalSize = CGSize(width: 40.0, height: 40.0) - self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer, overrideImage: avatarOverride) - self.avatarNode.bounds = CGRect(origin: .zero, size: avatarNaturalSize) - - let textSize = self.text.update( - transition: .immediate, - component: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString(string: peerName, font: Font.regular(15.0), textColor: component.peer != nil ? component.theme.list.itemAccentColor : component.theme.list.itemPrimaryTextColor, paragraphAlignment: .left)) - ) - ), - environment: {}, - containerSize: CGSize(width: availableSize.width - avatarSize.width - spacing, height: availableSize.height) - ) - - let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height) - - let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize) - self.avatarNode.frame = avatarFrame - - if let view = self.text.view { - if view.superview == nil { - self.addSubview(view) - } - let textFrame = CGRect(origin: CGPoint(x: avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) - transition.setFrame(view: view, frame: textFrame) - } - - return size - } - } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} - final class HeaderContentComponent: Component { let attributedText: NSAttributedString @@ -6165,73 +6204,6 @@ private final class HeaderButtonComponent: CombinedComponent { } } -final class AvatarComponent: Component { - let context: AccountContext - let theme: PresentationTheme - let peer: EnginePeer - - init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer) { - self.context = context - self.theme = theme - self.peer = peer - } - - static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.theme !== rhs.theme { - return false - } - if lhs.peer != rhs.peer { - return false - } - return true - } - - final class View: UIView { - private let avatarNode: AvatarNode - - private var component: AvatarComponent? - private weak var state: EmptyComponentState? - - override init(frame: CGRect) { - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 42.0)) - - super.init(frame: frame) - - self.addSubnode(self.avatarNode) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.component = component - self.state = state - - self.avatarNode.frame = CGRect(origin: .zero, size: availableSize) - self.avatarNode.setPeer( - context: component.context, - theme: component.theme, - peer: component.peer, - synchronousLoad: true - ) - - return availableSize - } - } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} - private struct GiftViewConfiguration { public static var defaultValue: GiftViewConfiguration { return GiftViewConfiguration(explorerUrl: "https://tonviewer.com") diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift index 1784432e..e9621f13 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift @@ -14,391 +14,222 @@ import Markdown import GiftItemComponent import StarsAvatarComponent import PasswordSetupUI -import OwnershipTransferController import PresentationDataUtils +import AlertComponent +import AlertTransferHeaderComponent +import AlertInputFieldComponent -private final class GiftWithdrawAlertContentNode: AlertContentNode { - private let context: AccountContext - private let strings: PresentationStrings - private var presentationTheme: PresentationTheme - private let title: String - private let text: String - private let gift: StarGift.UniqueGift - - private let titleNode: ASTextNode - private let giftView = ComponentView() - private let textNode: ASTextNode - private let arrowNode: ASImageNode - private let avatarView = ComponentView() - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init( - context: AccountContext, - theme: AlertControllerTheme, - ptheme: PresentationTheme, - strings: PresentationStrings, - gift: StarGift.UniqueGift, - title: String, - text: String, - actions: [TextAlertAction] - ) { - self.context = context - self.strings = strings - self.presentationTheme = ptheme - self.title = title - self.text = text - self.gift = gift - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 0 - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.arrowNode = ASImageNode() - self.arrowNode.displaysAsynchronously = false - self.arrowNode.displayWithoutProcessing = true - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - self.addSubnode(self.arrowNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor) - self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - linkAttribute: { url in - return ("URL", url) - } - ), textAlignment: .center) - self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let avatarSize = CGSize(width: 60.0, height: 60.0) - let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize) - - let _ = self.giftView.update( - transition: .immediate, - component: AnyComponent( - GiftItemComponent( - context: self.context, - theme: self.presentationTheme, - strings: self.strings, - peer: nil, - subject: .uniqueGift(gift: self.gift, price: nil), - mode: .thumbnail - ) - ), - environment: {}, - containerSize: avatarSize - ) - if let view = self.giftView.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = giftFrame - } - - if let arrowImage = self.arrowNode.image { - let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) - transition.updateFrame(node: self.arrowNode, frame: arrowFrame) - } - - let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize) - let _ = self.avatarView.update( - transition: .immediate, - component: AnyComponent( - StarsAvatarComponent( - context: self.context, - theme: self.presentationTheme, - peer: .transactionPeer(.fragment), - photo: nil, - media: [], - gift: nil, - backgroundColor: .clear, - size: avatarSize - ) - ), - environment: {}, - containerSize: avatarSize - ) - if let view = self.avatarView.view { - if view.superview == nil { - self.view.addSubview(view) - } - view.frame = avatarFrame - } - - origin.y += avatarSize.height + 17.0 - - let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 5.0 - - let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 10.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.vertical - if !"".isEmpty { - // Silence the warning - effectiveActionLayout = .horizontal - } - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - - let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 24.0 + insets.top + insets.bottom) - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} - -public func giftWithdrawAlertController(context: AccountContext, gift: StarGift.UniqueGift, commit: @escaping () -> Void) -> AlertController { +public func giftWithdrawAlertController( + context: AccountContext, + gift: StarGift.UniqueGift, + commit: @escaping () -> Void +) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertTransferHeaderComponent( + fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent( + GiftItemComponent( + context: context, + theme: presentationData.theme, + strings: strings, + peer: nil, + subject: .uniqueGift(gift: gift, price: nil), + mode: .thumbnail + ) + )), + toComponent: AnyComponentWithIdentity(id: "fragment", component: AnyComponent( + StarsAvatarComponent( + context: context, + theme: presentationData.theme, + peer: .transactionPeer(.fragment), + photo: nil, + media: [], + gift: nil, + backgroundColor: .clear, + size: CGSize(width: 60.0, height: 60.0) + ) + )), + type: .transfer + ) + ) + )) - let title = strings.Gift_Withdraw_Title - let text = strings.Gift_Withdraw_Text("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))").string - let buttonText = strings.Gift_Withdraw_Proceed - - var dismissImpl: ((Bool) -> Void)? - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { - dismissImpl?(true) - commit() - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - })] - - let contentNode = GiftWithdrawAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, title: title, text: text, actions: actions) - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Gift_Withdraw_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.Gift_Withdraw_Text("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))").string)) + ) + )) + + let alertController = AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + content: content, + actions: [ + .init(title: strings.Gift_Withdraw_Proceed, type: .default, action: { + commit() + }), + .init(title: strings.Common_Cancel) + ] + ) + return alertController } -public func confirmGiftWithdrawalController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, reference: StarGiftReference, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (String) -> Void) -> ViewController { - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } +public func confirmGiftWithdrawalController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + reference: StarGiftReference, + present: @escaping (ViewController, Any?) -> Void, + completion: @escaping (String) -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + let inputState = AlertInputFieldComponent.ExternalState() + + let doneIsEnabled: Signal = inputState.valueSignal + |> map { value in + return !value.isEmpty + } + + let doneInProgressPromise = ValuePromise(false) + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Gift_Withdraw_EnterPassword_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.Gift_Withdraw_EnterPassword_Text)) + ) + )) + + var applyImpl: (() -> Void)? + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder, + isSecureTextEntry: true, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) + + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) + } var dismissImpl: (() -> Void)? - var proceedImpl: (() -> Void)? - - let disposable = MetaDisposable() - - let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.Gift_Withdraw_EnterPassword_Title, text: presentationData.strings.Gift_Withdraw_EnterPassword_Text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?() - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Gift_Withdraw_EnterPassword_Done, action: { - proceedImpl?() - })]) - - contentNode.complete = { - proceedImpl?() - } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.theme = presentationData.theme - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - disposable.dispose() - } - dismissImpl = { [weak controller, weak contentNode] in - contentNode?.dismissInput() - controller?.dismissAnimated() - } - proceedImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - contentNode.updateIsChecking(true) - - let signal = context.engine.payments.requestStarGiftWithdrawalUrl(reference: reference, password: contentNode.password) - disposable.set((signal |> deliverOnMainQueue).start(next: { url in + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.Gift_Withdraw_EnterPassword_Done, type: .default, action: { + applyImpl?() + }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get()) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + applyImpl = { + doneInProgressPromise.set(true) + + let _ = (context.engine.payments.requestStarGiftWithdrawalUrl(reference: reference, password: inputState.value) + |> deliverOnMainQueue).start(next: { url in dismissImpl?() completion(url) - }, error: { [weak contentNode] error in + }, error: { error in var errorTextAndActions: (String, [TextAlertAction])? switch error { - case .invalidPassword: - contentNode?.animateError() - case .limitExceeded: - errorTextAndActions = (presentationData.strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - default: - errorTextAndActions = (presentationData.strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + case .invalidPassword: + inputState.animateError() + case .limitExceeded: + errorTextAndActions = (strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) + default: + errorTextAndActions = (strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) } - contentNode?.updateIsChecking(false) - + doneInProgressPromise.set(false) + if let (text, actions) = errorTextAndActions { dismissImpl?() present(textAlertController(context: context, title: nil, text: text, actions: actions), nil) } - })) + }) } - - return controller + dismissImpl = { [weak alertController] in + alertController?.dismiss(completion: nil) + } + return alertController } -public func giftWithdrawalController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, reference: StarGiftReference, initialError: RequestStarGiftWithdrawalError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (String) -> Void) -> ViewController { +public func giftWithdrawalController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + reference: StarGiftReference, + initialError: RequestStarGiftWithdrawalError, + present: @escaping (ViewController, Any?) -> Void, + completion: @escaping (String) -> Void +) -> ViewController { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - let theme = AlertControllerTheme(presentationData: presentationData) + let strings = presentationData.strings - var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.Gift_Withdraw_SecurityCheck, font: Font.semibold(presentationData.listsFontSize.itemListBaseFontSize), textColor: theme.primaryColor, paragraphAlignment: .center) + var title: String? = strings.Gift_Withdraw_SecurityCheck + var text = strings.Gift_Withdraw_SecurityRequirements - var text = presentationData.strings.Gift_Withdraw_SecurityRequirements - let textFontSize = presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0 - - var actions: [TextAlertAction] = [] + var actions: [AlertScreen.Action] = [ + .init(title: strings.Common_OK, type: .default) + ] switch initialError { case .requestPassword: return confirmGiftWithdrawalController(context: context, updatedPresentationData: updatedPresentationData, reference: reference, present: present, completion: completion) case .twoStepAuthTooFresh, .authSessionTooFresh: text = text + presentationData.strings.Gift_Withdraw_ComeBackLater - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] case .twoStepAuthMissing: - actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.Gift_Withdraw_SetupTwoStepAuth, action: { - let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in - if shouldDismiss { - controller.dismiss() - } - }) - present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})] + actions = [ + .init(title: strings.Gift_Withdraw_SetupTwoStepAuth, type: .default, action: { + let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in + if shouldDismiss { + controller.dismiss() + } + }) + present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }), + .init(title: strings.Common_Cancel) + ] default: title = nil - text = presentationData.strings.Login_UnknownError - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] + text = strings.Login_UnknownError } - let body = MarkdownAttributeSet(font: Font.regular(textFontSize), textColor: theme.primaryColor) - let bold = MarkdownAttributeSet(font: Font.semibold(textFontSize), textColor: theme.primaryColor) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - return richTextAlertController(context: context, title: title, text: attributedText, actions: actions) + return AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + title: title, + text: text, + actions: actions + ) } diff --git a/submodules/TelegramUI/Components/Gifts/PeerTableCellComponent/BUILD b/submodules/TelegramUI/Components/Gifts/PeerTableCellComponent/BUILD new file mode 100644 index 00000000..363e3ee0 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/PeerTableCellComponent/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "PeerTableCellComponent", + module_name = "PeerTableCellComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + "//submodules/TelegramCore", + "//submodules/Components/MultilineTextComponent", + "//submodules/AccountContext", + "//submodules/AvatarNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Gifts/PeerTableCellComponent/Sources/PeerTableCellComponent.swift b/submodules/TelegramUI/Components/Gifts/PeerTableCellComponent/Sources/PeerTableCellComponent.swift new file mode 100644 index 00000000..6ff8042f --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/PeerTableCellComponent/Sources/PeerTableCellComponent.swift @@ -0,0 +1,123 @@ +import Foundation +import UIKit +import ComponentFlow +import Display +import TelegramCore +import TelegramPresentationData +import MultilineTextComponent +import AvatarNode +import AccountContext + +public final class PeerTableCellComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let peer: EnginePeer? + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + peer: EnginePeer? + ) { + self.context = context + self.theme = theme + self.strings = strings + self.peer = peer + } + + public static func ==(lhs: PeerTableCellComponent, rhs: PeerTableCellComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.peer != rhs.peer { + return false + } + return true + } + + public final class View: UIView { + private let avatarNode: AvatarNode + private let text = ComponentView() + + private var component: PeerTableCellComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 12.0)) + + super.init(frame: frame) + + self.addSubnode(self.avatarNode) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: PeerTableCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let avatarSize = CGSize(width: 22.0, height: 22.0) + let spacing: CGFloat = 6.0 + + var peerName: String + let avatarOverride: AvatarNodeImageOverride? + if let peerValue = component.peer { + peerName = peerValue.compactDisplayTitle + if peerName.count > 40 { + peerName = "\(peerName.prefix(40))โ€ฆ" + } + avatarOverride = nil + } else { + peerName = component.strings.Gift_View_HiddenName + avatarOverride = .anonymousSavedMessagesIcon(isColored: true) + } + + let avatarNaturalSize = CGSize(width: 40.0, height: 40.0) + self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer, overrideImage: avatarOverride) + self.avatarNode.bounds = CGRect(origin: .zero, size: avatarNaturalSize) + + let textSize = self.text.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: peerName, font: Font.regular(15.0), textColor: component.peer != nil ? component.theme.list.itemAccentColor : component.theme.list.itemPrimaryTextColor, paragraphAlignment: .left)) + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - avatarSize.width - spacing, height: availableSize.height) + ) + + let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height) + + let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize) + self.avatarNode.frame = avatarFrame + + if let view = self.text.view { + if view.superview == nil { + self.addSubview(view) + } + let textFrame = CGRect(origin: CGPoint(x: avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + transition.setFrame(view: view, frame: textFrame) + } + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Gifts/TableComponent/BUILD b/submodules/TelegramUI/Components/Gifts/TableComponent/BUILD new file mode 100644 index 00000000..54dc0f8a --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/TableComponent/BUILD @@ -0,0 +1,23 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "TableComponent", + module_name = "TableComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + "//submodules/Components/MultilineTextComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Gifts/TableComponent/Sources/TableComponent.swift b/submodules/TelegramUI/Components/Gifts/TableComponent/Sources/TableComponent.swift new file mode 100644 index 00000000..af189d8f --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/TableComponent/Sources/TableComponent.swift @@ -0,0 +1,375 @@ +import Foundation +import UIKit +import ComponentFlow +import Display +import TelegramPresentationData +import MultilineTextComponent + +public final class TableComponent: CombinedComponent { + public class Item: Equatable { + public enum TitleFont { + case regular + case bold + } + + public let id: AnyHashable + public let title: String? + public let titleFont: TitleFont + public let hasBackground: Bool + public let component: AnyComponent + public let insets: UIEdgeInsets? + + public init( + id: IdType, + title: String?, + titleFont: TitleFont = .regular, + hasBackground: Bool = false, + component: AnyComponent, + insets: UIEdgeInsets? = nil + ) { + self.id = AnyHashable(id) + self.title = title + self.titleFont = titleFont + self.hasBackground = hasBackground + self.component = component + self.insets = insets + } + + public static func == (lhs: Item, rhs: Item) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.titleFont != rhs.titleFont { + return false + } + if lhs.hasBackground != rhs.hasBackground { + return false + } + if lhs.component != rhs.component { + return false + } + if lhs.insets != rhs.insets { + return false + } + return true + } + } + + private let theme: PresentationTheme + private let items: [Item] + private let semiTransparent: Bool + + public init(theme: PresentationTheme, items: [Item], semiTransparent: Bool = false) { + self.theme = theme + self.items = items + self.semiTransparent = semiTransparent + } + + public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.items != rhs.items { + return false + } + if lhs.semiTransparent != rhs.semiTransparent { + return false + } + return true + } + + public final class State: ComponentState { + var cachedLastBackgroundImage: (UIImage, PresentationTheme)? + var cachedLeftColumnImage: (UIImage, PresentationTheme)? + var cachedBorderImage: (UIImage, PresentationTheme)? + } + + public func makeState() -> State { + return State() + } + + public static var body: Body { + let leftColumnBackground = Child(Image.self) + let lastBackground = Child(Image.self) + let verticalBorder = Child(Rectangle.self) + let titleChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) + let valueChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) + let borderChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) + let outerBorder = Child(Image.self) + + return { context in + let verticalPadding: CGFloat = 11.0 + let horizontalPadding: CGFloat = 12.0 + let borderWidth: CGFloat = 1.0 + + let borderColor: UIColor + let secondaryBackgroundColor: UIColor + if context.component.semiTransparent { + borderColor = context.component.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1) + secondaryBackgroundColor = context.component.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.05) + } else { + let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor + borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6) + secondaryBackgroundColor = context.component.theme.overallDarkAppearance ? context.component.theme.list.itemModalBlocksBackgroundColor : context.component.theme.list.itemInputField.backgroundColor + } + + var leftColumnWidth: CGFloat = 0.0 + + var updatedTitleChildren: [Int: _UpdatedChildComponent] = [:] + var updatedValueChildren: [(_UpdatedChildComponent, UIEdgeInsets)] = [] + var updatedBorderChildren: [_UpdatedChildComponent] = [] + + var i = 0 + for item in context.component.items { + guard let title = item.title else { + i += 1 + continue + } + let titleChild = titleChildren[item.id].update( + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: title, font: item.titleFont == .bold ? Font.semibold(15.0) : Font.regular(15.0), textColor: context.component.theme.list.itemPrimaryTextColor)) + )), + availableSize: context.availableSize, + transition: context.transition + ) + updatedTitleChildren[i] = titleChild + + if titleChild.size.width > leftColumnWidth { + leftColumnWidth = titleChild.size.width + } + i += 1 + } + + leftColumnWidth = max(100.0, leftColumnWidth + horizontalPadding * 2.0) + let rightColumnWidth = context.availableSize.width - leftColumnWidth + + i = 0 + var rowHeights: [Int: CGFloat] = [:] + var totalHeight: CGFloat = 0.0 + var innerTotalHeight: CGFloat = 0.0 + var innerTotalOffset: CGFloat = 0.0 + var hasRowBackground = false + var rowBackgroundIsLast = false + var hasStraightSide = false + + for item in context.component.items { + let insets: UIEdgeInsets + if let customInsets = item.insets { + insets = customInsets + } else { + insets = UIEdgeInsets(top: 0.0, left: horizontalPadding, bottom: 0.0, right: horizontalPadding) + } + + var titleHeight: CGFloat = 0.0 + if let titleChild = updatedTitleChildren[i] { + titleHeight = titleChild.size.height + } + + let availableValueWidth: CGFloat + if titleHeight > 0.0 { + availableValueWidth = rightColumnWidth + } else { + availableValueWidth = context.availableSize.width + } + + let valueChild = valueChildren[item.id].update( + component: item.component, + availableSize: CGSize(width: availableValueWidth - insets.left - insets.right, height: context.availableSize.height), + transition: context.transition + ) + updatedValueChildren.append((valueChild, insets)) + + let rowHeight = max(40.0, max(titleHeight, valueChild.size.height) + verticalPadding * 2.0) + rowHeights[i] = rowHeight + totalHeight += rowHeight + if titleHeight > 0.0 { + innerTotalHeight += rowHeight + } else if i == 0 { + innerTotalOffset += rowHeight + } + + if i < context.component.items.count - 1 { + let borderChild = borderChildren[item.id].update( + component: AnyComponent(Rectangle(color: borderColor)), + availableSize: CGSize(width: context.availableSize.width, height: borderWidth), + transition: context.transition + ) + updatedBorderChildren.append(borderChild) + } + + if item.hasBackground { + if i != 0 { + rowBackgroundIsLast = true + } + hasRowBackground = true + } + if item.title == nil { + if i != 0 { + rowBackgroundIsLast = true + } + hasStraightSide = true + } + + i += 1 + } + + let borderRadius: CGFloat = 14.0 + + if hasRowBackground { + let lastBackgroundImage: UIImage + if let (currentImage, theme) = context.state.cachedLastBackgroundImage, theme === context.component.theme { + lastBackgroundImage = currentImage + } else { + lastBackgroundImage = generateImage(CGSize(width: borderRadius * 2.0 + 4.0, height: borderRadius * 2.0 + 4.0), rotatedContext: { size, context in + let bounds = CGRect(origin: .zero, size: CGSize(width: size.width, height: size.height + borderRadius)) + context.clear(bounds) + + let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0).insetBy(dx: 0.0, dy: rowBackgroundIsLast ? -borderRadius * 2.0 : 0.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil) + context.setFillColor(secondaryBackgroundColor.cgColor) + context.addPath(path) + context.fillPath() + })!.stretchableImage(withLeftCapWidth: Int(borderRadius), topCapHeight: Int(borderRadius)) + context.state.cachedLastBackgroundImage = (lastBackgroundImage, context.component.theme) + } + + let lastRowHeight: CGFloat + let position: CGFloat + if !rowBackgroundIsLast { + lastRowHeight = rowHeights[0] ?? 0 + position = lastRowHeight / 2.0 + } else { + lastRowHeight = rowHeights[i - 1] ?? 0 + position = totalHeight - lastRowHeight / 2.0 + } + let lastBackground = lastBackground.update( + component: Image(image: lastBackgroundImage), + availableSize: CGSize(width: context.availableSize.width, height: lastRowHeight), + transition: context.transition + ) + + context.add( + lastBackground + .position(CGPoint(x: context.availableSize.width / 2.0, y: position)) + ) + } + + let leftColumnImage: UIImage + if let (currentImage, theme) = context.state.cachedLeftColumnImage, theme === context.component.theme { + leftColumnImage = currentImage + } else { + leftColumnImage = generateImage(CGSize(width: borderRadius * 2.0 + 4.0, height: borderRadius * 2.0 + 4.0), rotatedContext: { size, context in + var bounds = CGRect(origin: .zero, size: CGSize(width: size.width + borderRadius, height: size.height)) + context.clear(bounds) + + var offset: CGFloat = 0.0 + if hasStraightSide { + offset = rowBackgroundIsLast ? 0.0 : -borderRadius + + bounds.origin.y += offset + bounds.size.height += borderRadius + } + + let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil) + context.setFillColor(secondaryBackgroundColor.cgColor) + context.addPath(path) + context.fillPath() + })!.stretchableImage(withLeftCapWidth: Int(borderRadius), topCapHeight: Int(borderRadius)) + context.state.cachedLeftColumnImage = (leftColumnImage, context.component.theme) + } + + let leftColumnBackground = leftColumnBackground.update( + component: Image(image: leftColumnImage), + availableSize: CGSize(width: leftColumnWidth, height: innerTotalHeight), + transition: context.transition + ) + context.add(leftColumnBackground + .position(CGPoint(x: leftColumnWidth / 2.0, y: innerTotalOffset + innerTotalHeight / 2.0)) + ) + + let borderImage: UIImage + if let (currentImage, theme) = context.state.cachedBorderImage, theme === context.component.theme { + borderImage = currentImage + } else { + borderImage = generateImage(CGSize(width: borderRadius * 2.0 + 4.0, height: borderRadius * 2.0 + 4.0), rotatedContext: { size, context in + let bounds = CGRect(origin: .zero, size: size) + context.clear(bounds) + + let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil) + context.setBlendMode(.clear) + context.addPath(path) + context.fillPath() + + context.setBlendMode(.normal) + context.setStrokeColor(borderColor.cgColor) + context.setLineWidth(borderWidth) + context.addPath(path) + context.strokePath() + })!.stretchableImage(withLeftCapWidth: Int(borderRadius), topCapHeight: Int(borderRadius)) + context.state.cachedBorderImage = (borderImage, context.component.theme) + } + + let outerBorder = outerBorder.update( + component: Image(image: borderImage), + availableSize: CGSize(width: context.availableSize.width, height: totalHeight), + transition: context.transition + ) + context.add(outerBorder + .position(CGPoint(x: context.availableSize.width / 2.0, y: totalHeight / 2.0)) + ) + + let verticalBorder = verticalBorder.update( + component: Rectangle(color: borderColor), + availableSize: CGSize(width: borderWidth, height: innerTotalHeight), + transition: context.transition + ) + context.add( + verticalBorder + .position(CGPoint(x: leftColumnWidth - borderWidth / 2.0, y: innerTotalOffset + innerTotalHeight / 2.0)) + ) + + i = 0 + var originY: CGFloat = 0.0 + for (valueChild, valueInsets) in updatedValueChildren { + let rowHeight = rowHeights[i] ?? 0.0 + + let valueFrame: CGRect + if let titleChild = updatedTitleChildren[i] { + let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size) + context.add(titleChild + .position(titleFrame.center) + ) + valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + valueInsets.left, y: originY + verticalPadding), size: valueChild.size) + } else { + if hasRowBackground && rowBackgroundIsLast { + valueFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - valueChild.size.width) / 2.0), y: originY + verticalPadding), size: valueChild.size) + } else { + valueFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: valueChild.size) + } + } + + context.add(valueChild + .position(valueFrame.center) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + + if i < updatedBorderChildren.count { + let borderChild = updatedBorderChildren[i] + context.add(borderChild + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + rowHeight - borderWidth / 2.0)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + } + + originY += rowHeight + i += 1 + } + + return CGSize(width: context.availableSize.width, height: totalHeight) + } + } +} diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/BUILD b/submodules/TelegramUI/Components/GlassBackgroundComponent/BUILD index 216445b2..03e0c56d 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/BUILD +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/BUILD @@ -15,6 +15,7 @@ swift_library( "//submodules/Components/ComponentDisplayAdapters", "//submodules/UIKitRuntimeUtils", "//submodules/AppBundle", + "//submodules/TelegramUI/Components/MeshTransform", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index 4c1852d9..ce271441 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -292,16 +292,18 @@ public class GlassBackgroundView: UIView { public let isDark: Bool public let tintColor: TintColor public let isInteractive: Bool + public let isVisible: Bool - init(shape: Shape, isDark: Bool, tintColor: TintColor, isInteractive: Bool) { + init(shape: Shape, isDark: Bool, tintColor: TintColor, isInteractive: Bool, isVisible: Bool) { self.shape = shape self.isDark = isDark self.tintColor = tintColor self.isInteractive = isInteractive + self.isVisible = isVisible } } - private let backgroundNode: NavigationBackgroundNode? + private let legacyView: LegacyGlassView? private let nativeView: UIVisualEffectView? private let nativeViewClippingContext: ClippingShapeContext? @@ -330,7 +332,7 @@ public class GlassBackgroundView: UIView { public override init(frame: CGRect) { if #available(iOS 26.0, *), !GlassBackgroundView.useCustomGlassImpl { - self.backgroundNode = nil + self.legacyView = nil let glassEffect = UIGlassEffect(style: .regular) glassEffect.isInteractive = false @@ -346,8 +348,7 @@ public class GlassBackgroundView: UIView { self.foregroundView = nil self.shadowView = nil } else { - let backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 8.0) - self.backgroundNode = backgroundNode + self.legacyView = LegacyGlassView(frame: CGRect()) self.nativeView = nil self.nativeViewClippingContext = nil self.nativeParamsView = nil @@ -375,8 +376,8 @@ public class GlassBackgroundView: UIView { if let nativeParamsView = self.nativeParamsView { self.addSubview(nativeParamsView) } - if let backgroundNode = self.backgroundNode { - self.addSubview(backgroundNode.view) + if let legacyView = self.legacyView { + self.addSubview(legacyView) } if let foregroundView = self.foregroundView { self.addSubview(foregroundView) @@ -390,6 +391,15 @@ public class GlassBackgroundView: UIView { } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.isUserInteractionEnabled { + return nil + } + if self.isHidden { + return nil + } + if self.alpha == 0.0 { + return nil + } if let nativeView = self.nativeView { if let result = nativeView.hitTest(self.convert(point, to: nativeView), with: event) { return result @@ -402,11 +412,11 @@ public class GlassBackgroundView: UIView { return nil } - public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, transition: ComponentTransition) { - self.update(size: size, shape: .roundedRect(cornerRadius: cornerRadius), isDark: isDark, tintColor: tintColor, isInteractive: isInteractive, transition: transition) + public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, isVisible: Bool = true, transition: ComponentTransition) { + self.update(size: size, shape: .roundedRect(cornerRadius: cornerRadius), isDark: isDark, tintColor: tintColor, isInteractive: isInteractive, isVisible: isVisible, transition: transition) } - public func update(size: CGSize, shape: Shape, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, transition: ComponentTransition) { + public func update(size: CGSize, shape: Shape, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, isVisible: Bool = true, transition: ComponentTransition) { if let nativeView = self.nativeView, let nativeViewClippingContext = self.nativeViewClippingContext, (nativeView.bounds.size != size || nativeViewClippingContext.shape != shape) { nativeViewClippingContext.update(shape: shape, size: size, transition: transition) @@ -416,15 +426,15 @@ public class GlassBackgroundView: UIView { let nativeFrame = CGRect(origin: CGPoint(), size: size) transition.setFrame(view: nativeView, frame: nativeFrame) } + nativeView.overrideUserInterfaceStyle = isDark ? .dark : .light } - if let backgroundNode = self.backgroundNode { - backgroundNode.updateColor(color: .clear, forceKeepBlur: tintColor.color.alpha != 1.0, transition: transition.containedViewLayoutTransition) - + if let legacyView = self.legacyView { switch shape { case let .roundedRect(cornerRadius): - backgroundNode.update(size: size, cornerRadius: cornerRadius, transition: transition.containedViewLayoutTransition) + legacyView.update(size: size, cornerRadius: cornerRadius, transition: transition) } - transition.setFrame(view: backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size)) + transition.setFrame(view: legacyView, frame: CGRect(origin: CGPoint(), size: size)) + transition.setAlpha(view: legacyView, alpha: isVisible ? 1.0 : 0.0) } let shadowInset: CGFloat = 32.0 @@ -468,7 +478,7 @@ public class GlassBackgroundView: UIView { innerBackgroundView.removeFromSuperview() } - let params = Params(shape: shape, isDark: isDark, tintColor: tintColor, isInteractive: isInteractive) + let params = Params(shape: shape, isDark: isDark, tintColor: tintColor, isInteractive: isInteractive, isVisible: isVisible) if self.params != params { self.params = params @@ -491,36 +501,73 @@ public class GlassBackgroundView: UIView { context.setBlendMode(.copy) context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), size: CGSize(width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0))) })?.stretchableImage(withLeftCapWidth: Int(shadowInset + outerCornerRadius), topCapHeight: Int(shadowInset + outerCornerRadius)) + transition.setAlpha(view: shadowView, alpha: isVisible ? 1.0 : 0.0) } if let foregroundView = self.foregroundView { - foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: outerCornerRadius * 2.0, height: outerCornerRadius * 2.0), inset: shadowInset, isDark: isDark, fillColor: tintColor.color) + let fillColor: UIColor + switch tintColor.kind { + case .panel: + if isDark { + fillColor = UIColor(white: 1.0, alpha: 1.0).mixedWith(.black, alpha: 1.0 - 0.11).withAlphaComponent(0.85) + } else { + fillColor = UIColor(white: 1.0, alpha: 0.7) + } + case .custom: + fillColor = tintColor.color + } + foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: outerCornerRadius * 2.0, height: outerCornerRadius * 2.0), inset: shadowInset, isDark: isDark, fillColor: fillColor) + transition.setAlpha(view: foregroundView, alpha: isVisible ? 1.0 : 0.0) } else { if let nativeParamsView = self.nativeParamsView, let nativeView = self.nativeView { if #available(iOS 26.0, *) { - let glassEffect = UIGlassEffect(style: .regular) - switch tintColor.kind { - case .panel: - glassEffect.tintColor = UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.1) - case .custom: - glassEffect.tintColor = tintColor.color - } - glassEffect.isInteractive = params.isInteractive + var glassEffect: UIGlassEffect? - if transition.animation.isImmediate { - nativeView.effect = glassEffect + if isVisible { + let glassEffectValue = UIGlassEffect(style: .regular) + switch tintColor.kind { + case .panel: + if isDark { + glassEffectValue.tintColor = UIColor(white: 1.0, alpha: 0.025) + } else { + glassEffectValue.tintColor = UIColor(white: 1.0, alpha: 0.1) + } + case .custom: + glassEffectValue.tintColor = tintColor.color + } + glassEffectValue.isInteractive = params.isInteractive + glassEffect = glassEffectValue + } + + if glassEffect == nil { + if nativeView.effect is UIGlassEffect { + if transition.animation.isImmediate { + nativeView.effect = nil + } else { + UIView.animate { + nativeView.effect = nil + } + } + } } else { - UIView.animate(withDuration: 0.2, animations: { + if transition.animation.isImmediate { nativeView.effect = glassEffect - }) + } else { + if let glassEffect, let currentEffect = nativeView.effect as? UIGlassEffect, currentEffect.tintColor == glassEffect.tintColor, currentEffect.isInteractive == glassEffect.isInteractive { + } else { + UIView.animate { + nativeView.effect = glassEffect + } + } + } } if isDark { nativeParamsView.lumaMin = 0.0 nativeParamsView.lumaMax = 0.15 } else { - nativeParamsView.lumaMin = 0.6 - nativeParamsView.lumaMax = 0.61 + nativeParamsView.lumaMin = 0.8 + nativeParamsView.lumaMax = 0.801 } } } @@ -537,6 +584,10 @@ public class GlassBackgroundView: UIView { } transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size)) } + + override public func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + } } public final class GlassBackgroundContainerView: UIView { @@ -595,9 +646,25 @@ public final class GlassBackgroundContainerView: UIView { } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.alpha.isZero { + return nil + } + if self.isHidden { + return nil + } + if !self.isUserInteractionEnabled { + return nil + } + for view in self.contentView.subviews.reversed() { + if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled { + return result + } + } + guard let result = self.contentView.hitTest(point, with: event) else { return nil } + return result } @@ -609,8 +676,8 @@ public final class GlassBackgroundContainerView: UIView { nativeParamsView.lumaMin = 0.0 nativeParamsView.lumaMax = 0.15 } else { - nativeParamsView.lumaMin = 0.6 - nativeParamsView.lumaMax = 0.61 + nativeParamsView.lumaMin = 0.8 + nativeParamsView.lumaMax = 0.801 } transition.setFrame(view: nativeView, frame: CGRect(origin: CGPoint(), size: size)) @@ -618,6 +685,10 @@ public final class GlassBackgroundContainerView: UIView { transition.setFrame(view: legacyView, frame: CGRect(origin: CGPoint(), size: size)) } } + + override public func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + } } private extension CGContext { @@ -736,8 +807,8 @@ public extension GlassBackgroundView { } } - addShadow(context, true, CGPoint(), 10.0, 0.0, UIColor(white: 0.0, alpha: 0.06), .normal) - addShadow(context, true, CGPoint(), 20.0, 0.0, UIColor(white: 0.0, alpha: 0.06), .normal) + addShadow(context, true, CGPoint(), 30.0, 0.0, UIColor(white: 0.0, alpha: 0.045), .normal) + addShadow(context, true, CGPoint(), 20.0, 0.0, UIColor(white: 0.0, alpha: 0.01), .normal) var a: CGFloat = 0.0 var b: CGFloat = 0.0 @@ -745,7 +816,7 @@ public extension GlassBackgroundView { fillColor.getHue(nil, saturation: &s, brightness: &b, alpha: &a) let innerImage: UIImage - if size == CGSize(width: 40.0 + inset * 2.0, height: 40.0 + inset * 2.0), b >= 0.2 { + /*if size == CGSize(width: 40.0 + inset * 2.0, height: 40.0 + inset * 2.0), b >= 0.2 { innerImage = UIGraphicsImageRenderer(size: size).image { ctx in let context = ctx.cgContext @@ -798,10 +869,78 @@ public extension GlassBackgroundView { addShadow(context, false, CGPoint(x: 0.64, y: 0.64), 1.2, 0.0, UIColor(white: 1.0, alpha: edgeAlpha), .normal) } } - } + }*/ - context.addEllipse(in: CGRect(origin: CGPoint(x: inset, y: inset), size: innerSize)) - context.clip() + innerImage = UIGraphicsImageRenderer(size: size).image { ctx in + let context = ctx.cgContext + + context.setFillColor(fillColor.cgColor) + var ellipseRect = CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset) + context.fillEllipse(in: ellipseRect) + + let lineWidth: CGFloat = isDark ? 0.8 : 0.8 + let strokeColor: UIColor + let blendMode: CGBlendMode + let baseAlpha: CGFloat = isDark ? 0.3 : 0.6 + + if s == 0.0 && abs(a - 0.7) < 0.1 && !isDark { + blendMode = .normal + strokeColor = UIColor(white: 1.0, alpha: baseAlpha) + } else if s <= 0.3 && !isDark { + blendMode = .normal + strokeColor = UIColor(white: 1.0, alpha: 0.7 * baseAlpha) + } else if b >= 0.2 { + let maxAlpha: CGFloat = isDark ? 0.7 : 0.8 + blendMode = .overlay + strokeColor = UIColor(white: 1.0, alpha: max(0.5, min(1.0, maxAlpha * s)) * baseAlpha) + } else { + blendMode = .normal + strokeColor = UIColor(white: 1.0, alpha: 0.5 * baseAlpha) + } + + context.setStrokeColor(strokeColor.cgColor) + ellipseRect = CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset) + context.addEllipse(in: ellipseRect) + context.clip() + + ellipseRect = CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5) + + context.setBlendMode(blendMode) + + let radius = ellipseRect.height * 0.5 + let smallerRadius = radius - lineWidth * 1.33 + context.move(to: CGPoint(x: ellipseRect.minX, y: ellipseRect.minY + radius)) + // Top-left corner (regular radius) + context.addArc(tangent1End: CGPoint(x: ellipseRect.minX, y: ellipseRect.minY), tangent2End: CGPoint(x: ellipseRect.minX + radius, y: ellipseRect.minY), radius: radius) + context.addLine(to: CGPoint(x: ellipseRect.maxX - smallerRadius, y: ellipseRect.minY)) + // Top-right corner (smaller radius) + context.addArc(tangent1End: CGPoint(x: ellipseRect.maxX, y: ellipseRect.minY), tangent2End: CGPoint(x: ellipseRect.maxX, y: ellipseRect.minY + smallerRadius), radius: smallerRadius) + context.addLine(to: CGPoint(x: ellipseRect.maxX, y: ellipseRect.maxY - radius)) + // Bottom-right corner (regular radius) + context.addArc(tangent1End: CGPoint(x: ellipseRect.maxX, y: ellipseRect.maxY), tangent2End: CGPoint(x: ellipseRect.maxX - radius, y: ellipseRect.maxY), radius: radius) + context.addLine(to: CGPoint(x: ellipseRect.minX + smallerRadius, y: ellipseRect.maxY)) + // Bottom-left corner (smaller radius) + context.addArc(tangent1End: CGPoint(x: ellipseRect.minX, y: ellipseRect.maxY), tangent2End: CGPoint(x: ellipseRect.minX, y: ellipseRect.maxY - smallerRadius), radius: smallerRadius) + context.closePath() + context.strokePath() + + context.resetClip() + context.setBlendMode(.normal) + + //let image = makeInnerShadowPillImageExact(size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), scale: UIScreenScale, glossColor: UIColor(white: 1.0, alpha: 1.0), borderWidth: 1.33) + /*let image = generateCircleImage(diameter: size.width - inset * 2.0, lineWidth: 0.5, color: UIColor(white: 1.0, alpha: 1.0))! + + if s == 0.0 && abs(a - 0.7) < 0.1 && !isDark { + image.draw(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset), blendMode: .normal, alpha: 1.0) + } else if s <= 0.3 && !isDark { + image.draw(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset), blendMode: .normal, alpha: 0.7) + } else if b >= 0.2 { + let maxAlpha: CGFloat = isDark ? 0.7 : 0.8 + image.draw(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset), blendMode: .overlay, alpha: max(0.5, min(1.0, maxAlpha * s))) + } else { + image.draw(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset), blendMode: .normal, alpha: 0.5) + }*/ + } innerImage.draw(in: CGRect(origin: CGPoint(), size: size)) }.stretchableImage(withLeftCapWidth: Int(size.width * 0.5), topCapHeight: Int(size.height * 0.5)) } @@ -892,12 +1031,20 @@ public final class GlassBackgroundComponent: Component { private let cornerRadius: CGFloat private let isDark: Bool private let tintColor: GlassBackgroundView.TintColor + private let isInteractive: Bool - public init(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: GlassBackgroundView.TintColor) { + public init( + size: CGSize, + cornerRadius: CGFloat, + isDark: Bool, + tintColor: GlassBackgroundView.TintColor, + isInteractive: Bool = false + ) { self.size = size self.cornerRadius = cornerRadius self.isDark = isDark self.tintColor = tintColor + self.isInteractive = isInteractive } public static func == (lhs: GlassBackgroundComponent, rhs: GlassBackgroundComponent) -> Bool { @@ -913,12 +1060,15 @@ public final class GlassBackgroundComponent: Component { if lhs.tintColor != rhs.tintColor { return false } + if lhs.isInteractive != rhs.isInteractive { + return false + } return true } public final class View: GlassBackgroundView { func update(component: GlassBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.update(size: component.size, cornerRadius: component.cornerRadius, isDark: component.isDark, tintColor: component.tintColor, transition: transition) + self.update(size: component.size, cornerRadius: component.cornerRadius, isDark: component.isDark, tintColor: component.tintColor, isInteractive: component.isInteractive, transition: transition) return component.size } diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift new file mode 100644 index 00000000..b688f816 --- /dev/null +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift @@ -0,0 +1,178 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import MeshTransform + +private let backdropLayerClass: NSObject? = { + let name = ("CA" as NSString).appendingFormat("BackdropLayer") + if let cls = NSClassFromString(name as String) as AnyObject as? NSObject { + return cls + } + return nil +}() + +@inline(__always) +private func getMethod(object: NSObject, selector: String) -> T? { + guard let method = object.method(for: NSSelectorFromString(selector)) else { + return nil + } + return unsafeBitCast(method, to: T.self) +} + +private var cachedBackdropLayerAllocMethod: (@convention(c) (AnyObject, Selector) -> NSObject?, Selector)? +private func invokeBackdropLayerCreateMethod() -> NSObject? { + guard let backdropLayerClass = backdropLayerClass else { + return nil + } + if let cachedBackdropLayerAllocMethod { + return cachedBackdropLayerAllocMethod.0(backdropLayerClass, cachedBackdropLayerAllocMethod.1) + } else { + let method: (@convention(c) (AnyObject, Selector) -> NSObject?)? = getMethod(object: backdropLayerClass, selector: "alloc") + if let method { + let selector = NSSelectorFromString("alloc") + cachedBackdropLayerAllocMethod = (method, selector) + return method(backdropLayerClass, selector) + } else { + return nil + } + } +} + +private var cachedBackdropLayerInitMethod: (@convention(c) (NSObject, Selector) -> NSObject?, Selector)? +private func invokeBackdropLayerInitMethod(object: NSObject) -> NSObject? { + if let cachedBackdropLayerInitMethod { + return cachedBackdropLayerInitMethod.0(object, cachedBackdropLayerInitMethod.1) + } else { + let method: (@convention(c) (AnyObject, Selector) -> NSObject?)? = getMethod(object: object, selector: "init") + if let method { + let selector = NSSelectorFromString("init") + cachedBackdropLayerInitMethod = (method, selector) + return method(object, selector) + } else { + return nil + } + } +} + +private var cachedBackdropLayerSetScaleMethod: (@convention(c) (NSObject, Selector, Double) -> Void, Selector)? +private func invokeBackdropLayerSetScaleMethod(object: NSObject, scale: Double) { + if let cachedBackdropLayerSetScaleMethod { + cachedBackdropLayerSetScaleMethod.0(object, cachedBackdropLayerSetScaleMethod.1, scale) + } else { + let method: (@convention(c) (AnyObject, Selector, Double) -> Void)? = getMethod(object: object, selector: "setScale:") + if let method { + let selector = NSSelectorFromString("setScale:") + cachedBackdropLayerSetScaleMethod = (method, selector) + return method(object, selector, scale) + } + } +} + +private final class BackdropLayerDelegate: NSObject, CALayerDelegate { + func action(for layer: CALayer, forKey event: String) -> CAAction? { + return nullAction + } +} + +final class LegacyGlassView: UIView { + private struct Params: Equatable { + let size: CGSize + let cornerRadius: CGFloat + + init(size: CGSize, cornerRadius: CGFloat) { + self.size = size + self.cornerRadius = cornerRadius + } + } + + private var params: Params? + + private let backdropLayer: CALayer? + private let backdropLayerDelegate: BackdropLayerDelegate + + override init(frame: CGRect) { + self.backdropLayerDelegate = BackdropLayerDelegate() + self.backdropLayer = invokeBackdropLayerCreateMethod().flatMap(invokeBackdropLayerInitMethod) as? CALayer + + super.init(frame: frame) + + self.layer.cornerCurve = .circular + self.clipsToBounds = true + + if let backdropLayer = self.backdropLayer { + self.layer.addSublayer(backdropLayer) + backdropLayer.delegate = self.backdropLayerDelegate + + let blur: CGFloat + let scale: CGFloat + + blur = 2.0 + scale = 1.0 + + invokeBackdropLayerSetScaleMethod(object: backdropLayer, scale: scale) + backdropLayer.rasterizationScale = scale + + if let blurFilter = CALayer.blur() { + blurFilter.setValue(blur as NSNumber, forKey: "inputRadius") + backdropLayer.filters = [blurFilter] + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) { + let params = Params(size: size, cornerRadius: cornerRadius) + if self.params == params { + return + } + self.params = params + + guard let backdropLayer = self.backdropLayer else { + return + } + + transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius) + transition.setFrame(layer: backdropLayer, frame: CGRect(origin: CGPoint(), size: size)) + + if !"".isEmpty { + let size = CGSize(width: max(1.0, size.width), height: max(1.0, size.height)) + let cornerRadius = min(min(size.width, size.height) * 0.5, cornerRadius) + let displacementMagnitudePoints: CGFloat = 20.0 + let displacementMagnitudeU = displacementMagnitudePoints / size.width + let displacementMagnitudeV = displacementMagnitudePoints / size.height + let outerEdgeDistance = 2.0 + + if let displacementMap = generateDisplacementMap(size: size, cornerRadius: cornerRadius, edgeDistance: min(12.0, cornerRadius), scale: 1.0) { + let meshTransform = generateGlassMeshFromDisplacementMap( + size: size, + cornerRadius: cornerRadius, + displacementMap: displacementMap, + displacementMagnitudeU: displacementMagnitudeU, + displacementMagnitudeV: displacementMagnitudeV, + cornerResolution: 12, + outerEdgeDistance: outerEdgeDistance, + bezier: DisplacementBezier( + x1: 0.816137566137566, + y1: 0.20502645502645533, + x2: 0.5806878306878306, + y2: 0.873015873015873 + ) + ).mesh.makeValue() + + if let meshTransform { + if !transition.animation.isImmediate, let previousTransform = backdropLayer.value(forKey: "meshTransform") as? NSObject { + backdropLayer.removeAnimation(forKey: "meshTransform") + backdropLayer.setValue(meshTransform, forKey: "meshTransform") + transition.animateMeshTransform(layer: backdropLayer, from: previousTransform, to: meshTransform) + } else { + backdropLayer.setValue(meshTransform, forKey: "meshTransform") + } + } + } + } + } +} diff --git a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift index 6b012d10..daa97d1b 100644 --- a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift +++ b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift @@ -13,29 +13,35 @@ public final class GlassBarButtonComponent: Component { } public let size: CGSize? - public let backgroundColor: UIColor + public let backgroundColor: UIColor? public let isDark: Bool public let state: DisplayState? public let isEnabled: Bool + public let animateScale: Bool public let component: AnyComponentWithIdentity public let action: ((UIView) -> Void)? + public let tag: AnyObject? public init( size: CGSize?, - backgroundColor: UIColor, + backgroundColor: UIColor?, isDark: Bool, state: DisplayState? = nil, isEnabled: Bool = true, + animateScale: Bool = true, component: AnyComponentWithIdentity, - action: ((UIView) -> Void)? + action: ((UIView) -> Void)?, + tag: AnyObject? = nil ) { self.size = size self.backgroundColor = backgroundColor self.isDark = isDark self.state = state self.isEnabled = isEnabled + self.animateScale = animateScale self.component = component self.action = action + self.tag = tag } public static func ==(lhs: GlassBarButtonComponent, rhs: GlassBarButtonComponent) -> Bool { @@ -54,14 +60,30 @@ public final class GlassBarButtonComponent: Component { if lhs.isEnabled != rhs.isEnabled { return false } + if lhs.animateScale != rhs.animateScale { + return false + } if lhs.component != rhs.component { return false } + if lhs.tag !== rhs.tag { + return false + } return true } - public final class View: HighlightTrackingButton { - private let containerView: UIView + public final class View: UIView, ComponentTaggedView { + public func matches(tag: Any) -> Bool { + if let component = self.component, let componentTag = component.tag { + let tag = tag as AnyObject + if componentTag === tag { + return true + } + } + return false + } + + private let containerView: HighlightTrackingButton private let genericContainerView: UIView private let genericBackgroundView: SimpleGlassView private let glassContainerView: UIView @@ -71,26 +93,24 @@ public final class GlassBarButtonComponent: Component { private var component: GlassBarButtonComponent? public override init(frame: CGRect) { - self.containerView = UIView() + self.containerView = HighlightTrackingButton() self.genericContainerView = UIView() self.genericBackgroundView = SimpleGlassView() self.glassContainerView = UIView() super.init(frame: frame) - self.containerView.isUserInteractionEnabled = false self.containerView.layer.rasterizationScale = UIScreenScale - self.addSubview(self.containerView) - self.containerView.addSubview(self.genericContainerView) - self.containerView.addSubview(self.glassContainerView) + self.addSubview(self.genericContainerView) + self.addSubview(self.glassContainerView) self.genericContainerView.addSubview(self.genericBackgroundView) - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.containerView.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - self.highligthedChanged = { [weak self] highlighted in - guard let self else { + self.containerView.highligthedChanged = { [weak self] highlighted in + guard let self, let component = self.component, component.animateScale else { return } if highlighted { @@ -112,11 +132,30 @@ public final class GlassBarButtonComponent: Component { action(self) } + @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + guard let component = self.component, let action = component.action else { + return + } + action(self) + } + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let result = super.hitTest(point, with: event) else { + return nil + } + if result === self.glassContainerView || result === self.genericContainerView { + return self.containerView + } + return result + } + func update(component: GlassBarButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let previousComponent = self.component self.component = component - self.isEnabled = component.isEnabled + self.containerView.isEnabled = component.isEnabled var componentView: ComponentView var animateAppearance = false @@ -159,6 +198,7 @@ public final class GlassBarButtonComponent: Component { let componentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((containerSize.width - componentSize.width) / 2.0), y: floorToScreenPixels((containerSize.height - componentSize.height) / 2.0)), size: componentSize) if let view = componentView.view { if view.superview == nil { + view.isUserInteractionEnabled = false self.containerView.addSubview(view) if animateAppearance { transition.animateScale(view: view, from: 0.01, to: 1.0) @@ -168,7 +208,11 @@ public final class GlassBarButtonComponent: Component { componentTransition.setFrame(view: view, frame: componentFrame) } - let effectiveState = component.state ?? .glass + let effectiveState: DisplayState = component.state ?? .glass + /*if "".isEmpty { + effectiveState = .glass + }*/ + var genericAlpha: CGFloat = 1.0 var glassAlpha: CGFloat = 1.0 switch effectiveState { @@ -181,8 +225,9 @@ public final class GlassBarButtonComponent: Component { } let cornerRadius = containerSize.height * 0.5 - self.genericBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .custom, color: component.backgroundColor), transition: transition) - + if let backgroundColor = component.backgroundColor { + self.genericBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .custom, color: backgroundColor), transition: transition) + } let bounds = CGRect(origin: .zero, size: containerSize) transition.setFrame(view: self.containerView, frame: bounds) @@ -196,7 +241,7 @@ public final class GlassBarButtonComponent: Component { transition.setFrame(view: self.genericBackgroundView, frame: bounds) - if glassAlpha == 1.0 { + if glassAlpha == 1.0, let backgroundColor = component.backgroundColor { let glassBackgroundView: GlassBackgroundView var glassBackgroundTransition = transition if let current = self.glassBackgroundView { @@ -204,13 +249,31 @@ public final class GlassBarButtonComponent: Component { } else { glassBackgroundTransition = .immediate glassBackgroundView = GlassBackgroundView() - glassBackgroundView.isUserInteractionEnabled = false self.glassContainerView.addSubview(glassBackgroundView) self.glassBackgroundView = glassBackgroundView + glassBackgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))) + transition.animateAlpha(view: glassBackgroundView, from: 0.0, to: 1.0) } - glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom : .panel , color: component.backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)), transition: glassBackgroundTransition) + glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom : .panel , color: backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)), isInteractive: true, transition: glassBackgroundTransition) + glassBackgroundTransition.setFrame(view: glassBackgroundView, frame: bounds) + } else if case .glass = component.state { + let glassBackgroundView: GlassBackgroundView + var glassBackgroundTransition = transition + if let current = self.glassBackgroundView { + glassBackgroundView = current + } else { + glassBackgroundTransition = .immediate + glassBackgroundView = GlassBackgroundView() + self.glassContainerView.addSubview(glassBackgroundView) + self.glassBackgroundView = glassBackgroundView + + glassBackgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))) + + transition.animateAlpha(view: glassBackgroundView, from: 0.0, to: 1.0) + } + glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .panel, color: UIColor(white: component.isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: glassBackgroundTransition) glassBackgroundTransition.setFrame(view: glassBackgroundView, frame: bounds) } else if let glassBackgroundView = self.glassBackgroundView { self.glassBackgroundView = nil @@ -219,6 +282,14 @@ public final class GlassBarButtonComponent: Component { }) } + if let glassBackgroundView = self.glassBackgroundView { + if self.containerView.superview !== glassBackgroundView.contentView { + glassBackgroundView.contentView.addSubview(self.containerView) + } + } else if self.containerView.superview !== self { + self.addSubview(self.containerView) + } + return containerSize } } @@ -310,7 +381,7 @@ public final class BarComponentHostNode: ASDisplayNode { transition.animateScale(view: view, from: 0.01, to: 1.0) } } - view.frame = CGRect(origin: CGPoint(x: 0.0, y: 3.0), size: self.size) + view.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.size) } } } diff --git a/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlGroup.swift b/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlGroup.swift index f4d3c15e..6b5638b3 100644 --- a/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlGroup.swift +++ b/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlGroup.swift @@ -148,13 +148,13 @@ public final class GlassControlGroupComponent: Component { )) case let .text(string): content = AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: string, font: Font.semibold(15.0), textColor: component.background == .activeTint ? component.theme.list.itemCheckColors.foregroundColor : component.theme.chat.inputPanel.panelControlColor)) + text: .plain(NSAttributedString(string: string, font: Font.medium(17.0), textColor: component.background == .activeTint ? component.theme.list.itemCheckColors.foregroundColor : component.theme.chat.inputPanel.panelControlColor)) )) itemInsets.left = 10.0 itemInsets.right = itemInsets.left } - var minItemWidth: CGFloat = 40.0 + var minItemWidth: CGFloat = availableSize.height if component.items.count == 1 { minItemWidth = max(minItemWidth, component.minWidth) } @@ -163,7 +163,7 @@ public final class GlassControlGroupComponent: Component { transition: itemTransition, component: AnyComponent(PlainButtonComponent( content: content, - minSize: CGSize(width: minItemWidth, height: 40.0), + minSize: CGSize(width: minItemWidth, height: availableSize.height), contentInsets: itemInsets, action: { item.action?() @@ -186,8 +186,9 @@ public final class GlassControlGroupComponent: Component { itemComponentView.alpha = 0.0 } itemTransition.setFrame(view: itemComponentView, frame: itemFrame) + alphaTransition.setAlpha(view: itemComponentView, alpha: item.action != nil ? 1.0 : 0.5) + if animateIn { - alphaTransition.setAlpha(view: itemComponentView, alpha: 1.0) alphaTransition.animateBlur(layer: itemComponentView.layer, fromRadius: 8.0, toRadius: 0.0) } } @@ -220,6 +221,7 @@ public final class GlassControlGroupComponent: Component { tintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7), innerColor: component.theme.list.itemCheckColors.fillColor) } transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) + isInteractive = true self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: tintColor, isInteractive: isInteractive, transition: transition) return size diff --git a/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlPanel.swift b/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlPanel.swift index 471f2c47..09861db2 100644 --- a/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlPanel.swift +++ b/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlPanel.swift @@ -9,10 +9,12 @@ public final class GlassControlPanelComponent: Component { public final class Item: Equatable { public let items: [GlassControlGroupComponent.Item] public let background: GlassControlGroupComponent.Background + public let keepWide: Bool - public init(items: [GlassControlGroupComponent.Item], background: GlassControlGroupComponent.Background) { + public init(items: [GlassControlGroupComponent.Item], background: GlassControlGroupComponent.Background, keepWide: Bool = false) { self.items = items self.background = background + self.keepWide = keepWide } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -22,6 +24,9 @@ public final class GlassControlPanelComponent: Component { if lhs.background != rhs.background { return false } + if lhs.keepWide != rhs.keepWide { + return false + } return true } } @@ -30,17 +35,20 @@ public final class GlassControlPanelComponent: Component { public let leftItem: Item? public let rightItem: Item? public let centralItem: Item? + public let centerAlignmentIfPossible: Bool public init( theme: PresentationTheme, leftItem: Item?, centralItem: Item?, - rightItem: Item? + rightItem: Item?, + centerAlignmentIfPossible: Bool = false ) { self.theme = theme self.leftItem = leftItem self.centralItem = centralItem self.rightItem = rightItem + self.centerAlignmentIfPossible = centerAlignmentIfPossible } public static func ==(lhs: GlassControlPanelComponent, rhs: GlassControlPanelComponent) -> Bool { @@ -56,6 +64,9 @@ public final class GlassControlPanelComponent: Component { if lhs.rightItem != rhs.rightItem { return false } + if lhs.centerAlignmentIfPossible != rhs.centerAlignmentIfPossible { + return false + } return true } @@ -118,7 +129,7 @@ public final class GlassControlPanelComponent: Component { theme: component.theme, background: leftItem.background, items: leftItem.items, - minWidth: 40.0 + minWidth: availableSize.height )), environment: {}, containerSize: CGSize(width: availableSize.width, height: availableSize.height) @@ -167,7 +178,7 @@ public final class GlassControlPanelComponent: Component { theme: component.theme, background: rightItem.background, items: rightItem.items, - minWidth: 40.0 + minWidth: availableSize.height )), environment: {}, containerSize: CGSize(width: availableSize.width, height: availableSize.height) @@ -233,12 +244,19 @@ public final class GlassControlPanelComponent: Component { theme: component.theme, background: centralItem.background, items: centralItem.items, - minWidth: 165.0 + minWidth: centralItem.keepWide ? 165.0 : availableSize.height )), environment: {}, containerSize: maxCentralItemSize ) - let centralItemFrameValue = CGRect(origin: CGPoint(x: centralLeftInset + floor((availableSize.width - centralLeftInset - centralRightInset - centralItemSize.width) * 0.5), y: 0.0), size: centralItemSize) + var centralItemFrameValue = CGRect(origin: CGPoint(x: centralLeftInset + floor((availableSize.width - centralLeftInset - centralRightInset - centralItemSize.width) * 0.5), y: 0.0), size: centralItemSize) + if component.centerAlignmentIfPossible { + let maxInset = max(centralLeftInset, centralRightInset) + if availableSize.width - maxInset * 2.0 > centralItemSize.width { + centralItemFrameValue.origin.x = maxInset + floor((availableSize.width - maxInset * 2.0 - centralItemSize.width) * 0.5) + } + } + if let centralItemComponentView = centralItemComponent.view { var animateIn = false if centralItemComponentView.superview == nil { diff --git a/submodules/TelegramUI/Components/GlobalControlPanelsContext/BUILD b/submodules/TelegramUI/Components/GlobalControlPanelsContext/BUILD new file mode 100644 index 00000000..a32c2d38 --- /dev/null +++ b/submodules/TelegramUI/Components/GlobalControlPanelsContext/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GlobalControlPanelsContext", + module_name = "GlobalControlPanelsContext", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/TelegramUIPreferences", + "//submodules/TelegramCallsUI", + "//submodules/Display", + "//submodules/UndoUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift b/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift new file mode 100644 index 00000000..d42d3910 --- /dev/null +++ b/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift @@ -0,0 +1,698 @@ +import Foundation +import UIKit +import SwiftSignalKit +import TelegramCore +import AccountContext +import TelegramUIPreferences +import TelegramCallsUI +import Display +import UndoUI + +public final class GlobalControlPanelsContext { + public final class MediaPlayback: Equatable { + public let version: Int + public let item: SharedMediaPlaylistItem + public let previousItem: SharedMediaPlaylistItem? + public let nextItem: SharedMediaPlaylistItem? + public let playbackOrder: MusicPlaybackSettingsOrder + public let kind: MediaManagerPlayerType + public let playlistLocation: SharedMediaPlaylistLocation + public let account: Account + + public init(version: Int, item: SharedMediaPlaylistItem, previousItem: SharedMediaPlaylistItem?, nextItem: SharedMediaPlaylistItem?, playbackOrder: MusicPlaybackSettingsOrder, kind: MediaManagerPlayerType, playlistLocation: SharedMediaPlaylistLocation, account: Account) { + self.version = version + self.item = item + self.previousItem = previousItem + self.nextItem = nextItem + self.playbackOrder = playbackOrder + self.kind = kind + self.playlistLocation = playlistLocation + self.account = account + } + + public static func ==(lhs: MediaPlayback, rhs: MediaPlayback) -> Bool { + if lhs.version != rhs.version { + return false + } + return true + } + } + + public enum LiveLocationMode { + case all + case peer(EnginePeer.Id) + } + + public final class LiveLocation: Equatable { + public let mode: LiveLocationMode + public let peers: [EnginePeer] + public let messages: [EngineMessage.Id: EngineMessage] + public let canClose: Bool + public let version: Int + + public init(mode: LiveLocationMode, peers: [EnginePeer], messages: [EngineMessage.Id: EngineMessage], canClose: Bool, version: Int) { + self.mode = mode + self.peers = peers + self.messages = messages + self.canClose = canClose + self.version = version + } + + public static func ==(lhs: LiveLocation, rhs: LiveLocation) -> Bool { + if lhs.version != rhs.version { + return false + } + return true + } + } + + public enum ChatListNotice: Equatable { + case clearStorage(sizeFraction: Double) + case setupPassword + case premiumUpgrade(discount: Int32) + case premiumAnnualDiscount(discount: Int32) + case premiumRestore(discount: Int32) + case xmasPremiumGift + case setupBirthday + case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday]) + case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int) + case premiumGrace + case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer]) + case setupPhoto(EnginePeer) + case accountFreeze + case link(id: String, url: String, title: ServerSuggestionInfo.Item.Text, subtitle: ServerSuggestionInfo.Item.Text) + } + + public final class GroupCall: Equatable { + public let peerId: EnginePeer.Id + public let isChannel: Bool + public let info: GroupCallInfo + public let topParticipants: [GroupCallParticipantsContext.Participant] + public let participantCount: Int + public let activeSpeakers: Set + public let groupCall: PresentationGroupCall? + + public init( + peerId: EnginePeer.Id, + isChannel: Bool, + info: GroupCallInfo, + topParticipants: [GroupCallParticipantsContext.Participant], + participantCount: Int, + activeSpeakers: Set, + groupCall: PresentationGroupCall? + ) { + self.peerId = peerId + self.isChannel = isChannel + self.info = info + self.topParticipants = topParticipants + self.participantCount = participantCount + self.activeSpeakers = activeSpeakers + self.groupCall = groupCall + } + + public static func ==(lhs: GroupCall, rhs: GroupCall) -> Bool { + if lhs.peerId != rhs.peerId { + return false + } + if lhs.isChannel != rhs.isChannel { + return false + } + if lhs.info != rhs.info { + return false + } + if lhs.topParticipants != rhs.topParticipants { + return false + } + if lhs.participantCount != rhs.participantCount { + return false + } + if lhs.activeSpeakers != rhs.activeSpeakers { + return false + } + if lhs.groupCall !== rhs.groupCall { + return false + } + return true + } + } + + public final class State { + public let mediaPlayback: MediaPlayback? + public let liveLocation: LiveLocation? + public let chatListNotice: ChatListNotice? + public let groupCall: GroupCall? + + public init( + mediaPlayback: MediaPlayback?, + liveLocation: LiveLocation?, + chatListNotice: ChatListNotice?, + groupCall: GroupCall? + ) { + self.mediaPlayback = mediaPlayback + self.liveLocation = liveLocation + self.chatListNotice = chatListNotice + self.groupCall = groupCall + } + } + + private final class Impl { + let queue: Queue + let context: AccountContext + + private(set) var stateValue: State + let statePipe = ValuePipe() + + private var nextVersion: Int = 0 + + var tempVoicePlaylistEnded: (() -> Void)? + var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)? + var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem? + + var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account, SharedMediaPlaylistLocation, Int)? + var mediaStatusDisposable: Disposable? + + var liveLocationState: (mode: LiveLocationMode, peers: [EnginePeer], messages: [EngineMessage.Id: EngineMessage], canClose: Bool, version: Int)? + var liveLocationDisposable: Disposable? + + var chatListNotice: ChatListNotice? + var suggestedChatListNoticeDisposable: Disposable? + + var groupCall: GroupCall? + var currentGroupCallDisposable: Disposable? + + init(queue: Queue, context: AccountContext, mediaPlayback: Bool, liveLocationMode: LiveLocationMode?, groupCalls: EnginePeer.Id?, chatListNotices: Bool) { + self.queue = queue + self.context = context + + self.stateValue = State(mediaPlayback: nil, liveLocation: nil, chatListNotice: nil, groupCall: nil) + + if mediaPlayback { + self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState + |> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in + if let (account, state, type) = playlistStateAndType { + switch state { + case let .state(state): + return .single((account, state, type)) + case .loading: + return .single(nil) |> delay(0.2, queue: .mainQueue()) + } + } else { + return .single(nil) + } + } + |> deliverOnMainQueue).start(next: { [weak self] playlistStateAndType in + guard let strongSelf = self else { + return + } + if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) || + !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) || + !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) || + strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order || strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 { + var previousVoiceItem: SharedMediaPlaylistItem? + if let playlistStateAndType = strongSelf.playlistStateAndType, playlistStateAndType.4 == .voice { + previousVoiceItem = playlistStateAndType.0 + } + + var updatedVoiceItem: SharedMediaPlaylistItem? + if let playlistStateAndType = playlistStateAndType, playlistStateAndType.2 == .voice { + updatedVoiceItem = playlistStateAndType.1.item + } + + strongSelf.tempVoicePlaylistCurrentItem = updatedVoiceItem + strongSelf.tempVoicePlaylistItemChanged?(previousVoiceItem, updatedVoiceItem) + if let playlistStateAndType = playlistStateAndType { + strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.previousItem, playlistStateAndType.1.nextItem, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0, playlistStateAndType.1.playlistLocation, 0) + } else { + var voiceEnded = false + if strongSelf.playlistStateAndType?.4 == .voice { + voiceEnded = true + } + strongSelf.playlistStateAndType = nil + if voiceEnded { + strongSelf.tempVoicePlaylistEnded?() + } + } + strongSelf.playlistStateAndType?.7 = strongSelf.nextVersion + strongSelf.nextVersion += 1 + strongSelf.notifyStateUpdated() + } + }) + } + + if let liveLocationMode, let liveLocationManager = context.liveLocationManager { + let signal: Signal<([EnginePeer]?, [EngineMessage.Id: EngineMessage]?), NoError> + switch liveLocationMode { + case let .peer(peerId): + signal = combineLatest(liveLocationManager.summaryManager.peersBroadcastingTo(peerId: peerId), liveLocationManager.summaryManager.broadcastingToMessages()) + |> map { peersAndMessages, outgoingMessages in + var peers = peersAndMessages?.map { $0.0 } + for message in outgoingMessages.values { + if message.id.peerId == peerId, let author = message.author { + if peers == nil { + peers = [] + } + peers?.append(author) + } + } + return (peers, outgoingMessages) + } + case .all: + signal = liveLocationManager.summaryManager.broadcastingToMessages() + |> map { messages -> ([EnginePeer]?, [EngineMessage.Id: EngineMessage]?) in + if messages.isEmpty { + return (nil, nil) + } else { + var peers: [EnginePeer] = [] + for message in messages.values.sorted(by: { $0.index < $1.index }) { + if let peer = message.peers[message.id.peerId] { + peers.append(EnginePeer(peer)) + } + } + return (peers, messages) + } + } + } + + self.liveLocationDisposable = (signal + |> deliverOnMainQueue).start(next: { [weak self] peers, messages in + guard let self else { + return + } + var updated = false + if let current = self.liveLocationState?.peers, let peers { + updated = current != peers + } else if (self.liveLocationState != nil) != (peers != nil) { + updated = true + } + + if updated { + if let peers, let messages { + var canClose = true + if case let .peer(peerId) = liveLocationMode { + canClose = false + for messageId in messages.keys { + if messageId.peerId == peerId { + canClose = true + } + } + } + + self.liveLocationState = ( + mode: liveLocationMode, + peers: peers, + messages: messages, + canClose: canClose, + version: self.nextVersion + ) + self.nextVersion += 1 + } else { + self.liveLocationState = nil + } + self.notifyStateUpdated() + } + }) + } + + if chatListNotices { + let twoStepData: Signal = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init)) + + let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + |> map { view -> AppConfiguration in + let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + return appConfiguration + } + |> distinctUntilChanged + |> map { appConfiguration -> AccountFreezeConfiguration in + return AccountFreezeConfiguration.with(appConfiguration: appConfiguration) + }) + + let starsSubscriptionsContextPromise = Promise(nil) + + let suggestedChatListNoticeSignal: Signal = combineLatest( + context.engine.notices.getServerProvidedSuggestions(), + context.engine.notices.getServerDismissedSuggestions(), + twoStepData, + newSessionReviews(postbox: context.account.postbox), + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), + TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId) + ), + context.account.stateManager.contactBirthdays, + starsSubscriptionsContextPromise.get(), + accountFreezeConfiguration + ) + |> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal in + let (accountPeer, birthday) = data + + if let newSessionReview = newSessionReviews.first { + return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count)) + } + if suggestions.contains(.setupPassword), let configuration { + var notSet = false + switch configuration { + case let .notSet(pendingEmail): + if pendingEmail == nil { + notSet = true + } + case .set: + break + } + if notSet { + return .single(.setupPassword) + } + } + + let today = Calendar(identifier: .gregorian).component(.day, from: Date()) + var todayBirthdayPeerIds: [EnginePeer.Id] = [] + for (peerId, birthday) in birthdays { + if birthday.day == today { + todayBirthdayPeerIds.append(peerId) + } + } + todayBirthdayPeerIds.sort { lhs, rhs in + return lhs < rhs + } + + if dismissedSuggestions.contains(ServerProvidedSuggestion.todayBirthdays.id) { + todayBirthdayPeerIds = [] + } + + if let _ = accountFreezeConfiguration.freezeUntilDate { + return .single(.accountFreeze) + } else if suggestions.contains(.starsSubscriptionLowBalance) { + if let starsSubscriptionsContext { + return starsSubscriptionsContext.state + |> map { state in + if state.balance > StarsAmount.zero && !state.subscriptions.isEmpty { + return .starsSubscriptionLowBalance( + amount: state.balance, + peers: state.subscriptions.map { $0.peer } + ) + } else { + return nil + } + } + } else { + starsSubscriptionsContextPromise.set(.single(context.engine.payments.peerStarsSubscriptionsContext(starsContext: nil, missingBalance: true))) + return .single(nil) + } + } else if suggestions.contains(.setupPhoto), let accountPeer, accountPeer.smallProfileImage == nil { + return .single(.setupPhoto(accountPeer)) + } else if suggestions.contains(.gracePremium) { + return .single(.premiumGrace) + } else if suggestions.contains(.xmasPremiumGift) { + return .single(.xmasPremiumGift) + } else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager { + return inAppPurchaseManager.availableProducts + |> map { products -> ChatListNotice? in + if products.count > 1 { + let shortestOptionPrice: (Int64, NSDecimalNumber) + if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) { + shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue) + } else { + shortestOptionPrice = (1, NSDecimalNumber(decimal: 1)) + } + for product in products { + if product.id.hasSuffix(".annual") { + let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(12) / Float(shortestOptionPrice.0) + let discount = Int32(round((1.0 - fraction) * 20.0) * 5.0) + if discount > 0 { + if suggestions.contains(.restorePremium) { + return .premiumRestore(discount: discount) + } else if suggestions.contains(.annualPremium) { + return .premiumAnnualDiscount(discount: discount) + } else if suggestions.contains(.upgradePremium) { + return .premiumUpgrade(discount: discount) + } + } + break + } + } + return nil + } else { + if !GlobalExperimentalSettings.isAppStoreBuild { + if suggestions.contains(.restorePremium) { + return .premiumRestore(discount: 0) + } else if suggestions.contains(.annualPremium) { + return .premiumAnnualDiscount(discount: 0) + } else if suggestions.contains(.upgradePremium) { + return .premiumUpgrade(discount: 0) + } + } + return nil + } + } + } else if !todayBirthdayPeerIds.isEmpty { + return context.engine.data.get( + EngineDataMap(todayBirthdayPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))) + ) + |> map { result -> ChatListNotice? in + var todayBirthdayPeers: [EnginePeer] = [] + for (peerId, _) in birthdays { + if let maybePeer = result[peerId], let peer = maybePeer { + todayBirthdayPeers.append(peer) + } + } + return .birthdayPremiumGift(peers: todayBirthdayPeers, birthdays: birthdays) + } + } else if suggestions.contains(.setupBirthday) && birthday == nil { + return .single(.setupBirthday) + } else if case let .link(id, url, title, subtitle) = suggestions.first(where: { if case .link = $0 { return true } else { return false} }) { + return .single(.link(id: id, url: url, title: title, subtitle: subtitle)) + } else { + return .single(nil) + } + } + |> distinctUntilChanged + + self.suggestedChatListNoticeDisposable = (suggestedChatListNoticeSignal + |> deliverOn(self.queue)).startStrict(next: { [weak self] chatListNotice in + guard let self else { + return + } + if self.chatListNotice != chatListNotice { + self.chatListNotice = chatListNotice + self.notifyStateUpdated() + } + }) + } + + if let callManager = context.sharedContext.callManager, let peerId = groupCalls { + let currentGroupCall: Signal = callManager.currentGroupCallSignal + |> distinctUntilChanged(isEqual: { lhs, rhs in + return lhs == rhs + }) + |> map { call -> PresentationGroupCall? in + guard case let .group(call) = call else { + return nil + } + guard call.peerId == peerId && call.account.peerId == context.account.peerId else { + return nil + } + return call + } + + let availableGroupCall: Signal + if let peerId = groupCalls { + availableGroupCall = context.account.viewTracker.peerView(peerId) + |> map { peerView -> (CachedChannelData.ActiveCall?, EnginePeer?) in + let peer = peerView.peers[peerId].flatMap(EnginePeer.init) + if let cachedData = peerView.cachedData as? CachedChannelData { + return (cachedData.activeCall, peer) + } else if let cachedData = peerView.cachedData as? CachedGroupData { + return (cachedData.activeCall, peer) + } else { + return (nil, peer) + } + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + return lhs.0 == rhs.0 + }) + |> mapToSignal { activeCall, peer -> Signal in + guard let activeCall = activeCall else { + return .single(nil) + } + + var isChannel = false + if let peer = peer, case let .channel(channel) = peer, case .broadcast = channel.info { + isChannel = true + } + + return Signal { [weak context] subscriber in + guard let context = context, let callContextCache = context.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl else { + return EmptyDisposable + } + + let disposable = MetaDisposable() + + callContextCache.impl.syncWith { impl in + let callContext = impl.get(account: context.account, engine: context.engine, peerId: peerId, isChannel: isChannel, call: EngineGroupCallDescription(activeCall)) + disposable.set((callContext.context.panelData + |> deliverOnMainQueue).start(next: { panelData in + callContext.keep() + var updatedPanelData = panelData + if let panelData { + var updatedInfo = panelData.info + updatedInfo.subscribedToScheduled = activeCall.subscribedToScheduled + updatedPanelData = panelData.withInfo(updatedInfo) + } + subscriber.putNext(updatedPanelData) + })) + } + + return ActionDisposable { + disposable.dispose() + } + } + |> runOn(.mainQueue()) + } + } else { + availableGroupCall = .single(nil) + } + + let previousCurrentGroupCall = Atomic(value: nil) + self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(), availableGroupCall, currentGroupCall).start(next: { [weak self] availableState, currentGroupCall in + guard let self else { + return + } + + let previousCurrentGroupCall = previousCurrentGroupCall.swap(currentGroupCall) + + let panelData: AccountGroupCallContextImpl.GroupCallPanelData? + if previousCurrentGroupCall != nil && currentGroupCall == nil && availableState?.participantCount == 1 { + panelData = nil + } else { + panelData = currentGroupCall != nil || (availableState?.participantCount == 0 && availableState?.info.scheduleTimestamp == nil && availableState?.info.isStream == false) ? nil : availableState + } + + let groupCall = panelData.flatMap { panelData in + return GroupCall( + peerId: panelData.peerId, + isChannel: panelData.isChannel, + info: panelData.info, + topParticipants: panelData.topParticipants, + participantCount: panelData.participantCount, + activeSpeakers: panelData.activeSpeakers, + groupCall: panelData.groupCall + ) + } + if self.groupCall != groupCall { + self.groupCall = groupCall + self.notifyStateUpdated() + } + }) + } + } + + deinit { + self.mediaStatusDisposable?.dispose() + self.liveLocationDisposable?.dispose() + self.suggestedChatListNoticeDisposable?.dispose() + self.currentGroupCallDisposable?.dispose() + } + + private func notifyStateUpdated() { + self.stateValue = State( + mediaPlayback: self.playlistStateAndType.flatMap { playlistStateAndType in + return MediaPlayback( + version: playlistStateAndType.7, + item: playlistStateAndType.0, + previousItem: playlistStateAndType.1, + nextItem: playlistStateAndType.2, + playbackOrder: playlistStateAndType.3, + kind: playlistStateAndType.4, + playlistLocation: playlistStateAndType.6, + account: playlistStateAndType.5 + ) + }, + liveLocation: self.liveLocationState.flatMap { liveLocationState in + return GlobalControlPanelsContext.LiveLocation( + mode: liveLocationState.mode, + peers: liveLocationState.peers, + messages: liveLocationState.messages, + canClose: liveLocationState.canClose, + version: liveLocationState.version + ) + }, + chatListNotice: self.chatListNotice, + groupCall: self.groupCall + ) + self.statePipe.putNext(self.stateValue) + } + + func dismissChatListNotice(parentController: ViewController, notice: ChatListNotice) { + let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }) + switch notice { + case .xmasPremiumGift: + let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.xmasPremiumGift.id).startStandalone() + parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in + return true + }), in: .current) + case .setupBirthday: + let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupBirthday.id).startStandalone() + parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_BirthdayInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in + return true + }), in: .current) + case .birthdayPremiumGift: + let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.todayBirthdays.id).startStandalone() + parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gift", scale: 0.058, colors: ["__allcolors__": UIColor.white], title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, customUndoText: nil, timeout: 5.0), elevatedLayout: false, action: { _ in + return true + }), in: .current) + case .premiumGrace: + let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.gracePremium.id).startStandalone() + case .setupPhoto: + let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPhoto.id).startStandalone() + case .starsSubscriptionLowBalance: + let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.starsSubscriptionLowBalance.id).startStandalone() + case let .link(id, _, _, _): + let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: id).startStandalone() + default: + break + } + } + } + + private let impl: QueueLocalObject + public var state: Signal { + return self.impl.signalWith { impl, subscriber in + subscriber.putNext(impl.stateValue) + return impl.statePipe.signal().start(next: subscriber.putNext) + } + } + + public init(context: AccountContext, mediaPlayback: Bool, liveLocationMode: LiveLocationMode?, groupCalls: EnginePeer.Id?, chatListNotices: Bool) { + self.impl = QueueLocalObject(queue: .mainQueue(), generate: { + return Impl(queue: .mainQueue(), context: context, mediaPlayback: mediaPlayback, liveLocationMode: liveLocationMode, groupCalls: groupCalls, chatListNotices: chatListNotices) + }) + } + + public func dismissChatListNotice(parentController: ViewController, notice: ChatListNotice) { + self.impl.with { impl in + impl.dismissChatListNotice(parentController: parentController, notice: notice) + } + } + + public func setTempVoicePlaylistEnded(_ f: (() -> Void)?) { + self.impl.with { impl in + return impl.tempVoicePlaylistEnded = f + } + } + + public func setTempVoicePlaylistItemChanged(_ f: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)?) { + self.impl.with { impl in + return impl.tempVoicePlaylistItemChanged = f + } + } + + public var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem? { + return self.impl.syncWith { impl in + return impl.tempVoicePlaylistCurrentItem + } + } + + public var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account, SharedMediaPlaylistLocation, Int)? { + return self.impl.syncWith { impl in + return impl.playlistStateAndType + } + } +} diff --git a/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/BUILD new file mode 100644 index 00000000..f3a92b45 --- /dev/null +++ b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/BUILD @@ -0,0 +1,38 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GroupCallHeaderPanelComponent", + module_name = "GroupCallHeaderPanelComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUIPreferences", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/GlobalControlPanelsContext", + "//submodules/PresentationDataUtils", + "//submodules/TextFormat", + "//submodules/Markdown", + "//submodules/LocalizedPeerData", + "//submodules/LiveLocationTimerNode", + "//submodules/TelegramStringFormatting", + "//submodules/AppBundle", + "//submodules/AnimatedAvatarSetNode", + "//submodules/AudioBlob", + "//submodules/TelegramCallsUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallHeaderPanelComponent.swift b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallHeaderPanelComponent.swift new file mode 100644 index 00000000..bd3d0802 --- /dev/null +++ b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallHeaderPanelComponent.swift @@ -0,0 +1,124 @@ +import Foundation +import UIKit +import Display +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import AccountContext +import TelegramCore +import GlobalControlPanelsContext +import SwiftSignalKit +import Postbox +import PresentationDataUtils + +public final class GroupCallHeaderPanelComponent: Component { + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + public let data: GlobalControlPanelsContext.GroupCall + public let onTapAction: () -> Void + public let onNotifyScheduledTapAction: () -> Void + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + data: GlobalControlPanelsContext.GroupCall, + onTapAction: @escaping () -> Void, + onNotifyScheduledTapAction: @escaping () -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.data = data + self.onTapAction = onTapAction + self.onNotifyScheduledTapAction = onNotifyScheduledTapAction + } + + public static func ==(lhs: GroupCallHeaderPanelComponent, rhs: GroupCallHeaderPanelComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.data != rhs.data { + return false + } + return true + } + + public final class View: UIView { + private var panel: GroupCallNavigationAccessoryPanel? + + private var component: GroupCallHeaderPanelComponent? + private weak var state: EmptyComponentState? + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func update(component: GroupCallHeaderPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let themeUpdated = self.component?.theme !== component.theme + + self.component = component + self.state = state + + let presentationData = component.context.sharedContext.currentPresentationData.with ({ $0 }).withUpdated(theme: component.theme) + + let panel: GroupCallNavigationAccessoryPanel + if let current = self.panel { + panel = current + } else { + panel = GroupCallNavigationAccessoryPanel( + context: component.context, + presentationData: presentationData, + tapAction: { [weak self] in + guard let self, let component = self.component else { + return + } + component.onTapAction() + }, + notifyScheduledTapAction: { [weak self] in + guard let self, let component = self.component else { + return + } + component.onNotifyScheduledTapAction() + } + ) + self.panel = panel + self.addSubview(panel.view) + } + + let size = CGSize(width: availableSize.width, height: 50.0) + let panelFrame = CGRect(origin: CGPoint(), size: size) + transition.setFrame(view: panel.view, frame: panelFrame) + panel.updateLayout(size: panelFrame.size, leftInset: 0.0, rightInset: 0.0, isHidden: false, transition: transition.containedViewLayoutTransition) + panel.update(data: component.data) + + if themeUpdated { + panel.updatePresentationData(presentationData) + } + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallNavigationAccessoryPanel.swift b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallNavigationAccessoryPanel.swift new file mode 100644 index 00000000..643569e2 --- /dev/null +++ b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallNavigationAccessoryPanel.swift @@ -0,0 +1,808 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramCore +import Postbox +import TelegramPresentationData +import TelegramUIPreferences +import TelegramStringFormatting +import AccountContext +import AppBundle +import SwiftSignalKit +import AnimatedAvatarSetNode +import AudioBlob +import TelegramCallsUI +import GlobalControlPanelsContext + +func textForTimeout(value: Int32) -> String { + if value < 3600 { + let minutes = value / 60 + let seconds = value % 60 + let secondsPadding = seconds < 10 ? "0" : "" + return "\(minutes):\(secondsPadding)\(seconds)" + } else { + let hours = value / 3600 + let minutes = (value % 3600) / 60 + let minutesPadding = minutes < 10 ? "0" : "" + let seconds = value % 60 + let secondsPadding = seconds < 10 ? "0" : "" + return "\(hours):\(minutesPadding)\(minutes):\(secondsPadding)\(seconds)" + } +} + +private let titleFont = Font.semibold(15.0) +private let subtitleFont = Font.regular(13.0) + +private final class FakeAudioLevelGenerator { + private var isFirstTime: Bool = true + private var nextTarget: Float = 0.0 + private var nextTargetProgress: Float = 0.0 + private var nextTargetProgressNorm: Float = 1.0 + + func get() -> Float { + let wasFirstTime = self.isFirstTime + self.isFirstTime = false + + self.nextTargetProgress *= 0.82 + if self.nextTargetProgress <= 0.01 { + if Int.random(in: 0 ... 4) <= 1 && !wasFirstTime { + self.nextTarget = 0.0 + self.nextTargetProgressNorm = Float.random(in: 0.1 ..< 0.3) + } else { + self.nextTarget = Float.random(in: 0.0 ..< 20.0) + self.nextTargetProgressNorm = Float.random(in: 0.2 ..< 0.7) + } + self.nextTargetProgress = self.nextTargetProgressNorm + return self.nextTarget + } else { + let value = self.nextTarget * max(0.0, self.nextTargetProgress / self.nextTargetProgressNorm) + return value + } + } +} + +final class GroupCallNavigationAccessoryPanel: ASDisplayNode { + private let context: AccountContext + private var theme: PresentationTheme + private var strings: PresentationStrings + private var dateTimeFormat: PresentationDateTimeFormat + + private let tapAction: () -> Void + private let notifyScheduledTapAction: () -> Void + + private let contentNode: ASDisplayNode + + private let tapButton: HighlightTrackingButtonNode + + private let joinButton: HighlightableButtonNode + private let joinButtonTitleNode: ImmediateTextNode + private let joinButtonBackgroundNode: ASImageNode + + private var previewImageNode: ASImageNode? + private var previewImage: UIImage? + + private var audioLevelView: VoiceBlobView? + + private let micButton: HighlightTrackingButtonNode + private let micButtonForegroundNode: VoiceChatMicrophoneNode + private let micButtonBackgroundNode: ASImageNode + private var micButtonBackgroundNodeIsMuted: Bool? + + let titleNode: ImmediateTextNode + let textNode: ImmediateTextNode + private var textIsActive = false + private let muteIconNode: ASImageNode + + private var isScheduled = false + private var isLate = false + private var currentText: String = "" + private var updateTimer: SwiftSignalKit.Timer? + + private let avatarsContext: AnimatedAvatarSetContext + private var avatarsContent: AnimatedAvatarSetContext.Content? + private let avatarsNode: AnimatedAvatarSetNode + private var audioLevelGenerators: [PeerId: FakeAudioLevelGenerator] = [:] + private var audioLevelGeneratorTimer: SwiftSignalKit.Timer? + + private let backgroundNode: ASDisplayNode + private let separatorNode: ASDisplayNode + + private let membersDisposable = MetaDisposable() + private let isMutedDisposable = MetaDisposable() + private let audioLevelDisposable = MetaDisposable() + private var imageDisposable: Disposable? + + private var callState: PresentationGroupCallState? + + private let hapticFeedback = HapticFeedback() + + private var currentData: GlobalControlPanelsContext.GroupCall? + private var validLayout: (CGSize, CGFloat, CGFloat, Bool)? + + public init(context: AccountContext, presentationData: PresentationData, tapAction: @escaping () -> Void, notifyScheduledTapAction: @escaping () -> Void) { + self.context = context + self.theme = presentationData.theme + self.strings = presentationData.strings + self.dateTimeFormat = presentationData.dateTimeFormat + + self.tapAction = tapAction + self.notifyScheduledTapAction = notifyScheduledTapAction + + self.contentNode = ASDisplayNode() + + self.tapButton = HighlightTrackingButtonNode() + + self.joinButton = HighlightableButtonNode() + self.joinButtonTitleNode = ImmediateTextNode() + self.joinButtonBackgroundNode = ASImageNode() + self.joinButtonBackgroundNode.clipsToBounds = true + self.joinButtonBackgroundNode.displaysAsynchronously = false + self.joinButtonBackgroundNode.cornerRadius = 14.0 + + self.micButton = HighlightTrackingButtonNode() + self.micButtonForegroundNode = VoiceChatMicrophoneNode() + self.micButtonBackgroundNode = ASImageNode() + + self.titleNode = ImmediateTextNode() + self.textNode = ImmediateTextNode() + + self.muteIconNode = ASImageNode() + + self.avatarsContext = AnimatedAvatarSetContext() + self.avatarsNode = AnimatedAvatarSetNode() + + self.backgroundNode = ASDisplayNode() + + self.separatorNode = ASDisplayNode() + self.separatorNode.isLayerBacked = true + + super.init() + + self.clipsToBounds = true + + self.addSubnode(self.contentNode) + + self.contentNode.addSubnode(self.backgroundNode) + + self.tapButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.titleNode.layer.removeAnimation(forKey: "opacity") + strongSelf.titleNode.alpha = 0.4 + strongSelf.textNode.layer.removeAnimation(forKey: "opacity") + strongSelf.textNode.alpha = 0.4 + } else { + strongSelf.titleNode.alpha = 1.0 + strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.textNode.alpha = 1.0 + strongSelf.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + + self.contentNode.addSubnode(self.titleNode) + self.contentNode.addSubnode(self.textNode) + + self.contentNode.addSubnode(self.avatarsNode) + + self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside]) + self.contentNode.addSubnode(self.tapButton) + + self.joinButton.addSubnode(self.joinButtonBackgroundNode) + self.joinButton.addSubnode(self.joinButtonTitleNode) + self.contentNode.addSubnode(self.joinButton) + self.joinButton.addTarget(self, action: #selector(self.joinTapped), forControlEvents: [.touchUpInside]) + + self.micButton.addSubnode(self.micButtonBackgroundNode) + self.micButton.addSubnode(self.micButtonForegroundNode) + self.contentNode.addSubnode(self.micButton) + self.micButton.addTarget(self, action: #selector(self.micTapped), forControlEvents: [.touchUpInside]) + + self.contentNode.addSubnode(self.separatorNode) + + self.updatePresentationData(presentationData) + } + + deinit { + self.membersDisposable.dispose() + self.isMutedDisposable.dispose() + self.audioLevelGeneratorTimer?.invalidate() + self.updateTimer?.invalidate() + self.imageDisposable?.dispose() + self.audioLevelDisposable.dispose() + } + + public override func didLoad() { + super.didLoad() + + let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.micButtonPressGesture(_:))) + longTapRecognizer.minimumPressDuration = 0.01 + self.micButton.view.addGestureRecognizer(longTapRecognizer) + } + + @objc private func tapped() { + self.tapAction() + } + + @objc private func joinTapped() { + if let info = self.currentData?.info, let _ = info.scheduleTimestamp, !info.subscribedToScheduled { + self.notifyScheduledTapAction() + } else { + self.tapAction() + } + } + + @objc private func micTapped() { + guard let call = self.currentData?.groupCall else { + return + } + call.toggleIsMuted() + } + + private var actionButtonPressGestureStartTime: Double = 0.0 + + @objc private func micButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) { + guard let call = self.currentData?.groupCall, let callState = self.callState else { + return + } + switch gestureRecognizer.state { + case .began: + self.hapticFeedback.impact(.veryLight) + + self.actionButtonPressGestureStartTime = CACurrentMediaTime() + if callState.muteState != nil { + call.setIsMuted(action: .muted(isPushToTalkActive: true)) + } + case .ended, .cancelled: + self.hapticFeedback.impact(.veryLight) + + let timestamp = CACurrentMediaTime() + if callState.muteState != nil || timestamp - self.actionButtonPressGestureStartTime < 0.1 { + call.toggleIsMuted() + } else { + call.setIsMuted(action: .muted(isPushToTalkActive: false)) + } + default: + break + } + } + + public func updatePresentationData(_ presentationData: PresentationData) { + self.theme = presentationData.theme + self.strings = presentationData.strings + self.dateTimeFormat = presentationData.dateTimeFormat + + self.separatorNode.backgroundColor = presentationData.theme.chat.historyNavigation.strokeColor + + self.joinButtonTitleNode.attributedText = NSAttributedString(string: self.joinButtonTitleNode.attributedText?.string ?? "", font: Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.isScheduled ? .white : presentationData.theme.chat.inputPanel.actionControlForegroundColor) + self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: presentationData.theme.chat.inputPanel.secondaryTextColor) + + self.muteIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(presentationData.theme) + + self.updateJoinButton() + + if let (size, leftInset, rightInset, isHidden) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isHidden: isHidden, transition: .immediate) + } + } + + private func updateJoinButton() { + if self.isScheduled { + let purple = UIColor(rgb: 0x5d4ed1) + let pink = UIColor(rgb: 0xea436f) + let latePurple = UIColor(rgb: 0xaa56a6) + let latePink = UIColor(rgb: 0xef476f) + let colors: [UIColor] + if self.isLate { + colors = [latePurple, latePink] + } else { + colors = [purple, pink] + } + if self.joinButtonBackgroundNode.image != nil, let snapshotView = self.joinButtonBackgroundNode.view.snapshotContentTree() { + self.joinButtonBackgroundNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.joinButtonBackgroundNode.view) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 1.0, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + + self.joinButtonBackgroundNode.image = generateGradientImage(size: CGSize(width: 100.0, height: 1.0), colors: colors, locations: [0.0, 1.0], direction: .horizontal) + self.joinButtonBackgroundNode.backgroundColor = nil + } else { + self.joinButtonBackgroundNode.image = nil + self.joinButtonBackgroundNode.backgroundColor = self.theme.chat.inputPanel.actionControlFillColor + } + } + + private func animateTextChange() { + if let snapshotView = self.textNode.view.snapshotContentTree() { + let offset: CGFloat = self.textIsActive ? -7.0 : 7.0 + self.textNode.view.superview?.insertSubview(snapshotView, belowSubview: self.textNode.view) + + snapshotView.frame = self.textNode.frame + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -offset), duration: 0.2, removeOnCompletion: false, additive: true) + + self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.textNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.2, additive: true) + } + } + + public func update(data: GlobalControlPanelsContext.GroupCall) { + let previousData = self.currentData + self.currentData = data + + var updateAudioLevels = false + + if previousData?.groupCall !== data.groupCall { + let membersText: String + if data.participantCount == 0 { + membersText = self.strings.VoiceChat_Panel_TapToJoin + } else if let groupCall = data.groupCall, groupCall.isStream { + membersText = self.strings.LiveStream_ViewerCount(Int32(data.participantCount)) + } else { + membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount)) + } + self.currentText = membersText + + if data.info.isStream { + self.avatarsContent = self.avatarsContext.update(peers: [], animated: false) + } else { + self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.compactMap { $0.peer }, animated: false) + + if let imageDisposable = self.imageDisposable { + self.imageDisposable = nil + imageDisposable.dispose() + } + } + + self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor) + + self.callState = nil + + self.membersDisposable.set(nil) + self.isMutedDisposable.set(nil) + + if let groupCall = data.groupCall { + self.membersDisposable.set((groupCall.summaryState + |> deliverOnMainQueue).start(next: { [weak self] summaryState in + guard let strongSelf = self, let summaryState = summaryState else { + return + } + + let membersText: String + if summaryState.participantCount == 0 { + membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin + } else if let info = summaryState.info, info.isStream { + membersText = strongSelf.strings.LiveStream_ViewerCount(Int32(summaryState.participantCount)) + } else { + membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount)) + } + strongSelf.currentText = membersText + + if let info = summaryState.info, info.isStream { + strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: [], animated: false) + } else { + strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.compactMap { $0.peer }, animated: false) + } + + if let (size, leftInset, rightInset, isHidden) = strongSelf.validLayout { + strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isHidden: isHidden, transition: .immediate) + } + })) + + self.isMutedDisposable.set((groupCall.state + |> deliverOnMainQueue).start(next: { [weak self] callState in + guard let strongSelf = self else { + return + } + + var transition: ContainedViewLayoutTransition = .immediate + if strongSelf.callState != nil { + transition = .animated(duration: 0.3, curve: .spring) + } + + strongSelf.callState = callState + + if let (size, leftInset, rightInset, isHidden) = strongSelf.validLayout { + strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isHidden: isHidden, transition: transition) + } + })) + + self.audioLevelDisposable.set((groupCall.myAudioLevel + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let strongSelf = self else { + return + } + + if strongSelf.audioLevelView == nil, strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency { + let blobFrame = CGRect(origin: CGPoint(), size: CGSize(width: 36.0, height: 36.0)).insetBy(dx: -12.0, dy: -12.0) + + let audioLevelView = VoiceBlobView( + frame: blobFrame, + maxLevel: 0.3, + smallBlobRange: (0, 0), + mediumBlobRange: (0.7, 0.8), + bigBlobRange: (0.8, 0.9) + ) + + let maskRect = CGRect(origin: .zero, size: blobFrame.size) + let playbackMaskLayer = CAShapeLayer() + playbackMaskLayer.frame = maskRect + playbackMaskLayer.fillRule = .evenOdd + let maskPath = UIBezierPath() + maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 12, dy: 12), cornerRadius: 22)) + maskPath.append(UIBezierPath(rect: maskRect)) + playbackMaskLayer.path = maskPath.cgPath + audioLevelView.layer.mask = playbackMaskLayer + + audioLevelView.setColor(UIColor(rgb: 0x30B251)) + strongSelf.audioLevelView = audioLevelView + + strongSelf.micButton.view.insertSubview(audioLevelView, at: 0) + } + + strongSelf.audioLevelView?.updateLevel(CGFloat(value) * 2.0) + if value > 0.0 { + strongSelf.audioLevelView?.startAnimating() + } else { + strongSelf.audioLevelView?.stopAnimating(duration: 0.5) + } + })) + } + } else if data.groupCall == nil { + self.audioLevelDisposable.set(nil) + + let membersText: String + if data.participantCount == 0 { + membersText = self.strings.VoiceChat_Panel_TapToJoin + } else if data.info.isStream { + membersText = self.strings.LiveStream_ViewerCount(Int32(data.participantCount)) + } else { + membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount)) + } + self.currentText = membersText + + if data.info.isStream { + self.avatarsContent = self.avatarsContext.update(peers: [], animated: false) + } else { + self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.compactMap { $0.peer }, animated: false) + } + + updateAudioLevels = true + } + + #if DEBUG + if data.info.isStream, !"".isEmpty { + if self.imageDisposable == nil { + let engine = self.context.engine + let info = data.info + self.imageDisposable = (engine.calls.getAudioBroadcastDataSource(callId: info.id, accessHash: info.accessHash) + |> mapToSignal { source -> Signal in + guard let source else { + return .single(nil) + } + + let time = engine.calls.requestStreamState(dataSource: source, callId: info.id, accessHash: info.accessHash) + |> map { state -> Int64? in + guard let state else { + return nil + } + return state.channels.first?.latestTimestamp + } + + return time + |> mapToSignal { latestTimestamp -> Signal in + guard let latestTimestamp else { + return .single(nil) + } + + let durationMilliseconds: Int64 = 32000 + let bufferOffset: Int64 = 1 * durationMilliseconds + let timestampId = (latestTimestamp / durationMilliseconds) * durationMilliseconds - bufferOffset + + return engine.calls.getVideoBroadcastPart(dataSource: source, callId: info.id, accessHash: info.accessHash, timestampIdMilliseconds: timestampId, durationMilliseconds: durationMilliseconds, channelId: 2, quality: 0) + |> mapToSignal { result -> Signal in + switch result.status { + case let .data(data): + return .single(data) + case .notReady, .resyncNeeded, .rejoinNeeded: + return .single(nil) + } + } + } + } + |> deliverOnMainQueue).start(next: { [weak self] data in + guard let self, let data else { + return + } + + var image: UIImage? + for i in 0 ..< 100 { + image = UIImage(data: data.subdata(in: i ..< data.count)) + if image != nil { + break + } + } + self.previewImage = image + if let (size, leftInset, rightInset, isHidden) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isHidden: isHidden, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + }) + } + } + #endif + + if let (size, leftInset, rightInset, isHidden) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isHidden: isHidden, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + + if updateAudioLevels { + for peerId in data.activeSpeakers { + if self.audioLevelGenerators[peerId] == nil { + self.audioLevelGenerators[peerId] = FakeAudioLevelGenerator() + } + } + var removeGenerators: [PeerId] = [] + for peerId in self.audioLevelGenerators.keys { + if !data.activeSpeakers.contains(peerId) { + removeGenerators.append(peerId) + } + } + for peerId in removeGenerators { + self.audioLevelGenerators.removeValue(forKey: peerId) + } + + if self.audioLevelGenerators.isEmpty { + self.audioLevelGeneratorTimer?.invalidate() + self.audioLevelGeneratorTimer = nil + self.avatarsNode.updateAudioLevels(color: self.theme.chat.inputPanel.actionControlFillColor, backgroundColor: self.theme.chat.inputPanel.actionControlFillColor, levels: [:]) + } else if self.audioLevelGeneratorTimer == nil { + let audioLevelGeneratorTimer = SwiftSignalKit.Timer(timeout: 1.0 / 30.0, repeat: true, completion: { [weak self] in + self?.sampleAudioGenerators() + }, queue: .mainQueue()) + self.audioLevelGeneratorTimer = audioLevelGeneratorTimer + audioLevelGeneratorTimer.start() + } + } + } + + private func sampleAudioGenerators() { + var levels: [PeerId: Float] = [:] + for (peerId, generator) in self.audioLevelGenerators { + levels[peerId] = generator.get() + } + self.avatarsNode.updateAudioLevels(color: self.theme.chat.inputPanel.actionControlFillColor, backgroundColor: self.theme.chat.inputPanel.actionControlFillColor, levels: levels) + } + + public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, isHidden: Bool, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, leftInset, rightInset, isHidden) + + let staticTransition: ContainedViewLayoutTransition = .immediate + + let panelHeight = size.height + + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: isHidden ? -size.height : 0.0), size: size)) + transition.updateAlpha(node: self.contentNode, alpha: isHidden ? 0.0 : 1.0) + + self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width - 7.0 - 36.0 - 7.0, height: panelHeight)) + + if let avatarsContent = self.avatarsContent { + var avatarsTransition = transition + if self.avatarsNode.bounds.isEmpty { + avatarsTransition = .immediate + } + + let avatarsSize = self.avatarsNode.update(context: self.context, content: avatarsContent, itemSize: CGSize(width: 32.0, height: 32.0), animated: avatarsTransition.isAnimated, synchronousLoad: true) + avatarsTransition.updateFrame(node: self.avatarsNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarsSize.width) / 2.0), y: floor((size.height - avatarsSize.height) / 2.0)), size: avatarsSize)) + } + + var joinText = self.strings.VoiceChat_PanelJoin + var title = self.strings.VoiceChat_Title + var isChannel = false + if let currentData = self.currentData { + if currentData.isChannel || currentData.info.isStream { + if let titleValue = currentData.info.title, !titleValue.isEmpty { + title = titleValue + } else { + title = self.strings.VoiceChatChannel_Title + } + isChannel = true + } + } + var text = self.currentText + var isScheduled = false + var isLate = false + if let info = self.currentData?.info, let scheduleTime = info.scheduleTimestamp { + isScheduled = true + if let voiceChatTitle = self.currentData?.info.title { + title = voiceChatTitle + text = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: scheduleTime, alwaysShowTime: true, format: HumanReadableStringFormat(dateFormatString: { isChannel ? self.strings.Conversation_ScheduledLiveStreamStartsOn($0) : self.strings.Conversation_ScheduledVoiceChatStartsOn($0) }, tomorrowFormatString: { isChannel ? self.strings.Conversation_ScheduledLiveStreamStartsTomorrow($0) : self.strings.Conversation_ScheduledVoiceChatStartsTomorrow($0) }, todayFormatString: { isChannel ? self.strings.Conversation_ScheduledLiveStreamStartsToday($0) : self.strings.Conversation_ScheduledVoiceChatStartsToday($0) })).string + } else { + title = isChannel ? self.strings.Conversation_ScheduledLiveStream : self.strings.Conversation_ScheduledVoiceChat + text = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: scheduleTime, alwaysShowTime: true, format: HumanReadableStringFormat(dateFormatString: { self.strings.Conversation_ScheduledVoiceChatStartsOnShort($0) }, tomorrowFormatString: { self.strings.Conversation_ScheduledVoiceChatStartsTomorrowShort($0) }, todayFormatString: { self.strings.Conversation_ScheduledVoiceChatStartsTodayShort($0) })).string + } + + if info.subscribedToScheduled { + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let elapsedTime = scheduleTime - currentTime + if elapsedTime >= 86400 { + joinText = scheduledTimeIntervalString(strings: strings, value: elapsedTime).uppercased() + } else if elapsedTime < 0 { + joinText = "-\(textForTimeout(value: abs(elapsedTime)))".uppercased() + isLate = true + } else { + joinText = textForTimeout(value: elapsedTime).uppercased() + } + } else { + joinText = strings.Chat_TitleVideochatPanel_NotifyScheduledButton + } + + if self.updateTimer == nil { + let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in + if let strongSelf = self, let (size, leftInset, rightInset, isHidden) = strongSelf.validLayout { + strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isHidden: isHidden, transition: .immediate) + } + }, queue: Queue.mainQueue()) + self.updateTimer = timer + timer.start() + } + } else { + if let timer = self.updateTimer { + self.updateTimer = nil + timer.invalidate() + } + if let voiceChatTitle = self.currentData?.info.title, voiceChatTitle.count < 15 { + title = voiceChatTitle + } + } + + if self.isScheduled != isScheduled || self.isLate != isLate { + self.isScheduled = isScheduled + self.isLate = isLate + self.updateJoinButton() + } + + self.joinButtonTitleNode.attributedText = NSAttributedString(string: joinText, font: Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: isScheduled ? .white : self.theme.chat.inputPanel.actionControlForegroundColor) + + let joinButtonTitleSize = self.joinButtonTitleNode.updateLayout(CGSize(width: 150.0, height: .greatestFiniteMagnitude)) + let joinButtonSize = CGSize(width: joinButtonTitleSize.width + 20.0, height: 28.0) + let joinButtonFrame = CGRect(origin: CGPoint(x: size.width - rightInset - 7.0 - joinButtonSize.width, y: floor((panelHeight - joinButtonSize.height) / 2.0)), size: joinButtonSize) + staticTransition.updateFrame(node: self.joinButton, frame: joinButtonFrame) + staticTransition.updateFrame(node: self.joinButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: joinButtonFrame.size)) + staticTransition.updateFrame(node: self.joinButtonTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((joinButtonFrame.width - joinButtonTitleSize.width) / 2.0), y: floorToScreenPixels((joinButtonFrame.height - joinButtonTitleSize.height) / 2.0)), size: joinButtonTitleSize)) + + if let previewImage = self.previewImage { + let previewImageNode: ASImageNode + if let current = self.previewImageNode { + previewImageNode = current + } else { + previewImageNode = ASImageNode() + previewImageNode.clipsToBounds = true + previewImageNode.cornerRadius = 8.0 + previewImageNode.contentMode = .scaleAspectFill + self.previewImageNode = previewImageNode + self.contentNode.addSubnode(previewImageNode) + } + previewImageNode.image = previewImage + let previewSize = CGSize(width: 40.0, height: 40.0) + previewImageNode.frame = CGRect(origin: CGPoint(x: joinButtonFrame.minX - previewSize.width - 8.0, y: joinButtonFrame.minY + floor((joinButtonFrame.height - previewSize.height) / 2.0)), size: previewSize) + } else if let previewImageNode = self.previewImageNode { + self.previewImageNode = nil + previewImageNode.removeFromSupernode() + } + + let micButtonSize = CGSize(width: 36.0, height: 36.0) + let micButtonFrame = CGRect(origin: CGPoint(x: size.width - rightInset - 7.0 - micButtonSize.width, y: floor((panelHeight - micButtonSize.height) / 2.0)), size: micButtonSize) + staticTransition.updateFrame(node: self.micButton, frame: micButtonFrame) + staticTransition.updateFrame(node: self.micButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: micButtonFrame.size)) + + let animationSize = CGSize(width: 36.0, height: 36.0) + staticTransition.updateFrame(node: self.micButtonForegroundNode, frame: CGRect(origin: CGPoint(x: floor((micButtonFrame.width - animationSize.width) / 2.0), y: floor((micButtonFrame.height - animationSize.height) / 2.0)), size: animationSize)) + + var isMuted = true + if let _ = self.callState?.muteState { + isMuted = true + } else { + isMuted = false + } + self.micButtonForegroundNode.update(state: VoiceChatMicrophoneNode.State(muted: isMuted, filled: false, color: UIColor.white), animated: transition.isAnimated) + + if isMuted != self.micButtonBackgroundNodeIsMuted { + self.micButtonBackgroundNodeIsMuted = isMuted + let updatedImage = generateStretchableFilledCircleImage(diameter: 36.0, color: isMuted ? UIColor(rgb: 0xb6b6bb) : UIColor(rgb: 0x30b251)) + + if let updatedImage = updatedImage, let previousImage = self.micButtonBackgroundNode.image?.cgImage, transition.isAnimated { + self.micButtonBackgroundNode.image = updatedImage + self.micButtonBackgroundNode.layer.animate(from: previousImage, to: updatedImage.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.25, delay: 0.0) + } else { + self.micButtonBackgroundNode.image = updatedImage + } + } + + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(15.0), textColor: self.theme.chat.inputPanel.primaryTextColor) + + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor) + + var constrainedWidth = size.width - leftInset - rightInset - 32.0 - joinButtonSize.width - 60.0 + if isScheduled { + constrainedWidth = size.width - 100.0 + } + + let titleSize = self.titleNode.updateLayout(CGSize(width: constrainedWidth, height: .greatestFiniteMagnitude)) + let textSize = self.textNode.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude)) + + let titleFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 9.0), size: titleSize) + staticTransition.updateFrame(node: self.titleNode, frame: titleFrame) + staticTransition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset + 16.0, y: titleFrame.maxY + 1.0), size: textSize)) + + if let image = self.muteIconNode.image { + staticTransition.updateFrame(node: self.muteIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + 5.0), size: image.size)) + } + self.muteIconNode.isHidden = self.currentData?.groupCall != nil + self.joinButton.isHidden = self.currentData?.groupCall != nil + self.micButton.isHidden = self.currentData?.groupCall == nil + + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel))) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: panelHeight))) + } + + public func animateIn(_ transition: ContainedViewLayoutTransition) { + let contentPosition = self.contentNode.layer.position + transition.animatePosition(node: self.contentNode, from: CGPoint(x: contentPosition.x, y: contentPosition.y - 50.0)) + + guard let (size, _, _, _) = self.validLayout else { + return + } + + transition.animatePositionAdditive(node: self.separatorNode, offset: CGPoint(x: 0.0, y: size.height)) + } + + public func animateOut(_ transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { + let contentPosition = self.contentNode.layer.position + transition.animatePosition(node: self.contentNode, to: CGPoint(x: contentPosition.x, y: contentPosition.y - 50.0), removeOnCompletion: false, completion: { _ in + completion() + }) + + guard let (size, _, _, _) = self.validLayout else { + return + } + + transition.updatePosition(node: self.separatorNode, position: self.separatorNode.position.offsetBy(dx: 0.0, dy: size.height)) + } + + func rightButtonSnapshotViews() -> (background: UIView, foreground: UIView)? { + if !self.joinButton.isHidden { + if let foregroundView = self.joinButtonTitleNode.view.snapshotContentTree() { + let backgroundFrame = self.joinButtonBackgroundNode.view.convert(self.joinButtonBackgroundNode.bounds, to: nil) + let foregroundFrame = self.joinButtonTitleNode.view.convert(self.joinButtonTitleNode.bounds, to: nil) + + let backgroundView = UIView() + backgroundView.backgroundColor = self.theme.chat.inputPanel.actionControlFillColor + backgroundView.frame = backgroundFrame + backgroundView.layer.cornerRadius = backgroundFrame.height / 2.0 + + foregroundView.frame = foregroundFrame + return (backgroundView, foregroundView) + } + } else if !self.micButton.isHidden { + if let foregroundView = self.micButtonForegroundNode.view.snapshotContentTree() { + let backgroundFrame = self.micButtonBackgroundNode.view.convert(self.micButtonBackgroundNode.bounds, to: nil) + let foregroundFrame = self.micButtonForegroundNode.view.convert(self.micButtonForegroundNode.bounds, to: nil) + + let backgroundView = UIView() + backgroundView.backgroundColor = (self.micButtonBackgroundNodeIsMuted ?? true) ? UIColor(rgb: 0xb6b6bb) : UIColor(rgb: 0x30b251) + backgroundView.frame = backgroundFrame + backgroundView.layer.cornerRadius = backgroundFrame.height / 2.0 + + foregroundView.frame = foregroundFrame + return (backgroundView, foregroundView) + } + } + + return nil + } +} diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD index 8b9993c9..442a3f6a 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/BUILD @@ -24,6 +24,11 @@ swift_library( "//submodules/SearchUI:SearchUI", "//submodules/MergeLists:MergeLists", "//submodules/UndoUI:UndoUI", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/AppBundle", + "//submodules/ActivityIndicator", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift index a536dab8..622412a7 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift @@ -170,7 +170,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { self.removeButtonIcon = ASImageNode() self.removeButtonIcon.displaysAsynchronously = false - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) if let placeholderNode = self.placeholderNode { self.addSubnode(placeholderNode) diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift index 7f275d26..4b215b5e 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift @@ -105,8 +105,8 @@ private final class GroupStickerSearchItemNode: ItemListControllerSearchNode { } override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight))) - self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)), navigationBarHeight: 0.0, transition: transition) + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) + self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height)), navigationBarHeight: navigationBarHeight, transition: transition) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift index 408019f9..d8dcb0c7 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift @@ -8,34 +8,94 @@ import TelegramPresentationData import ItemListUI import PresentationDataUtils import SearchBarNode +import GlassBackgroundComponent +import ComponentFlow +import ComponentDisplayAdapters +import AppBundle +import ActivityIndicator private let searchBarFont = Font.regular(17.0) final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, ItemListControllerSearchNavigationContentNode { + private struct Params: Equatable { + let size: CGSize + let leftInset: CGFloat + let rightInset: CGFloat + + init(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + self.size = size + self.leftInset = leftInset + self.rightInset = rightInset + } + } + private var theme: PresentationTheme private let strings: PresentationStrings private let cancel: () -> Void + private let backgroundContainer: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView + private let iconView: UIImageView + private var activityIndicator: ActivityIndicator? private let searchBar: SearchBarNode + private let close: (background: GlassBackgroundView, icon: UIImageView) + + private var params: Params? private var queryUpdated: ((String) -> Void)? var activity: Bool = false { didSet { - self.searchBar.activity = activity + if self.activity != oldValue { + if let params = self.params { + let _ = self.updateLayout(size: params.size, leftInset: params.leftInset, rightInset: params.rightInset, transition: .immediate) + } + } } } + init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) { self.theme = theme self.strings = strings self.cancel = cancel - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: false) + self.backgroundContainer = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + self.backgroundContainer.contentView.addSubview(self.backgroundView) + self.iconView = UIImageView() + self.backgroundView.contentView.addSubview(self.iconView) + + self.close = (GlassBackgroundView(), UIImageView()) + self.close.background.contentView.addSubview(self.close.icon) + + self.searchBar = SearchBarNode( + theme: SearchBarNodeTheme( + background: .clear, + separator: .clear, + inputFill: .clear, + primaryText: theme.chat.inputPanel.panelControlColor, + placeholder: theme.chat.inputPanel.inputPlaceholderColor, + inputIcon: theme.chat.inputPanel.inputControlColor, + inputClear: theme.chat.inputPanel.panelControlColor, + accent: theme.chat.inputPanel.panelControlAccentColor, + keyboard: theme.rootController.keyboardColor + ), + presentationTheme: theme, + strings: strings, + fieldStyle: .inlineNavigation, + forceSeparator: false, + displayBackground: false, + cancelText: nil + ) super.init() - self.addSubnode(self.searchBar) + self.view.addSubview(self.backgroundContainer) + self.backgroundView.contentView.addSubview(self.searchBar.view) + + self.backgroundContainer.contentView.addSubview(self.close.background) + self.close.background.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:)))) self.searchBar.cancel = { [weak self] in self?.searchBar.deactivate(clear: false) @@ -47,19 +107,28 @@ final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, I } updateActivity({ [weak self] value in - self?.activity = value + guard let self else { + return + } + self.activity = value }) self.updatePlaceholder() } + @objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.searchBar.cancel?() + } + } + func setQueryUpdated(_ f: @escaping (String) -> Void) { self.queryUpdated = f } func updateTheme(_ theme: PresentationTheme) { self.theme = theme - self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: self.theme), strings: self.strings) + self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: self.theme), presentationTheme: self.theme, strings: self.strings) self.updatePlaceholder() } @@ -68,13 +137,87 @@ final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, I } override var nominalHeight: CGFloat { - return 54.0 + return 60.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { - let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0)) - self.searchBar.frame = searchBarFrame - self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + self.params = Params(size: size, leftInset: leftInset, rightInset: rightInset) + + let transition = ComponentTransition(transition) + + let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0), size: CGSize(width: size.width - 16.0 * 2.0 - leftInset - rightInset - 44.0 - 8.0, height: 44.0)) + let closeFrame = CGRect(origin: CGPoint(x: size.width - 16.0 - rightInset - 44.0, y: backgroundFrame.minY), size: CGSize(width: 44.0, height: 44.0)) + + transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size)) + self.backgroundContainer.update(size: size, isDark: self.theme.overallDarkAppearance, transition: transition) + + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + if self.iconView.image == nil { + self.iconView.image = UIImage(bundleImageName: "Navigation/Search")?.withRenderingMode(.alwaysTemplate) + } + transition.setTintColor(view: self.iconView, color: self.theme.rootController.navigationSearchBar.inputIconColor) + + if let image = self.iconView.image { + let imageSize: CGSize + let iconFrame: CGRect + let iconFraction: CGFloat = 0.8 + imageSize = CGSize(width: image.size.width * iconFraction, height: image.size.height * iconFraction) + iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floor((backgroundFrame.height - imageSize.height) * 0.5)), size: imageSize) + transition.setPosition(view: self.iconView, position: iconFrame.center) + transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + } + + if self.activity { + let activityIndicator: ActivityIndicator + if let current = self.activityIndicator { + activityIndicator = current + } else { + activityIndicator = ActivityIndicator(type: .custom(self.theme.chat.inputPanel.inputControlColor, 14.0, 14.0, false)) + self.activityIndicator = activityIndicator + self.backgroundView.contentView.addSubview(activityIndicator.view) + } + let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0)) + let indicatorFrame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((backgroundFrame.height - indicatorSize.height) * 0.5)), size: indicatorSize) + transition.setPosition(view: activityIndicator.view, position: indicatorFrame.center) + transition.setBounds(view: activityIndicator.view, bounds: CGRect(origin: CGPoint(), size: indicatorFrame.size)) + } else if let activityIndicator = self.activityIndicator { + self.activityIndicator = nil + activityIndicator.view.removeFromSuperview() + } + self.iconView.isHidden = self.activity + + let searchBarFrame = CGRect(origin: CGPoint(x: 36.0, y: 0.0), size: CGSize(width: backgroundFrame.width - 36.0 - 4.0, height: 44.0)) + transition.setFrame(view: self.searchBar.view, frame: searchBarFrame) + self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition) + + if self.close.icon.image == nil { + self.close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setStrokeColor(UIColor.white.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0)) + context.move(to: CGPoint(x: size.width - 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) + } + + if let image = close.icon.image { + self.close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size)) + } + self.close.icon.tintColor = self.theme.chat.inputPanel.panelControlColor + + transition.setFrame(view: self.close.background, frame: closeFrame) + self.close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + return size } func activate() { @@ -85,4 +228,3 @@ final class GroupStickerSearchNavigationContentNode: NavigationBarContentNode, I self.searchBar.deactivate(clear: false) } } - diff --git a/submodules/TelegramUI/Components/HeaderPanelContainerComponent/BUILD b/submodules/TelegramUI/Components/HeaderPanelContainerComponent/BUILD new file mode 100644 index 00000000..c509cc4c --- /dev/null +++ b/submodules/TelegramUI/Components/HeaderPanelContainerComponent/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "HeaderPanelContainerComponent", + module_name = "HeaderPanelContainerComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift b/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift new file mode 100644 index 00000000..b48bdf91 --- /dev/null +++ b/submodules/TelegramUI/Components/HeaderPanelContainerComponent/Sources/HeaderPanelContainerComponent.swift @@ -0,0 +1,267 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData +import GlassBackgroundComponent + +public protocol HeaderPanelContainerChildView: UIView { + func setOverlayContainerView(overlayContainerView: UIView) +} + +public final class HeaderPanelContainerComponent: Component { + public final class Panel: Equatable { + public let key: AnyHashable + public let orderIndex: Int + public let component: AnyComponent + + public init(key: AnyHashable, orderIndex: Int, component: AnyComponent) { + self.key = key + self.orderIndex = orderIndex + self.component = component + } + + public static func ==(lhs: Panel, rhs: Panel) -> Bool { + if lhs.key != rhs.key { + return false + } + if lhs.orderIndex != rhs.orderIndex { + return false + } + if lhs.component != rhs.component { + return false + } + return true + } + } + + public let theme: PresentationTheme + public let tabs: AnyComponent? + public let panels: [Panel] + + public init( + theme: PresentationTheme, + tabs: AnyComponent?, + panels: [Panel] + ) { + self.theme = theme + self.tabs = tabs + self.panels = panels + } + + public static func ==(lhs: HeaderPanelContainerComponent, rhs: HeaderPanelContainerComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.tabs != rhs.tabs { + return false + } + if lhs.panels != rhs.panels { + return false + } + return true + } + + private final class PanelItemView: UIView { + let view = ComponentView() + let separator = SimpleLayer() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + + public final class View: UIView { + private let backgroundContainer: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView + private let contentContainer: UIView + + private var tabsView: ComponentView? + private var panelViews: [AnyHashable: PanelItemView] = [:] + + private var component: HeaderPanelContainerComponent? + private weak var state: EmptyComponentState? + + public var tabs: UIView? { + return self.tabsView?.view + } + + public func panel(forKey key: AnyHashable) -> UIView? { + return self.panelViews[key]?.view.view + } + + override init(frame: CGRect) { + self.backgroundContainer = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + self.contentContainer = UIView() + self.contentContainer.clipsToBounds = true + + super.init(frame: frame) + + self.backgroundContainer.contentView.addSubview(self.backgroundView) + self.addSubview(self.backgroundContainer) + + self.backgroundView.contentView.addSubview(self.contentContainer) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: HeaderPanelContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + var isAnimatingReplacement = false + if let previousComponent = self.component { + isAnimatingReplacement = !component.panels.contains(where: { panel in previousComponent.panels.contains(where: { $0.key == panel.key }) }) + } + + self.component = component + self.state = state + + let sideInset: CGFloat = 16.0 + + var size = CGSize(width: availableSize.width, height: 0.0) + + var isFirstPanel = true + + if let tabs = component.tabs { + let tabsView: ComponentView + var tabsTransition = transition + if let current = self.tabsView { + tabsView = current + } else { + tabsTransition = tabsTransition.withAnimation(.none) + tabsView = ComponentView() + self.tabsView = tabsView + } + let tabsSize = tabsView.update( + transition: tabsTransition, + component: tabs, + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 40.0) + ) + let tabsFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: tabsSize) + if let tabsComponentView = tabsView.view { + if tabsComponentView.superview == nil { + self.contentContainer.addSubview(tabsComponentView) + if let tabsComponentView = tabsComponentView as? HeaderPanelContainerChildView { + tabsComponentView.setOverlayContainerView(overlayContainerView: self.backgroundContainer.contentView) + } + transition.animateAlpha(view: tabsComponentView, from: 0.0, to: 1.0) + } + tabsTransition.setFrame(view: tabsComponentView, frame: tabsFrame) + } + size.height += tabsSize.height + isFirstPanel = false + } else if let tabsView = self.tabsView { + self.tabsView = nil + if let tabsComponentView = tabsView.view { + transition.setAlpha(view: tabsComponentView, alpha: 0.0, completion: { [weak tabsComponentView] _ in + tabsComponentView?.removeFromSuperview() + }) + } + } + + var validPanelKeys: [AnyHashable] = [] + for panel in component.panels { + validPanelKeys.append(panel.key) + + var panelTransition = transition + let panelView: PanelItemView + if let current = self.panelViews[panel.key] { + panelView = current + } else { + panelTransition = panelTransition.withAnimation(.none) + panelView = PanelItemView() + self.panelViews[panel.key] = panelView + self.contentContainer.layer.insertSublayer(panelView.separator, at: 0) + self.contentContainer.addSubview(panelView) + } + + let panelSize = panelView.view.update( + transition: panelTransition, + component: panel.component, + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: panelSize) + if let panelComponentView = panelView.view.view { + if panelComponentView.superview == nil { + panelView.addSubview(panelComponentView) + transition.animateAlpha(view: panelView, from: 0.0, to: 1.0) + panelView.separator.opacity = 0.0 + panelView.clipsToBounds = true + if isAnimatingReplacement { + panelView.frame = panelFrame + } else { + panelView.frame = CGRect(origin: panelFrame.origin, size: CGSize(width: panelFrame.width, height: 0.0)) + } + } + + panelView.separator.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor + + let isFrameUpdated = panelComponentView.frame != panelFrame + transition.setFrame(view: panelView, frame: panelFrame, completion: { [weak panelView] completed in + if let panelView, completed, isFrameUpdated { + panelView.clipsToBounds = false + } + }) + panelTransition.setFrame(view: panelComponentView, frame: CGRect(origin: CGPoint(), size: panelFrame.size)) + panelTransition.setFrame(layer: panelView.separator, frame: CGRect(origin: panelFrame.origin, size: CGSize(width: panelFrame.width, height: UIScreenPixel))) + + transition.setAlpha(layer: panelView.separator, alpha: isFirstPanel ? 0.0 : 1.0) + } + size.height += panelSize.height + isFirstPanel = false + } + + var removedPanelKeys: [AnyHashable] = [] + for (key, panelView) in self.panelViews { + if !validPanelKeys.contains(key) { + removedPanelKeys.append(key) + transition.setAlpha(view: panelView, alpha: 0.0, completion: { [weak panelView] _ in + panelView?.removeFromSuperview() + }) + let separator = panelView.separator + transition.setAlpha(layer: separator, alpha: 0.0, completion: { [weak separator] _ in + separator?.removeFromSuperlayer() + }) + if !isAnimatingReplacement { + panelView.clipsToBounds = true + transition.setFrame(view: panelView, frame: CGRect(origin: panelView.frame.origin, size: CGSize(width: panelView.bounds.width, height: 0.0))) + } + } + } + for key in removedPanelKeys { + self.panelViews.removeValue(forKey: key) + } + + let backgroundSize = CGSize(width: size.width, height: max(40.0, size.height)) + + transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: backgroundSize)) + self.backgroundContainer.update(size: backgroundSize, isDark: component.theme.overallDarkAppearance, transition: transition) + + let backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: CGSize(width: size.width - sideInset * 2.0, height: backgroundSize.height)) + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: 20.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + + transition.setAlpha(view: self.backgroundContainer, alpha: (component.tabs != nil || !component.panels.isEmpty) ? 1.0 : 0.0) + + transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) + self.contentContainer.layer.cornerRadius = 20.0 + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/HorizontalTabsComponent/BUILD b/submodules/TelegramUI/Components/HorizontalTabsComponent/BUILD new file mode 100644 index 00000000..7f31dd2d --- /dev/null +++ b/submodules/TelegramUI/Components/HorizontalTabsComponent/BUILD @@ -0,0 +1,29 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "HorizontalTabsComponent", + module_name = "HorizontalTabsComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/TelegramCore", + "//submodules/AsyncDisplayKit", + "//submodules/AnimationUI", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextWithEntitiesComponent", + "//submodules/TelegramUI/Components/LiquidLens", + "//submodules/TelegramUI/Components/TextBadgeComponent", + "//submodules/TelegramUI/Components/HeaderPanelContainerComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/HorizontalTabsComponent/Sources/HorizontalTabsComponent.swift b/submodules/TelegramUI/Components/HorizontalTabsComponent/Sources/HorizontalTabsComponent.swift new file mode 100644 index 00000000..789b2613 --- /dev/null +++ b/submodules/TelegramUI/Components/HorizontalTabsComponent/Sources/HorizontalTabsComponent.swift @@ -0,0 +1,1270 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData +import AccountContext +import TelegramCore +import MultilineTextWithEntitiesComponent +import TextBadgeComponent +import LiquidLens +import HeaderPanelContainerComponent + +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 InternalGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if otherGestureRecognizer is UIPanGestureRecognizer { + return true + } else { + return false + } + } +} + +private final class ReorderingGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { + private let internalDelegate = InternalGestureRecognizerDelegate() + + 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.internalDelegate + } + + override func reset() { + super.reset() + + self.initialLocation = nil + self.delayTimer?.invalidate() + self.delayTimer = nil + self.currentLocation = nil + } + + 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) + } + } + } +} + + +public final class HorizontalTabsComponent: Component { + public final class Tab: Equatable { + public typealias Id = AnyHashable + + public struct Badge: Equatable { + public var title: String + public var isAccent: Bool + + public init(title: String, isAccent: Bool) { + self.title = title + self.isAccent = isAccent + } + } + + public struct Title: Equatable { + public let text: String + public let entities: [MessageTextEntity] + public let enableAnimations: Bool + + public init(text: String, entities: [MessageTextEntity], enableAnimations: Bool) { + self.text = text + self.entities = entities + self.enableAnimations = enableAnimations + } + } + + public enum Content: Equatable { + case title(Title) + case custom(AnyComponent) + } + + public let id: AnyHashable + public let content: Content + public let badge: Badge? + public let action: () -> Void + public let contextAction: ((ContextExtractedContentContainingView, ContextGesture?) -> Void)? + public let deleteAction: (() -> Void)? + + public init(id: AnyHashable, content: Content, badge: Badge?, action: @escaping () -> Void, contextAction: ((ContextExtractedContentContainingView, ContextGesture?) -> Void)?, deleteAction: (() -> Void)?) { + self.id = id + self.content = content + self.badge = badge + self.action = action + self.contextAction = contextAction + self.deleteAction = deleteAction + } + + public static func ==(lhs: Tab, rhs: Tab) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.content != rhs.content { + return false + } + if lhs.badge != rhs.badge { + return false + } + if (lhs.contextAction == nil) != (rhs.contextAction == nil) { + return false + } + if (lhs.deleteAction == nil) != (rhs.deleteAction == nil) { + return false + } + return true + } + } + + public enum Layout { + case fit + case fill + } + + public let context: AccountContext? + public let theme: PresentationTheme + public let tabs: [Tab] + public let selectedTab: Tab.Id? + public let isEditing: Bool + public let layout: Layout + public let liftWhileSwitching: Bool + + public init( + context: AccountContext?, + theme: PresentationTheme, + tabs: [Tab], + selectedTab: Tab.Id?, + isEditing: Bool, + layout: Layout = .fill, + liftWhileSwitching: Bool = true + ) { + self.context = context + self.theme = theme + self.tabs = tabs + self.selectedTab = selectedTab + self.isEditing = isEditing + self.layout = layout + self.liftWhileSwitching = liftWhileSwitching + } + + public static func ==(lhs: HorizontalTabsComponent, rhs: HorizontalTabsComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.tabs != rhs.tabs { + return false + } + if lhs.selectedTab != rhs.selectedTab { + return false + } + if lhs.isEditing != rhs.isEditing { + return false + } + if lhs.layout != rhs.layout { + return false + } + if lhs.liftWhileSwitching != rhs.liftWhileSwitching { + return false + } + return true + } + + private final class ScrollView: UIScrollView { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + + private struct LayoutData { + var size: CGSize + var selectedItemFrame: CGRect + + init(size: CGSize, selectedItemFrame: CGRect) { + self.size = size + self.selectedItemFrame = selectedItemFrame + } + } + + private final class ItemView { + var frame: CGRect = CGRect() + var selectionFrame: CGRect = CGRect() + let regularView = ComponentView() + let selectedView = ComponentView() + + init() { + } + } + + public final class View: UIView, UIScrollViewDelegate, HeaderPanelContainerChildView { + private let lensView: LiquidLensView + private let scrollView: ScrollView + private let selectedScrollView: UIView + private var itemViews: [Tab.Id: ItemView] = [:] + + private var ignoreScrolling: Bool = false + private var tabSwitchFraction: CGFloat = 0.0 + private var isDraggingTabs: Bool = false + private var temporaryLiftTimer: Foundation.Timer? + + private var tapRecognizer: UITapGestureRecognizer? + + private var reorderingGesture: ReorderingGestureRecognizer? + private var reorderingItem: AnyHashable? + private var reorderingItemPosition: (initial: CGFloat, offset: CGFloat)? + private var reorderingAutoScrollAnimator: ConstantDisplayLinkAnimator? + private var initialReorderedItemIds: [AnyHashable]? + public private(set) var reorderedItemIds: [AnyHashable]? + + private var layoutData: LayoutData? + + private var component: HorizontalTabsComponent? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + + override init(frame: CGRect) { + self.lensView = LiquidLensView(kind: .noContainer) + self.scrollView = ScrollView() + + self.selectedScrollView = UIView() + self.selectedScrollView.clipsToBounds = true + + super.init(frame: frame) + + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = false + self.scrollView.scrollsToTop = false + self.scrollView.clipsToBounds = true + self.scrollView.delegate = self + + self.scrollView.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in + guard let self else { + return false + } + return self.scrollView.contentOffset.x > .ulpOfOne + } + + self.addSubview(self.lensView) + + self.lensView.contentView.addSubview(self.scrollView) + self.lensView.selectedContentView.addSubview(self.selectedScrollView) + /*self.lensView.onUpdatedIsAnimating = { [weak self] _ in + guard let self else { + return + } + self.alpha = self.lensView.isAnimating ? 1.0 : 0.7 + }*/ + /*self.lensView.isLiftedAnimationCompleted = { [weak self] in + guard let self else { + return + } + if let temporaryLiftTimer = self.temporaryLiftTimer { + let _ = temporaryLiftTimer + /*self.temporaryLiftTimer = nil + temporaryLiftTimer.invalidate() + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.5)) + }*/ + } + }*/ + + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))) + self.tapRecognizer = tapRecognizer + self.addGestureRecognizer(tapRecognizer) + + let reorderingGesture = ReorderingGestureRecognizer(shouldBegin: { [weak self] point in + guard let self else { + return false + } + for (_, itemView) in self.itemViews { + guard let itemView = itemView.regularView.view else { + continue + } + if itemView.convert(itemView.bounds, to: self).contains(point) { + return true + } + } + return false + }, began: { [weak self] point in + guard let self else { + return + } + self.initialReorderedItemIds = self.reorderedItemIds + for (id, itemView) in self.itemViews { + guard let regularItemView = itemView.regularView.view, let selectedItemView = itemView.selectedView.view else { + continue + } + let itemFrame = regularItemView.convert(regularItemView.bounds, to: self) + if itemFrame.contains(point) { + HapticFeedback().impact() + + self.reorderingItem = id + regularItemView.frame = itemFrame + selectedItemView.frame = itemFrame + + self.reorderingAutoScrollAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in + guard let self, let currentLocation = self.reorderingGesture?.currentLocation else { + return + } + let edgeWidth: CGFloat = 20.0 + if currentLocation.x <= edgeWidth { + var contentOffset = self.scrollView.contentOffset + contentOffset.x = max(0.0, contentOffset.x - 3.0) + self.scrollView.setContentOffset(contentOffset, animated: false) + } else if currentLocation.x >= self.bounds.width - edgeWidth { + var contentOffset = self.scrollView.contentOffset + contentOffset.x = max(0.0, min(self.scrollView.contentSize.width - self.scrollView.bounds.width, contentOffset.x + 3.0)) + self.scrollView.setContentOffset(contentOffset, animated: false) + } + }) + self.reorderingAutoScrollAnimator?.isPaused = false + self.addSubview(regularItemView) + self.addSubview(selectedItemView) + + self.reorderingItemPosition = (regularItemView.frame.minX, 0.0) + self.state?.updated(transition: .easeInOut(duration: 0.25)) + + return + } + } + }, ended: { [weak self] in + guard let self, let reorderingItem = self.reorderingItem else { + return + } + + if let itemView = self.itemViews[reorderingItem], let regularItemView = itemView.regularView.view, let selectedItemView = itemView.selectedView.view { + let projectedItemFrame = regularItemView.convert(regularItemView.bounds, to: self.scrollView) + regularItemView.frame = projectedItemFrame + selectedItemView.frame = projectedItemFrame + self.scrollView.addSubview(regularItemView) + self.selectedScrollView.addSubview(selectedItemView) + } + + /*if strongSelf.currentParams?.canReorderAllChats == false, let firstItem = strongSelf.reorderedItemIds?.first, case .filter = firstItem { + strongSelf.reorderedItemIds = strongSelf.initialReorderedItemIds + strongSelf.presentPremiumTip?() + }*/ + + self.reorderingItem = nil + self.reorderingItemPosition = nil + self.reorderingAutoScrollAnimator?.invalidate() + self.reorderingAutoScrollAnimator = nil + + self.state?.updated(transition: .easeInOut(duration: 0.25)) + }, moved: { [weak self] offset in + guard let self, let reorderingItem = self.reorderingItem else { + return + } + + let minIndex = 0 + if let reorderingItemView = self.itemViews[reorderingItem], let regularItemView = reorderingItemView.regularView.view, let _ = reorderingItemView.selectedView.view, let (initial, _) = self.reorderingItemPosition, let reorderedItemIds = self.reorderedItemIds, let currentItemIndex = reorderedItemIds.firstIndex(of: reorderingItem) { + + for (id, otherItemView) in self.itemViews { + guard let itemIndex = reorderedItemIds.firstIndex(of: id) else { + continue + } + guard let otherRegularItemView = otherItemView.regularView.view else { + continue + } + if id != reorderingItem { + let itemFrame = otherRegularItemView.convert(otherRegularItemView.bounds, to: self) + if regularItemView.frame.intersects(itemFrame) { + let targetIndex: Int + if regularItemView.frame.midX < itemFrame.midX { + targetIndex = max(minIndex, itemIndex - 1) + } else { + targetIndex = max(minIndex, min(reorderedItemIds.count - 1, itemIndex)) + } + if targetIndex != currentItemIndex { + 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) + } + self.reorderedItemIds = updatedReorderedItemIds + + self.state?.updated(transition: .easeInOut(duration: 0.25)) + } + break + } + } + } + + self.reorderingItemPosition = (initial, offset) + } + + self.state?.updated(transition: .immediate) + }) + self.reorderingGesture = reorderingGesture + self.addGestureRecognizer(reorderingGesture) + reorderingGesture.isEnabled = false + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func setOverlayContainerView(overlayContainerView: UIView) { + self.lensView.setLiftedContainer(view: overlayContainerView) + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return self.scrollView.hitTest(self.convert(point, to: self.scrollView), with: event) + } + + @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { + guard let component = self.component else { + return + } + if case .ended = recognizer.state { + let point = recognizer.location(in: self) + for (id, itemView) in self.itemViews { + if self.scrollView.convert(itemView.selectionFrame, to: self).contains(point) { + if let tab = component.tabs.first(where: { $0.id == id }) { + tab.action() + } + } + } + } + } + + public func updateTabSwitchFraction(fraction: CGFloat, isDragging: Bool, transition: ComponentTransition) { + self.tabSwitchFraction = -fraction + self.isDraggingTabs = isDragging + self.state?.updated(transition: transition, isLocal: true) + + /*if self.isDraggingTabs != isDragging { + self.isDraggingTabs = isDragging + + if !isDragging { + self.temporaryLiftTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false, block: { [weak self] timer in + guard let self else { + return + } + if self.temporaryLiftTimer === timer { + self.temporaryLiftTimer = nil + self.state?.updated(transition: .spring(duration: 0.5)) + } + }) + } else { + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) + } + }*/ + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + if self.ignoreScrolling { + return + } + self.updateScrolling(transition: .immediate) + } + + private func updateScrolling(transition: ComponentTransition) { + guard let component = self.component, let layoutData = self.layoutData else { + return + } + var isLifted = self.temporaryLiftTimer != nil + if !component.liftWhileSwitching { + isLifted = false + } + if #available(iOS 26.0, *) { + } else { + isLifted = false + } + self.lensView.update(size: CGSize(width: layoutData.size.width - 3.0 * 2.0, height: layoutData.size.height - 3.0 * 2.0), selectionOrigin: CGPoint(x: -self.scrollView.contentOffset.x + layoutData.selectedItemFrame.minX, y: 0.0), selectionSize: CGSize(width: layoutData.selectedItemFrame.width, height: layoutData.size.height - 3.0 * 2.0), inset: 0.0, liftedInset: 6.0, isDark: component.theme.overallDarkAppearance, isLifted: isLifted, transition: transition) + + transition.setPosition(view: self.selectedScrollView, position: CGRect(origin: CGPoint(x: 3.0, y: 0.0), size: CGSize(width: layoutData.size.width - 3.0 * 2.0, height: layoutData.size.height - 3.0 * 2.0)).center) + transition.setBounds(view: self.selectedScrollView, bounds: CGRect(origin: CGPoint(x: self.scrollView.contentOffset.x, y: 0.0), size: CGSize(width: layoutData.size.width - 3.0 * 2.0, height: layoutData.size.height - 3.0 * 2.0))) + } + + func update(component: HorizontalTabsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + var shouldFocusOnSelectedTab = self.isDraggingTabs + + if component.isEditing { + if self.reorderedItemIds == nil { + self.reorderedItemIds = component.tabs.map(\.id) + } + } else { + self.reorderedItemIds = nil + } + + if self.component?.selectedTab != component.selectedTab { + self.tabSwitchFraction = 0.0 + if !self.isDraggingTabs { + self.temporaryLiftTimer?.invalidate() + self.temporaryLiftTimer = nil + + if !transition.animation.isImmediate { + self.temporaryLiftTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false, block: { [weak self] _ in + guard let self else { + return + } + self.temporaryLiftTimer = nil + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2), isLocal: true) + } + }) + } + } + shouldFocusOnSelectedTab = true + } + + self.component = component + self.state = state + + self.reorderingGesture?.isEnabled = component.isEditing + + let sizeHeight: CGFloat = availableSize.height + + let sideInset: CGFloat = 0.0 + + var validIds: [Tab.Id] = [] + + var orderedTabs = component.tabs + if let reorderedItemIds = self.reorderedItemIds { + orderedTabs.removeAll() + for id in reorderedItemIds { + if let item = component.tabs.first(where: { $0.id == id }) { + orderedTabs.append(item) + } + } + for tab in component.tabs { + if !orderedTabs.contains(where: { $0.id == tab.id }) { + orderedTabs.append(tab) + } + } + } + + var items: [(tabId: AnyHashable, itemView: ItemView, size: CGSize, itemTransition: ComponentTransition)] = [] + + for tab in orderedTabs { + let tabId = tab.id + validIds.append(tabId) + + var itemTransition = transition + let itemView: ItemView + if let current = self.itemViews[tabId] { + itemView = current + } else { + itemTransition = itemTransition.withAnimation(.none) + itemView = ItemView() + self.itemViews[tabId] = itemView + } + + var itemEditing: ItemComponent.Editing? + if component.isEditing { + itemEditing = ItemComponent.Editing(isEditable: true) + } + + let itemSize = itemView.regularView.update( + transition: itemTransition, + component: AnyComponent(ItemComponent( + context: component.context, + theme: component.theme, + tab: tab, + isSelected: false, + editing: itemEditing + )), + environment: {}, + containerSize: CGSize(width: 1000.0, height: sizeHeight - 3.0 * 2.0) + ) + let _ = itemView.selectedView.update( + transition: itemTransition, + component: AnyComponent(ItemComponent( + context: component.context, + theme: component.theme, + tab: tab, + isSelected: true, + editing: itemEditing + )), + environment: {}, + containerSize: CGSize(width: 1000.0, height: sizeHeight - 3.0 * 2.0) + ) + + items.append((tabId, itemView, itemSize, itemTransition)) + } + + var totalContentWidth: CGFloat = sideInset + for item in items { + totalContentWidth += item.size.width + } + totalContentWidth += sideInset + + let scrollContentWidth: CGFloat + if case .fill = component.layout, totalContentWidth < availableSize.width { + let regularItemWidth = floor((availableSize.width - 3.0 * 2.0 - sideInset * 2.0) / CGFloat(items.count)) + let lastItemWidth = (availableSize.width - 3.0 * 2.0 - sideInset * 2.0) - regularItemWidth * CGFloat(items.count - 1) + for i in 0 ..< items.count { + let item = items[i] + let itemWidth = (i == items.count - 1) ? lastItemWidth : regularItemWidth + var itemFrame = CGRect(origin: CGPoint(x: sideInset + regularItemWidth * CGFloat(i) + floor((itemWidth - item.size.width) * 0.5), y: 0.0), size: item.size) + if item.tabId == self.reorderingItem, let (initial, offset) = self.reorderingItemPosition { + itemFrame.origin = CGPoint(x: initial + offset, y: 3.0 + itemFrame.minY) + } + item.itemView.frame = itemFrame + item.itemView.selectionFrame = CGRect(origin: CGPoint(x: sideInset + regularItemWidth * CGFloat(i), y: 0.0), size: CGSize(width: itemWidth, height: item.size.height)) + } + + scrollContentWidth = availableSize.width - 3.0 * 2.0 + } else { + var contentWidth: CGFloat = sideInset + for item in items { + var itemFrame = CGRect(origin: CGPoint(x: contentWidth - 3.0, y: 0.0), size: item.size) + if item.tabId == self.reorderingItem, let (initial, offset) = self.reorderingItemPosition { + itemFrame.origin = CGPoint(x: initial + offset, y: 3.0 + itemFrame.minY) + } + item.itemView.frame = itemFrame + item.itemView.selectionFrame = itemFrame + item.itemView.selectionFrame.size.width += 3.0 + contentWidth += item.size.width + } + contentWidth += sideInset + scrollContentWidth = contentWidth + } + + for (tabId, itemView, _, itemTransition) in items { + let itemFrame = itemView.frame + + if let itemRegularView = itemView.regularView.view, let itemSelectedView = itemView.selectedView.view { + if itemRegularView.superview == nil { + self.scrollView.addSubview(itemRegularView) + self.selectedScrollView.addSubview(itemSelectedView) + + transition.animateAlpha(view: itemRegularView, from: 0.0, to: 1.0) + transition.animateScale(view: itemRegularView, from: 0.001, to: 1.0) + + transition.animateAlpha(view: itemSelectedView, from: 0.0, to: 1.0) + transition.animateScale(view: itemSelectedView, from: 0.001, to: 1.0) + } + itemTransition.setFrame(view: itemRegularView, frame: itemFrame) + itemTransition.setFrame(view: itemSelectedView, frame: itemFrame) + + if tabId == self.reorderingItem { + itemTransition.setSublayerTransform(view: itemRegularView, transform: CATransform3DMakeScale(1.2, 1.2, 1.0)) + itemTransition.setSublayerTransform(view: itemSelectedView, transform: CATransform3DMakeScale(1.2, 1.2, 1.0)) + itemTransition.setAlpha(view: itemRegularView, alpha: 0.9) + itemTransition.setAlpha(view: itemSelectedView, alpha: 0.0) + } else { + itemTransition.setSublayerTransform(view: itemRegularView, transform: CATransform3DIdentity) + itemTransition.setSublayerTransform(view: itemSelectedView, transform: CATransform3DIdentity) + itemTransition.setAlpha(view: itemRegularView, alpha: 1.0) + itemTransition.setAlpha(view: itemSelectedView, alpha: 1.0) + } + } + } + + var removedIds: [Tab.Id] = [] + for (id, itemView) in self.itemViews { + if !validIds.contains(id) { + removedIds.append(id) + if let itemRegularView = itemView.regularView.view, let itemSelectedView = itemView.selectedView.view { + transition.setScale(view: itemRegularView, scale: 0.001) + transition.setAlpha(view: itemRegularView, alpha: 0.0, completion: { [weak itemRegularView] _ in + itemRegularView?.removeFromSuperview() + }) + transition.setScale(view: itemSelectedView, scale: 0.001) + transition.setAlpha(view: itemSelectedView, alpha: 0.0, completion: { [weak itemSelectedView] _ in + itemSelectedView?.removeFromSuperview() + }) + } + } + } + for id in removedIds { + self.itemViews.removeValue(forKey: id) + } + + var selectedItemFrame: CGRect? + if let selectedTab = component.selectedTab { + for i in 0 ..< component.tabs.count { + if component.tabs[i].id == selectedTab { + if let itemView = self.itemViews[component.tabs[i].id] { + var selectedItemFrameValue = itemView.selectionFrame + if selectedTab == self.reorderingItem, let itemSuperview = itemView.regularView.view?.superview { + selectedItemFrameValue = itemSuperview.convert(itemView.selectionFrame, to: self.scrollView) + } + + var pendingItemFrame: CGRect? + if self.tabSwitchFraction != 0.0 { + if self.tabSwitchFraction > 0.0 && i != component.tabs.count - 1 { + if let nextItemView = self.itemViews[component.tabs[i + 1].id] { + pendingItemFrame = nextItemView.selectionFrame + } + } else if self.tabSwitchFraction < 0.0 && i != 0 { + if let previousItemView = self.itemViews[component.tabs[i - 1].id] { + pendingItemFrame = previousItemView.selectionFrame + } + } + } + if let pendingItemFrame { + let fraction = abs(self.tabSwitchFraction) + selectedItemFrameValue.origin.x = selectedItemFrameValue.minX * (1.0 - fraction) + pendingItemFrame.minX * fraction + selectedItemFrameValue.size.width = selectedItemFrameValue.width * (1.0 - fraction) + pendingItemFrame.width * fraction + } + + selectedItemFrame = selectedItemFrameValue + } + break + } + } + } + + let contentSize = CGSize(width: scrollContentWidth, height: sizeHeight - 3.0 * 2.0) + + let sizeWidth: CGFloat + switch component.layout { + case .fill: + sizeWidth = availableSize.width + case .fit: + sizeWidth = min(availableSize.width, scrollContentWidth + 3.0 * 2.0) + } + + let size = CGSize(width: sizeWidth, height: sizeHeight) + + self.layoutData = LayoutData( + size: size, + selectedItemFrame: selectedItemFrame ?? CGRect() + ) + + self.ignoreScrolling = true + let scrollViewFrame = CGRect(origin: CGPoint(x: 3.0, y: 0.0), size: CGSize(width: size.width - 3.0 * 2.0, height: size.height - 3.0 * 2.0)) + transition.setPosition(view: self.scrollView, position: scrollViewFrame.center) + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + + var scrollViewBounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollViewFrame.size) + if shouldFocusOnSelectedTab || self.scrollView.bounds.size != scrollViewBounds.size { + if shouldFocusOnSelectedTab, let selectedItemFrame { + let scrollLookahead: CGFloat = 100.0 + + if scrollViewBounds.minX + scrollViewBounds.width - scrollLookahead < selectedItemFrame.maxX { + scrollViewBounds.origin.x = selectedItemFrame.maxX - scrollViewBounds.width + scrollLookahead + } + if scrollViewBounds.minX > selectedItemFrame.minX - scrollLookahead { + scrollViewBounds.origin.x = selectedItemFrame.minX - scrollLookahead + } + if scrollViewBounds.origin.x + scrollViewBounds.width > contentSize.width { + scrollViewBounds.origin.x = contentSize.width - scrollViewBounds.width + } + if scrollViewBounds.origin.x < 0.0 { + scrollViewBounds.origin.x = 0.0 + } + } + transition.setBounds(view: self.scrollView, bounds: scrollViewBounds) + } + + self.scrollView.layer.cornerRadius = (size.height - 3.0 * 2.0) * 0.5 + self.selectedScrollView.layer.cornerRadius = (size.height - 3.0 * 2.0) * 0.5 + + transition.setFrame(view: self.lensView, frame: CGRect(origin: CGPoint(x: 3.0, y: 3.0), size: CGSize(width: size.width - 3.0 * 2.0, height: size.height - 3.0 * 2.0))) + self.lensView.clipsToBounds = true + self.lensView.layer.cornerRadius = (size.height - 3.0 * 2.0) * 0.5 + self.ignoreScrolling = false + + self.updateScrolling(transition: transition) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ItemComponent: Component { + struct Editing: Equatable { + var isEditable: Bool + + init(isEditable: Bool) { + self.isEditable = isEditable + } + } + + let context: AccountContext? + let theme: PresentationTheme + let tab: HorizontalTabsComponent.Tab + let isSelected: Bool + let editing: Editing? + + init(context: AccountContext?, theme: PresentationTheme, tab: HorizontalTabsComponent.Tab, isSelected: Bool, editing: Editing?) { + self.context = context + self.theme = theme + self.tab = tab + self.isSelected = isSelected + self.editing = editing + } + + static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.tab != rhs.tab { + return false + } + if lhs.isSelected != rhs.isSelected { + return false + } + if lhs.editing != rhs.editing { + return false + } + return true + } + + final class View: UIView { + let extractedContainerView: ContextExtractedContentContainingView + let containerView: ContextControllerSourceView + + var titleContent: ComponentView? + var customContent: ComponentView? + var badge: ComponentView? + var deleteIcon: (button: HighlightTrackingButton, icon: UIImageView)? + + var tapRecognizer: UITapGestureRecognizer? + + var component: ItemComponent? + + override init(frame: CGRect) { + self.extractedContainerView = ContextExtractedContentContainingView() + self.containerView = ContextControllerSourceView() + + super.init(frame: frame) + + //self.extractedContainerView.contentView.addSubview(self.extractedBackgroundNode) + + self.containerView.addSubview(self.extractedContainerView) + self.containerView.targetViewForActivationProgress = self.extractedContainerView.contentView + self.addSubview(self.containerView) + + self.containerView.activated = { [weak self] gesture, _ in + guard let self, let component = self.component else { + return + } + component.tab.contextAction?(self.extractedContainerView, gesture) + } + + self.extractedContainerView.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in + guard let self, let component else { + return + } + let _ = component + + /*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 + } + })*/ + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateIsShaking(animated: Bool) { + guard let component = self.component else { + return + } + + if component.editing != nil { + if self.layer.animation(forKey: "shaking_position") == nil { + let degreesToRadians: (_ x: CGFloat) -> CGFloat = { x in + 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") + } + } else if self.layer.animation(forKey: "shaking_position") != nil { + if let presentationLayer = self.layer.presentation() { + let transition: ComponentTransition = .easeInOut(duration: 0.1) + if presentationLayer.position != self.layer.position { + transition.animatePosition(layer: self.layer, from: CGPoint(x: presentationLayer.position.x - self.layer.position.x, y: presentationLayer.position.y - self.layer.position.y), to: CGPoint(), additive: true) + } + if !CATransform3DIsIdentity(presentationLayer.transform) { + transition.setTransform(layer: self.layer, transform: CATransform3DIdentity) + } + } + + self.layer.removeAnimation(forKey: "shaking_position") + self.layer.removeAnimation(forKey: "shaking_rotation") + } + } + + @objc private func deleteButtonPressed() { + self.component?.tab.deleteAction?() + } + + func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + self.containerView.isGestureEnabled = component.editing == nil + self.tapRecognizer?.isEnabled = component.editing == nil + + let sideInset: CGFloat = 16.0 + let badgeSpacing: CGFloat = 5.0 + + var size = CGSize(width: sideInset, height: availableSize.height) + + var titleContentSize: CGSize? + if case let .title(title) = component.tab.content { + let titleContent: ComponentView + if let current = self.titleContent { + titleContent = current + } else { + titleContent = ComponentView() + self.titleContent = titleContent + } + + let font = Font.medium(15.0) + + let rawAttributedString = ChatTextInputStateText(text: title.text, attributes: title.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in + if case let .CustomEmoji(_, fileId) = entity.type { + return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId, enableAnimation: title.enableAnimations), range: entity.range) + } + return nil + }).attributedText() + + let titleString = NSMutableAttributedString(attributedString: rawAttributedString) + titleString.addAttributes([ + .font: font, + .foregroundColor: component.theme.chat.inputPanel.panelControlColor + ], range: NSRange(location: 0, length: titleString.length)) + + titleContentSize = titleContent.update( + transition: .immediate, + component: AnyComponent(MultilineTextWithEntitiesComponent( + context: component.context, + animationCache: component.context?.animationCache, + animationRenderer: component.context?.animationRenderer, + placeholderColor: component.theme.chat.inputPanel.panelControlColor.withMultipliedAlpha(0.1), + text: .plain(titleString), + displaysAsynchronously: false + )), + environment: {}, + containerSize: CGSize(width: 300.0, height: 100.0) + ) + } else if let titleContent = self.titleContent { + self.titleContent = nil + titleContent.view?.removeFromSuperview() + } + + var customContentSize: CGSize? + if case let .custom(custom) = component.tab.content { + let customContent: ComponentView + if let current = self.customContent { + customContent = current + } else { + customContent = ComponentView() + self.customContent = customContent + } + + customContentSize = customContent.update( + transition: transition, + component: custom, + environment: {}, + containerSize: CGSize(width: 300.0, height: 100.0) + ) + } else if let customContent = self.customContent { + self.customContent = nil + customContent.view?.removeFromSuperview() + } + + if let titleContentSize { + size.width += titleContentSize.width + } + if let customContentSize { + size.width += customContentSize.width + } + + if let badgeData = component.tab.badge, component.tab.deleteAction == nil { + let badge: ComponentView + var badgeTransition = transition + if let current = self.badge { + badge = current + } else { + badgeTransition = badgeTransition.withAnimation(.none) + badge = ComponentView() + self.badge = badge + } + let badgeSize = badge.update( + transition: badgeTransition, + component: AnyComponent(TextBadgeComponent( + text: badgeData.title, + font: Font.medium(12.0), + background: badgeData.isAccent ? component.theme.list.itemCheckColors.fillColor : component.theme.chatList.unreadBadgeInactiveBackgroundColor, + foreground: component.theme.list.itemCheckColors.foregroundColor, + insets: UIEdgeInsets(top: 1.0, left: 5.0, bottom: 2.0, right: 5.0) + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + size.width += badgeSpacing + let badgeFrame = CGRect(origin: CGPoint(x: size.width, y: floorToScreenPixels((size.height - badgeSize.height) * 0.5)), size: badgeSize) + if let badgeView = badge.view { + if badgeView.superview == nil { + self.extractedContainerView.contentView.addSubview(badgeView) + transition.animateAlpha(view: badgeView, from: 0.0, to: 1.0) + transition.animateScale(view: badgeView, from: 0.001, to: 1.0) + } + badgeTransition.setFrame(view: badgeView, frame: badgeFrame) + } + size.width += badgeSize.width - 2.0 + } else if let badge = self.badge { + self.badge = nil + if let badgeView = badge.view { + transition.setFrame(view: badgeView, frame: badgeView.bounds.size.centered(around: CGPoint(x: size.width + sideInset - badgeView.bounds.width * 0.5, y: size.height * 0.5))) + transition.setScale(view: badgeView, scale: 0.001) + transition.setAlpha(view: badgeView, alpha: 0.0, completion: { [weak badgeView] _ in + badgeView?.removeFromSuperview() + }) + } + } + + if component.tab.deleteAction != nil { + let deleteIcon: (button: HighlightTrackingButton, icon: UIImageView) + if let current = self.deleteIcon { + deleteIcon = current + } else { + deleteIcon = (HighlightTrackingButton(), UIImageView()) + self.deleteIcon = deleteIcon + deleteIcon.button.addSubview(deleteIcon.icon) + deleteIcon.icon.image = generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(UIColor.white.cgColor) + context.setLineWidth(1.33) + context.setLineCap(.round) + context.move(to: CGPoint(x: 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) + deleteIcon.button.addTarget(self, action: #selector(self.deleteButtonPressed), for: .touchUpInside) + } + deleteIcon.icon.tintColor = component.theme.chat.inputPanel.panelControlColor + if let image = deleteIcon.icon.image { + let deleteButtonFrame = CGRect(origin: CGPoint(x: size.width + 2.0, y: 0.0), size: CGSize(width: image.size.width + 6.0 * 2.0, height: size.height)) + let deleteIconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((deleteButtonFrame.width - image.size.width) * 0.5), y: floorToScreenPixels((deleteButtonFrame.height - image.size.height) * 0.5)), size: image.size) + if deleteIcon.button.superview == nil { + self.addSubview(deleteIcon.button) + deleteIcon.button.frame = deleteButtonFrame + deleteIcon.icon.frame = deleteIconFrame + transition.animateAlpha(view: deleteIcon.button, from: 0.0, to: 1.0) + transition.animateScale(view: deleteIcon.button, from: 0.001, to: 1.0) + } + transition.setFrame(view: deleteIcon.button, frame: deleteButtonFrame) + transition.setFrame(view: deleteIcon.icon, frame: deleteIconFrame) + size.width += deleteButtonFrame.width - 3.0 + } + } else if let deleteIcon = self.deleteIcon { + self.deleteIcon = nil + let (button, _) = deleteIcon + transition.setScale(view: button, scale: 0.001) + transition.setAlpha(view: button, alpha: 0.0, completion: { [weak button] _ in + button?.removeFromSuperview() + }) + } + + size.width += sideInset + + if let titleView = self.titleContent?.view, let titleContentSize { + let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((size.height - titleContentSize.height) * 0.5)), size: titleContentSize) + if titleView.superview == nil { + titleView.layer.anchorPoint = CGPoint() + self.extractedContainerView.contentView.addSubview(titleView) + } + transition.setPosition(view: titleView, position: titleFrame.origin) + titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) + } + + if let customView = self.customContent?.view, let customContentSize { + let customFrame = CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((size.height - customContentSize.height) * 0.5)), size: customContentSize) + if customView.superview == nil { + customView.layer.anchorPoint = CGPoint() + self.extractedContainerView.contentView.addSubview(customView) + } + transition.setFrame(view: customView, frame: customFrame) + } + + transition.setFrame(view: self.extractedContainerView, frame: CGRect(origin: CGPoint(), size: size)) + transition.setFrame(view: self.extractedContainerView.contentView, frame: CGRect(origin: CGPoint(), size: size)) + + let extractedBackgroundFrame = CGRect(origin: CGPoint(), size: size) + + self.extractedContainerView.contentRect = CGRect(origin: CGPoint(x: extractedBackgroundFrame.minX, y: 0.0), size: CGSize(width: extractedBackgroundFrame.width, height: size.height)) + transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: size)) + + self.updateIsShaking(animated: !transition.animation.isImmediate) + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/ItemListDatePickerItem/Sources/ItemListDatePickerItem.swift b/submodules/TelegramUI/Components/ItemListDatePickerItem/Sources/ItemListDatePickerItem.swift index b5e0920f..db1bb623 100644 --- a/submodules/TelegramUI/Components/ItemListDatePickerItem/Sources/ItemListDatePickerItem.swift +++ b/submodules/TelegramUI/Components/ItemListDatePickerItem/Sources/ItemListDatePickerItem.swift @@ -131,7 +131,7 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode { self.containerNode = ASDisplayNode() self.containerNode.clipsToBounds = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.containerNode) diff --git a/submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent/BUILD new file mode 100644 index 00000000..abdc05b2 --- /dev/null +++ b/submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "LegacyChatHeaderPanelComponent", + module_name = "LegacyChatHeaderPanelComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/ChatPresentationInterfaceState", + "//submodules/AccountContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent/Sources/LegacyChatHeaderPanelComponent.swift b/submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent/Sources/LegacyChatHeaderPanelComponent.swift new file mode 100644 index 00000000..941ae252 --- /dev/null +++ b/submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent/Sources/LegacyChatHeaderPanelComponent.swift @@ -0,0 +1,90 @@ +import Foundation +import UIKit +import Display +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import ChatPresentationInterfaceState +import AsyncDisplayKit +import AccountContext + +open class ChatTitleAccessoryPanelNode: ASDisplayNode { + public typealias LayoutResult = ChatControllerCustomNavigationPanelNodeLayoutResult + + open var interfaceInteraction: ChatPanelInterfaceInteraction? + + open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult { + preconditionFailure() + } +} + +public final class LegacyChatHeaderPanelComponent: Component { + public let panelNode: ChatTitleAccessoryPanelNode + public let interfaceState: ChatPresentationInterfaceState + + public init( + panelNode: ChatTitleAccessoryPanelNode, + interfaceState: ChatPresentationInterfaceState + ) { + self.panelNode = panelNode + self.interfaceState = interfaceState + } + + public static func ==(lhs: LegacyChatHeaderPanelComponent, rhs: LegacyChatHeaderPanelComponent) -> Bool { + if lhs.panelNode !== rhs.panelNode { + return false + } + if lhs.interfaceState != rhs.interfaceState { + return false + } + return true + } + + public final class View: UIView { + private var component: LegacyChatHeaderPanelComponent? + private weak var state: EmptyComponentState? + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func update(component: LegacyChatHeaderPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let previousComponent = self.component + self.component = component + self.state = state + + if previousComponent?.panelNode !== component.panelNode { + previousComponent?.panelNode.view.removeFromSuperview() + self.addSubview(component.panelNode.view) + } + + let result = component.panelNode.updateLayout( + width: availableSize.width, + leftInset: 0.0, + rightInset: 0.0, + transition: transition.containedViewLayoutTransition, + interfaceState: component.interfaceState + ) + let size = CGSize(width: availableSize.width, height: result.backgroundHeight) + let panelFrame = CGRect(origin: CGPoint(), size: size) + transition.setFrame(view: component.panelNode.view, frame: panelFrame) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/LiquidLens/Sources/LiquidLensView.swift b/submodules/TelegramUI/Components/LiquidLens/Sources/LiquidLensView.swift index e5d1e849..6931fbd1 100644 --- a/submodules/TelegramUI/Components/LiquidLens/Sources/LiquidLensView.swift +++ b/submodules/TelegramUI/Components/LiquidLens/Sources/LiquidLensView.swift @@ -58,36 +58,54 @@ private final class RestingBackgroundView: UIVisualEffectView { } public final class LiquidLensView: UIView { + public enum Kind { + case externalContainer + case builtinContainer + case noContainer + } + private struct Params: Equatable { var size: CGSize - var selectionX: CGFloat - var selectionWidth: CGFloat + var cornerRadius: CGFloat? + var selectionOrigin: CGPoint + var selectionSize: CGSize + var inset: CGFloat + var liftedInset: CGFloat var isDark: Bool var isLifted: Bool + var isCollapsed: Bool - init(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool) { + init(size: CGSize, cornerRadius: CGFloat?, selectionOrigin: CGPoint, selectionSize: CGSize, inset: CGFloat, liftedInset: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool) { self.size = size - self.selectionX = selectionX - self.selectionWidth = selectionWidth + self.cornerRadius = cornerRadius + self.selectionOrigin = selectionOrigin + self.selectionSize = selectionSize + self.inset = inset + self.liftedInset = liftedInset self.isLifted = isLifted self.isDark = isDark + self.isCollapsed = isCollapsed } } private struct LensParams: Equatable { var baseFrame: CGRect + var inset: CGFloat + var liftedInset: CGFloat var isLifted: Bool - init(baseFrame: CGRect, isLifted: Bool) { + init(baseFrame: CGRect, inset: CGFloat, liftedInset: CGFloat, isLifted: Bool) { self.baseFrame = baseFrame + self.inset = inset + self.liftedInset = liftedInset self.isLifted = isLifted } } private let containerView: UIView - private let backgroundContainerContainer: UIView - private let backgroundContainer: GlassBackgroundContainerView - private let backgroundView: GlassBackgroundView + private let backgroundContainer: GlassBackgroundContainerView? + private let genericBackgroundContainer: UIView? + private let backgroundView: GlassBackgroundView? private var lensView: UIView? private let liftedContainerView: UIView public let contentView: UIView @@ -109,34 +127,64 @@ public final class LiquidLensView: UIView { private var liftedDisplayLink: SharedDisplayLinkDriver.Link? - public var selectionX: CGFloat? { - return self.params?.selectionX + public var selectionOrigin: CGPoint? { + return self.params?.selectionOrigin } - public var selectionWidth: CGFloat? { - return self.params?.selectionWidth + public var selectionSize: CGSize? { + return self.params?.selectionSize } + + public private(set) var isAnimating: Bool = false { + didSet { + if self.isAnimating != oldValue { + self.onUpdatedIsAnimating?(self.isAnimating) + } + } + } + public var onUpdatedIsAnimating: ((Bool) -> Void)? + public var isLiftedAnimationCompleted: (() -> Void)? - override public init(frame: CGRect) { + public init(kind: Kind) { self.containerView = UIView() - self.backgroundContainerContainer = UIView() - self.backgroundContainer = GlassBackgroundContainerView() + switch kind { + case .builtinContainer: + self.backgroundContainer = GlassBackgroundContainerView() + self.genericBackgroundContainer = nil + case .externalContainer, .noContainer: + self.backgroundContainer = nil + self.genericBackgroundContainer = UIView() + } - self.backgroundView = GlassBackgroundView() + if case .noContainer = kind { + self.backgroundView = nil + } else { + self.backgroundView = GlassBackgroundView() + } self.contentView = UIView() self.liftedContainerView = UIView() self.restingBackgroundView = RestingBackgroundView() - super.init(frame: frame) + super.init(frame: CGRect()) - self.backgroundContainerContainer.addSubview(self.backgroundContainer) - self.addSubview(self.backgroundContainerContainer) - - self.backgroundContainer.contentView.addSubview(self.backgroundView) - self.backgroundView.contentView.addSubview(self.containerView) + if let backgroundContainer = self.backgroundContainer { + self.addSubview(backgroundContainer) + if let backgroundView = self.backgroundView { + backgroundContainer.contentView.addSubview(backgroundView) + backgroundView.contentView.addSubview(self.containerView) + } + } else if let genericBackgroundContainer = self.genericBackgroundContainer { + self.addSubview(genericBackgroundContainer) + if let backgroundView = self.backgroundView { + genericBackgroundContainer.addSubview(backgroundView) + backgroundView.contentView.addSubview(self.containerView) + } else { + genericBackgroundContainer.addSubview(self.containerView) + } + } self.containerView.isUserInteractionEnabled = false if #available(iOS 26.0, *) { @@ -150,7 +198,11 @@ public final class LiquidLensView: UIView { } if let lensView = self.lensView { - self.backgroundContainer.layer.zPosition = 1 + if let backgroundContainer = self.backgroundContainer { + backgroundContainer.layer.zPosition = 1 + } else if let genericBackgroundContainer = self.genericBackgroundContainer{ + genericBackgroundContainer.layer.zPosition = 1 + } lensView.layer.zPosition = 10.0 self.liftedContainerView.addSubview(self.restingBackgroundView) @@ -159,7 +211,11 @@ public final class LiquidLensView: UIView { self.containerView.addSubview(lensView) self.containerView.addSubview(self.contentView) - lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: self.backgroundContainer.contentView) + if let backgroundContainer = self.backgroundContainer { + lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: backgroundContainer.contentView) + } else if let genericBackgroundContainer = self.genericBackgroundContainer { + lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: genericBackgroundContainer) + } lensView.perform(NSSelectorFromString("setLiftedContentView:"), with: self.liftedContainerView) lensView.perform(NSSelectorFromString("setOverridePunchoutView:"), with: self.contentView) @@ -194,7 +250,11 @@ public final class LiquidLensView: UIView { } else { let legacySelectionView = GlassBackgroundView.ContentImageView() self.legacySelectionView = legacySelectionView - self.backgroundView.contentView.insertSubview(legacySelectionView, at: 0) + if let backgroundView = self.backgroundView { + backgroundView.contentView.insertSubview(legacySelectionView, at: 0) + } else { + self.containerView.insertSubview(legacySelectionView, at: 0) + } let legacyContentMaskView = UIView() legacyContentMaskView.backgroundColor = .white @@ -222,9 +282,16 @@ public final class LiquidLensView: UIView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + public func setLiftedContainer(view: UIView) { + guard let lensView = self.lensView else { + return + } + lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: view) + } - public func update(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool, transition: ComponentTransition) { - let params = Params(size: size, selectionX: selectionX, selectionWidth: selectionWidth, isDark: isDark, isLifted: isLifted) + public func update(size: CGSize, cornerRadius: CGFloat? = nil, selectionOrigin: CGPoint, selectionSize: CGSize, inset: CGFloat, liftedInset: CGFloat = 4.0, isDark: Bool, isLifted: Bool, isCollapsed: Bool = false, transition: ComponentTransition) { + let params = Params(size: size, cornerRadius: cornerRadius, selectionOrigin: selectionOrigin, selectionSize: selectionSize, inset: inset, liftedInset: liftedInset, isDark: isDark, isLifted: isLifted, isCollapsed: isCollapsed) if self.params == params { return } @@ -238,7 +305,7 @@ public final class LiquidLensView: UIView { self.update(params: params, transition: transition) } - private func updateLens(params: LensParams, animated: Bool) { + private func updateLens(params: LensParams, transition: ComponentTransition) { guard let lensView = self.lensView else { return } @@ -249,22 +316,23 @@ public final class LiquidLensView: UIView { } self.isApplyingLensParams = true let previousParams = self.appliedLensParams - - let transition: ComponentTransition = animated ? .easeInOut(duration: 0.3) : .immediate + self.appliedLensParams = params if previousParams?.isLifted != params.isLifted { + self.isAnimating = true + let selector = NSSelectorFromString("setLifted:animated:alongsideAnimations:completion:") var shouldScheduleUpdate = false var didProcessUpdate = false self.pendingLensParams = params if let lensView = self.lensView, let method = lensView.method(for: selector) { - typealias ObjCMethod = @convention(c) (AnyObject, Selector, Bool, Bool, @escaping () -> Void, AnyObject?) -> Void + typealias ObjCMethod = @convention(c) (AnyObject, Selector, Bool, Bool, @escaping () -> Void, (() -> Void)?) -> Void let function = unsafeBitCast(method, to: ObjCMethod.self) function(lensView, selector, params.isLifted, !transition.animation.isImmediate, { [weak self] in guard let self else { return } - let liftedInset: CGFloat = params.isLifted ? 4.0 : -4.0 + let liftedInset: CGFloat = params.isLifted ? params.liftedInset : (-params.inset) lensView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: params.baseFrame.width + liftedInset * 2.0, height: params.baseFrame.height + liftedInset * 2.0)) didProcessUpdate = true if shouldScheduleUpdate { @@ -274,10 +342,18 @@ public final class LiquidLensView: UIView { } self.isApplyingLensParams = false self.pendingLensParams = nil - self.updateLens(params: pendingLensParams, animated: !transition.animation.isImmediate) + self.updateLens(params: pendingLensParams, transition: transition) } } - }, nil) + }, { [weak self] in + guard let self else { + return + } + if !self.isApplyingLensParams { + self.isAnimating = false + } + self.isLiftedAnimationCompleted?() + }) } if didProcessUpdate { transition.animateView { @@ -289,11 +365,32 @@ public final class LiquidLensView: UIView { shouldScheduleUpdate = true } } else { + let liftedInset: CGFloat = params.isLifted ? params.liftedInset : (-params.inset) + let lensBounds = CGRect(origin: CGPoint(), size: CGSize(width: params.baseFrame.width + liftedInset * 2.0, height: params.baseFrame.height + liftedInset * 2.0)) + let lensCenter = CGPoint(x: params.baseFrame.midX, y: params.baseFrame.midY) + + let previousBounds: CGRect = lensView.bounds transition.animateView { - let liftedInset: CGFloat = params.isLifted ? 4.0 : -4.0 - lensView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: params.baseFrame.width + liftedInset * 2.0, height: params.baseFrame.height + liftedInset * 2.0)) - lensView.center = CGPoint(x: params.baseFrame.midX, y: params.baseFrame.midY) + lensView.bounds = lensBounds } + + lensView.layer.removeAllAnimations() + lensView.bounds = lensBounds + + if !transition.animation.isImmediate { + self.isAnimating = true + } + transition.setPosition(view: lensView, position: lensCenter, completion: { [weak self] flag in + guard let self, flag else { + return + } + if !self.isApplyingLensParams { + self.isAnimating = false + } + }) + // No idea why + transition.animatePosition(layer: lensView.layer, from: CGPoint(x: (lensBounds.width - previousBounds.width) * 0.5, y: 0.0), to: CGPoint(), additive: true) + self.isApplyingLensParams = false } } @@ -319,25 +416,48 @@ public final class LiquidLensView: UIView { self.params = params transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: params.size)) - transition.setFrame(view: self.backgroundContainerContainer, frame: CGRect(origin: CGPoint(), size: params.size)) - transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: params.size)) - self.backgroundContainer.update(size: params.size, isDark: params.isDark, transition: transition) + if let backgroundContainer = self.backgroundContainer { + transition.setFrame(view: backgroundContainer, frame: CGRect(origin: CGPoint(), size: params.size)) + backgroundContainer.update(size: params.size, isDark: params.isDark, transition: transition) + } else if let genericBackgroundContainer = self.genericBackgroundContainer { + transition.setFrame(view: genericBackgroundContainer, frame: CGRect(origin: CGPoint(), size: params.size)) + } - transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: params.size)) - self.backgroundView.update(size: params.size, cornerRadius: params.size.height * 0.5, isDark: params.isDark, tintColor: GlassBackgroundView.TintColor.init(kind: .panel, color: UIColor(white: params.isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + if let backgroundView = self.backgroundView { + transition.setFrame(view: backgroundView, frame: CGRect(origin: CGPoint(), size: params.size)) + backgroundView.update(size: params.size, cornerRadius: params.cornerRadius ?? (params.size.height * 0.5), isDark: params.isDark, tintColor: GlassBackgroundView.TintColor.init(kind: .panel, color: UIColor(white: params.isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + } - transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: params.size)) - transition.setFrame(view: self.liftedContainerView, frame: CGRect(origin: CGPoint(), size: params.size)) + if self.contentView.bounds.size != params.size { + self.contentView.clipsToBounds = true + transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: params.size), completion: { [weak self] completed in + guard let self, completed else { + return + } + self.contentView.clipsToBounds = false + }) + transition.setCornerRadius(layer: self.contentView.layer, cornerRadius: params.cornerRadius ?? (params.size.height * 0.5)) - let baseLensFrame = CGRect(origin: CGPoint(x: max(0.0, min(params.selectionX, params.size.width - params.selectionWidth)), y: 0.0), size: CGSize(width: params.selectionWidth, height: params.size.height)) - self.updateLens(params: LensParams(baseFrame: baseLensFrame, isLifted: params.isLifted), animated: !transition.animation.isImmediate) + self.liftedContainerView.clipsToBounds = true + transition.setFrame(view: self.liftedContainerView, frame: CGRect(origin: CGPoint(), size: params.size), completion: { [weak self] completed in + guard let self, completed else { + return + } + self.liftedContainerView.clipsToBounds = false + }) + transition.setCornerRadius(layer: self.liftedContainerView.layer, cornerRadius: params.cornerRadius ?? (params.size.height * 0.5)) + } + + + let baseLensFrame = CGRect(origin: params.selectionOrigin, size: params.selectionSize) + self.updateLens(params: LensParams(baseFrame: baseLensFrame, inset: params.inset, liftedInset: params.liftedInset, isLifted: params.isLifted), transition: transition) if let legacyContentMaskView = self.legacyContentMaskView { transition.setFrame(view: legacyContentMaskView, frame: CGRect(origin: CGPoint(), size: params.size)) } if let legacyContentMaskBlobView = self.legacyContentMaskBlobView, let legacyLiftedContentBlobMaskView = self.legacyLiftedContentBlobMaskView, let legacySelectionView = self.legacySelectionView { - let lensFrame = baseLensFrame.insetBy(dx: 4.0, dy: 4.0) + let lensFrame = baseLensFrame.insetBy(dx: params.inset, dy: params.inset) let effectiveLensFrame = lensFrame.insetBy(dx: params.isLifted ? -2.0 : 0.0, dy: params.isLifted ? -2.0 : 0.0) if legacyContentMaskBlobView.image?.size.height != lensFrame.height { @@ -354,7 +474,7 @@ public final class LiquidLensView: UIView { transition.setFrame(view: self.restingBackgroundView, frame: CGRect(origin: CGPoint(), size: params.size)) self.restingBackgroundView.update(isDark: params.isDark) - transition.setAlpha(view: self.restingBackgroundView, alpha: params.isLifted ? 0.0 : 1.0) + transition.setAlpha(view: self.restingBackgroundView, alpha: (params.isLifted || params.isCollapsed) ? 0.0 : 1.0) if params.isLifted { if self.liftedDisplayLink == nil { diff --git a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/BUILD new file mode 100644 index 00000000..fa7ab63c --- /dev/null +++ b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/BUILD @@ -0,0 +1,34 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "LiveLocationHeaderPanelComponent", + module_name = "LiveLocationHeaderPanelComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUIPreferences", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/GlobalControlPanelsContext", + "//submodules/PresentationDataUtils", + "//submodules/TextFormat", + "//submodules/Markdown", + "//submodules/LocalizedPeerData", + "//submodules/LiveLocationTimerNode", + "//submodules/AvatarNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LiveLocationHeaderPanelComponent.swift b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LiveLocationHeaderPanelComponent.swift new file mode 100644 index 00000000..9ec0dfbf --- /dev/null +++ b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LiveLocationHeaderPanelComponent.swift @@ -0,0 +1,286 @@ +import Foundation +import UIKit +import Display +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import AccountContext +import TelegramCore +import GlobalControlPanelsContext +import SwiftSignalKit +import Postbox +import PresentationDataUtils + +private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) { + let presentImpl: (EngineMessage?) -> Void = { [weak controller] message in + if let message = message, let strongController = controller { + let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message._asMessage(), standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: { + controller?.view.endEditing(true) + }, present: { c, a, _ in + controller?.present(c, in: .window(.root), with: a, blockInteraction: true) + }, transitionNode: { _, _, _ in + return nil + }, addToTransitionSurface: { _ in + }, openUrl: { _ in + }, openPeer: { peer, navigation in + }, callPeer: { _, _ in + }, openConferenceCall: { _ in + }, enqueueMessage: { message in + let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() + }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in + }, chatAvatarHiddenMedia: { _, _ in + })) + } + } + if let id = context.liveLocationManager?.internalMessageForPeerId(peerId) { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: id)) + |> deliverOnMainQueue).start(next: presentImpl) + } else if let liveLocationManager = context.liveLocationManager { + let _ = (liveLocationManager.summaryManager.peersBroadcastingTo(peerId: peerId) + |> take(1) + |> map { peersAndMessages -> EngineMessage? in + return peersAndMessages?.first?.1 + } |> deliverOnMainQueue).start(next: presentImpl) + } +} + +public final class LiveLocationHeaderPanelComponent: Component { + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + public let data: GlobalControlPanelsContext.LiveLocation + public let controller: () -> ViewController? + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + data: GlobalControlPanelsContext.LiveLocation, + controller: @escaping () -> ViewController? + ) { + self.context = context + self.theme = theme + self.strings = strings + self.data = data + self.controller = controller + } + + public static func ==(lhs: LiveLocationHeaderPanelComponent, rhs: LiveLocationHeaderPanelComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.data != rhs.data { + return false + } + return true + } + + public final class View: UIView { + private var panel: LocationBroadcastNavigationAccessoryPanel? + + private var component: LiveLocationHeaderPanelComponent? + private weak var state: EmptyComponentState? + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func update(component: LiveLocationHeaderPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let themeUpdated = self.component?.theme !== component.theme + + self.component = component + self.state = state + + let panel: LocationBroadcastNavigationAccessoryPanel + if let current = self.panel { + panel = current + } else { + panel = LocationBroadcastNavigationAccessoryPanel( + accountPeerId: component.context.account.peerId, + theme: component.theme, + strings: component.strings, + nameDisplayOrder: component.context.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder, + tapAction: { [weak self] in + guard let self, let component = self.component, let controller = component.controller() else { + return + } + switch component.data.mode { + case .all: + let messages = component.data.messages.values.sorted(by: { $0.index > $1.index }) + + if messages.count == 1 { + presentLiveLocationController(context: component.context, peerId: messages[0].id.peerId, controller: controller) + } else { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(presentationData: presentationData) + let dismissAction: () -> Void = { [weak actionSheet] in + actionSheet?.dismissAnimated() + } + var items: [ActionSheetItem] = [] + if !messages.isEmpty { + items.append(ActionSheetTextItem(title: presentationData.strings.LiveLocation_MenuChatsCount(Int32(messages.count)))) + for message in messages { + if let peer = message.peers[message.id.peerId] { + var beginTimeAndTimeout: (Double, Double)? + for media in message.media { + if let media = media as? TelegramMediaMap, let timeout = media.liveBroadcastingTimeout { + beginTimeAndTimeout = (Double(message.timestamp), Double(timeout)) + } + } + + if let beginTimeAndTimeout { + items.append(LocationBroadcastActionSheetItem(context: component.context, peer: peer, title: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), beginTimestamp: beginTimeAndTimeout.0, timeout: beginTimeAndTimeout.1, strings: presentationData.strings, action: { [weak self] in + dismissAction() + + guard let self, let component = self.component, let controller = component.controller() else { + return + } + presentLiveLocationController(context: component.context, peerId: peer.id, controller: controller) + })) + } + } + } + items.append(ActionSheetButtonItem(title: presentationData.strings.LiveLocation_MenuStopAll, color: .destructive, action: { [weak self] in + dismissAction() + + guard let self, let component = self.component else { + return + } + for peer in component.data.peers { + component.context.liveLocationManager?.cancelLiveLocation(peerId: peer.id) + } + })) + } + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + self.window?.endEditing(true) + controller.present(actionSheet, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } + case let .peer(peerId): + presentLiveLocationController(context: component.context, peerId: peerId, controller: controller) + } + }, + close: { [weak self] in + guard let self, let component = self.component, let controller = component.controller() else { + return + } + var closePeers: [EnginePeer]? + var closePeerId: EnginePeer.Id? + switch component.data.mode { + case .all: + if component.data.peers.count > 1 { + closePeers = component.data.peers + } else { + closePeerId = component.data.peers.first?.id + } + case let .peer(peerId): + closePeerId = peerId + } + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(presentationData: presentationData) + let dismissAction: () -> Void = { [weak actionSheet] in + actionSheet?.dismissAnimated() + } + var items: [ActionSheetItem] = [] + if let closePeers = closePeers, !closePeers.isEmpty { + items.append(ActionSheetTextItem(title: presentationData.strings.LiveLocation_MenuChatsCount(Int32(closePeers.count)))) + for peer in closePeers { + items.append(ActionSheetButtonItem(title: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), action: { [weak self] in + dismissAction() + + guard let self, let component = self.component, let controller = component.controller() else { + return + } + presentLiveLocationController(context: component.context, peerId: peer.id, controller: controller) + })) + } + items.append(ActionSheetButtonItem(title: presentationData.strings.LiveLocation_MenuStopAll, color: .destructive, action: { [weak self] in + dismissAction() + + guard let self, let component = self.component else { + return + } + for peer in closePeers { + component.context.liveLocationManager?.cancelLiveLocation(peerId: peer.id) + } + })) + } else if let closePeerId { + items.append(ActionSheetButtonItem(title: presentationData.strings.Map_StopLiveLocation, color: .destructive, action: { [weak self] in + dismissAction() + + guard let self, let component = self.component else { + return + } + component.context.liveLocationManager?.cancelLiveLocation(peerId: closePeerId) + })) + } + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + self.window?.endEditing(true) + controller.present(actionSheet, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } + ) + self.panel = panel + self.addSubview(panel.view) + } + + let size = CGSize(width: availableSize.width, height: 40.0) + let panelFrame = CGRect(origin: CGPoint(), size: size) + transition.setFrame(view: panel.view, frame: panelFrame) + panel.updateLayout(size: panelFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition) + + let mappedMode: LocationBroadcastNavigationAccessoryPanelMode + switch component.data.mode { + case .all: + mappedMode = .summary + case .peer: + mappedMode = .peer + } + panel.update(peers: component.data.peers, mode: mappedMode, canClose: component.data.canClose) + + if themeUpdated { + panel.updatePresentationData(PresentationData( + strings: component.strings, + theme: component.theme, + autoNightModeTriggered: false, + chatWallpaper: .builtin(WallpaperSettings()), + chatFontSize: .regular, + chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: true), + listsFontSize: .regular, + dateTimeFormat: PresentationDateTimeFormat(), + nameDisplayOrder: .firstLast, + nameSortOrder: .firstLast, + reduceMotion: false, + largeEmoji: false + )) + } + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastActionSheetItem.swift b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastActionSheetItem.swift new file mode 100644 index 00000000..30bdac85 --- /dev/null +++ b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastActionSheetItem.swift @@ -0,0 +1,139 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramCore +import Postbox +import TelegramPresentationData +import AccountContext +import LiveLocationTimerNode +import AvatarNode + +public class LocationBroadcastActionSheetItem: ActionSheetItem { + public let context: AccountContext + public let peer: Peer + public let title: String + public let beginTimestamp: Double + public let timeout: Double + public let strings: PresentationStrings + public let action: () -> Void + + public init(context: AccountContext, peer: Peer, title: String, beginTimestamp: Double, timeout: Double, strings: PresentationStrings, action: @escaping () -> Void) { + self.context = context + self.peer = peer + self.title = title + self.beginTimestamp = beginTimestamp + self.timeout = timeout + self.strings = strings + self.action = action + } + + public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + let node = LocationBroadcastActionSheetItemNode(theme: theme) + node.setItem(self) + return node + } + + public func updateNode(_ node: ActionSheetItemNode) { + guard let node = node as? LocationBroadcastActionSheetItemNode else { + assertionFailure() + return + } + + node.setItem(self) + node.requestLayoutUpdate() + } +} + +private let avatarFont = avatarPlaceholderFont(size: 15.0) + +public class LocationBroadcastActionSheetItemNode: ActionSheetItemNode { + private let theme: ActionSheetControllerTheme + + private let defaultFont: UIFont + + private var item: LocationBroadcastActionSheetItem? + + private let button: HighlightTrackingButton + private let avatarNode: AvatarNode + private let label: ImmediateTextNode + private let timerNode: ChatMessageLiveLocationTimerNode + + override public init(theme: ActionSheetControllerTheme) { + self.theme = theme + self.defaultFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0)) + + self.button = HighlightTrackingButton() + + self.avatarNode = AvatarNode(font: avatarFont) + self.avatarNode.isLayerBacked = !smartInvertColorsEnabled() + + self.label = ImmediateTextNode() + self.label.isUserInteractionEnabled = false + self.label.displaysAsynchronously = false + self.label.maximumNumberOfLines = 1 + + self.timerNode = ChatMessageLiveLocationTimerNode() + + super.init(theme: theme) + + self.view.addSubview(self.button) + self.addSubnode(self.avatarNode) + self.addSubnode(self.label) + self.addSubnode(self.timerNode) + + self.button.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor + } else { + UIView.animate(withDuration: 0.3, animations: { + strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor + }) + } + } + } + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + } + + func setItem(_ item: LocationBroadcastActionSheetItem) { + self.item = item + + let defaultFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0)) + + let textColor: UIColor = self.theme.primaryTextColor + self.label.attributedText = NSAttributedString(string: item.title, font: defaultFont, textColor: textColor) + + self.avatarNode.setPeer(context: item.context, theme: (item.context.sharedContext.currentPresentationData.with { $0 }).theme, peer: EnginePeer(item.peer)) + + self.timerNode.update(backgroundColor: self.theme.controlAccentColor.withAlphaComponent(0.4), foregroundColor: self.theme.controlAccentColor, textColor: self.theme.controlAccentColor, beginTimestamp: item.beginTimestamp, timeout: Int32(item.timeout) == liveLocationIndefinitePeriod ? -1.0 : item.timeout, strings: item.strings) + } + + public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let size = CGSize(width: constrainedSize.width, height: 57.0) + + self.button.frame = CGRect(origin: CGPoint(), size: size) + + let avatarInset: CGFloat = 42.0 + let avatarSize: CGFloat = 32.0 + + self.avatarNode.frame = CGRect(origin: CGPoint(x: 16.0, y: floor((size.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize)) + + let labelSize = self.label.updateLayout(CGSize(width: max(1.0, size.width - avatarInset - 16.0 - 16.0 - 30.0), height: size.height)) + self.label.frame = CGRect(origin: CGPoint(x: 16.0 + avatarInset, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) + + let timerSize = CGSize(width: 28.0, height: 28.0) + self.timerNode.frame = CGRect(origin: CGPoint(x: size.width - 16.0 - timerSize.width, y: floorToScreenPixels((size.height - timerSize.height) / 2.0)), size: timerSize) + + self.updateInternalLayout(size, constrainedSize: constrainedSize) + return size + } + + @objc func buttonPressed() { + if let item = self.item { + item.action() + } + } +} + diff --git a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastNavigationAccessoryPanel.swift b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastNavigationAccessoryPanel.swift new file mode 100644 index 00000000..c0f86a67 --- /dev/null +++ b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastNavigationAccessoryPanel.swift @@ -0,0 +1,212 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import TextFormat +import Markdown +import LocalizedPeerData +import LiveLocationTimerNode + +private let titleFont = Font.regular(12.0) +private let subtitleFont = Font.regular(10.0) + +enum LocationBroadcastNavigationAccessoryPanelMode { + case summary + case peer +} + +final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode { + private let accountPeerId: EnginePeer.Id + private var theme: PresentationTheme + private var strings: PresentationStrings + private var nameDisplayOrder: PresentationPersonNameOrder + + private let tapAction: () -> Void + private let close: () -> Void + + private let contentNode: ASDisplayNode + + private let iconNode: ASImageNode + private let wavesNode: LiveLocationWavesNode + private let titleNode: TextNode + private let subtitleNode: TextNode + private let closeButton: HighlightableButtonNode + + private var validLayout: (CGSize, CGFloat, CGFloat)? + private var peersAndMode: ([EnginePeer], LocationBroadcastNavigationAccessoryPanelMode, Bool)? + + init(accountPeerId: EnginePeer.Id, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, tapAction: @escaping () -> Void, close: @escaping () -> Void) { + self.accountPeerId = accountPeerId + self.theme = theme + self.strings = strings + self.nameDisplayOrder = nameDisplayOrder + + self.tapAction = tapAction + self.close = close + + self.contentNode = ASDisplayNode() + + self.iconNode = ASImageNode() + self.iconNode.isLayerBacked = true + self.iconNode.displayWithoutProcessing = true + self.iconNode.displaysAsynchronously = false + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/LiveLocationPanelIcon"), color: theme.chat.inputPanel.panelControlColor) + + self.wavesNode = LiveLocationWavesNode(color: self.theme.chat.inputPanel.panelControlColor) + + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + + self.subtitleNode = TextNode() + self.subtitleNode.isUserInteractionEnabled = false + + self.closeButton = HighlightableButtonNode() + self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(theme.chat.inputPanel.panelControlColor.cgColor) + context.setLineWidth(1.33) + context.setLineCap(.round) + context.move(to: CGPoint(x: 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0)) + context.strokePath() + }), for: []) + self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) + self.closeButton.displaysAsynchronously = false + + super.init() + + self.clipsToBounds = true + + self.addSubnode(self.contentNode) + + self.contentNode.addSubnode(self.iconNode) + self.contentNode.addSubnode(self.wavesNode) + self.contentNode.addSubnode(self.titleNode) + self.contentNode.addSubnode(self.subtitleNode) + self.contentNode.addSubnode(self.closeButton) + + self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside) + } + + override func didLoad() { + super.didLoad() + + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + self.view.addGestureRecognizer(tapRecognizer) + } + + func updatePresentationData(_ presentationData: PresentationData) { + self.theme = presentationData.theme + self.strings = presentationData.strings + + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/LiveLocationPanelIcon"), color: theme.chat.inputPanel.panelControlColor) + + self.wavesNode.color = self.theme.chat.inputPanel.panelControlColor + self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(theme.chat.inputPanel.panelControlColor.cgColor) + context.setLineWidth(1.33) + context.setLineCap(.round) + context.move(to: CGPoint(x: 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0)) + context.strokePath() + }), for: []) + } + + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, leftInset, rightInset) + + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) + transition.updateAlpha(node: self.contentNode, alpha: isHidden ? 0.0 : 1.0) + + let titleString = NSAttributedString(string: self.strings.Conversation_LiveLocation, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor) + var subtitleString: NSAttributedString? + if let (peers, mode, canClose) = self.peersAndMode { + switch mode { + case .summary: + let text: String + if peers.count == 1 { + text = self.strings.DialogList_LiveLocationSharingTo(peers[0].displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder)).string + } else { + text = self.strings.DialogList_LiveLocationChatsCount(Int32(peers.count)) + } + subtitleString = NSAttributedString(string: text, font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor) + case .peer: + self.closeButton.isHidden = !canClose + let filteredPeers = peers.filter { + $0.id != self.accountPeerId + } + if filteredPeers.count == 0 { + subtitleString = NSAttributedString(string: self.strings.Conversation_LiveLocationYou, font: subtitleFont, textColor: self.theme.chat.inputPanel.panelControlColor) + } else { + let otherString: String + if filteredPeers.count == 1 { + otherString = peers[0].compactDisplayTitle.replacingOccurrences(of: "*", with: "") + } else { + otherString = self.strings.Conversation_LiveLocationMembersCount(Int32(peers.count)) + } + let rawText: String + if filteredPeers.count != peers.count { + rawText = self.strings.Conversation_LiveLocationYouAndOther(otherString).string + } else { + rawText = otherString + } + let body = MarkdownAttributeSet(font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor) + let accent = MarkdownAttributeSet(font: subtitleFont, textColor: self.theme.chat.inputPanel.panelControlColor) + subtitleString = parseMarkdownIntoAttributedString(rawText, attributes: MarkdownAttributes(body: body, bold: accent, link: body, linkAttribute: { _ in nil })) + } + + } + } + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - 80.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - 80.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let _ = titleApply() + let _ = subtitleApply() + + let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0 + + let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 4.0 + minimizedTitleOffset), size: titleLayout.size) + let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 20.0), size: subtitleLayout.size) + + if let image = self.iconNode.image { + transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: 7.0 + leftInset, y: 9.0), size: image.size)) + transition.updateFrame(node: self.wavesNode, frame: CGRect(origin: CGPoint(x: -2.0 + leftInset, y: -3.0), size: CGSize(width: 48.0, height: 48.0))) + } + + transition.updateFrame(node: self.titleNode, frame: minimizedTitleFrame) + transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame) + + let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) + transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - rightInset, y: minimizedTitleFrame.minY + 10.0), size: closeButtonSize)) + } + + func update(peers: [EnginePeer], mode: LocationBroadcastNavigationAccessoryPanelMode, canClose: Bool) { + self.peersAndMode = (peers, mode, canClose) + if let (size, leftInset, rightInset) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate) + } + } + + @objc func closePressed() { + self.close() + } + + @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.tapAction() + } + } +} diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkScreen.swift index d3d015ac..eb682e3a 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkScreen.swift @@ -109,7 +109,7 @@ private final class SheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 1b649ed2..3291c6ca 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1259,7 +1259,6 @@ final class MediaEditorScreenComponent: Component { pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, diff --git a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/BUILD new file mode 100644 index 00000000..4fc1322a --- /dev/null +++ b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/BUILD @@ -0,0 +1,37 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "MediaPlaybackHeaderPanelComponent", + module_name = "MediaPlaybackHeaderPanelComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/TelegramCore", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUIPreferences", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/TelegramStringFormatting", + "//submodules/ManagedAnimationNode", + "//submodules/ContextUI", + "//submodules/TelegramNotices", + "//submodules/TooltipUI", + "//submodules/TelegramUI/Components/SliderContextItem", + "//submodules/TelegramUI/Components/GlobalControlPanelsContext", + "//submodules/UndoUI", + "//submodules/OverlayStatusController", + "//submodules/Postbox", + "//submodules/PresentationDataUtils", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryContainerNode.swift b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryContainerNode.swift new file mode 100644 index 00000000..087ff60a --- /dev/null +++ b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryContainerNode.swift @@ -0,0 +1,39 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramCore +import TelegramPresentationData +import AccountContext + +public final class MediaNavigationAccessoryContainerNode: ASDisplayNode, ASGestureRecognizerDelegate { + public let headerNode: MediaNavigationAccessoryHeaderNode + private var presentationData: PresentationData + + init(context: AccountContext, presentationData: PresentationData) { + self.presentationData = presentationData + + self.headerNode = MediaNavigationAccessoryHeaderNode(context: context, presentationData: presentationData) + + super.init() + + self.addSubnode(self.headerNode) + } + + func updatePresentationData(_ presentationData: PresentationData) { + self.presentationData = presentationData + self.headerNode.updatePresentationData(presentationData) + } + + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.headerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) + self.headerNode.updateLayout(size: CGSize(width: size.width, height: size.height), leftInset: leftInset, rightInset: rightInset, transition: transition) + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.headerNode.frame.contains(point) { + return nil + } + return super.hitTest(point, with: event) + } +} diff --git a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift new file mode 100644 index 00000000..387df50b --- /dev/null +++ b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -0,0 +1,817 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import UniversalMediaPlayer +import AccountContext +import TelegramStringFormatting +import ManagedAnimationNode +import ContextUI +import TelegramNotices +import TooltipUI +import SliderContextItem + +private let titleFont = Font.regular(12.0) +private let subtitleFont = Font.regular(10.0) + +private func normalizeValue(_ value: CGFloat) -> CGFloat { + return round(value * 10.0) / 10.0 +} + +private class MediaHeaderItemNode: ASDisplayNode { + private let titleNode: TextNode + private let subtitleNode: TextNode + + override init() { + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + self.titleNode.displaysAsynchronously = false + self.subtitleNode = TextNode() + self.subtitleNode.isUserInteractionEnabled = false + self.subtitleNode.displaysAsynchronously = false + + super.init() + + self.isUserInteractionEnabled = false + + self.addSubnode(self.titleNode) + self.addSubnode(self.subtitleNode) + } + + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, playbackItem: SharedMediaPlaylistItem?, transition: ContainedViewLayoutTransition) -> (NSAttributedString?, NSAttributedString?, Bool) { + var rateButtonHidden = false + var titleString: NSAttributedString? + var subtitleString: NSAttributedString? + if let playbackItem = playbackItem, let displayData = playbackItem.displayData { + switch displayData { + case let .music(title, performer, _, long, _): + rateButtonHidden = !long + let titleText: String = title ?? strings.MediaPlayer_UnknownTrack + let subtitleText: String = performer ?? strings.MediaPlayer_UnknownArtist + + titleString = NSAttributedString(string: titleText, font: titleFont, textColor: theme.rootController.navigationBar.primaryTextColor) + subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: theme.rootController.navigationBar.secondaryTextColor) + case let .voice(author, peer): + rateButtonHidden = false + let titleText: String = author?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? "" + let subtitleText: String + if let peer = peer { + if case let .channel(peer) = peer, case .broadcast = peer.info { + subtitleText = strings.MusicPlayer_VoiceNote + } else if case .legacyGroup = peer { + subtitleText = peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder) + } else if case .channel = peer { + subtitleText = peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder) + } else { + subtitleText = strings.MusicPlayer_VoiceNote + } + } else { + subtitleText = strings.MusicPlayer_VoiceNote + } + + titleString = NSAttributedString(string: titleText, font: titleFont, textColor: theme.rootController.navigationBar.primaryTextColor) + subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: theme.rootController.navigationBar.secondaryTextColor) + case let .instantVideo(author, peer, timestamp): + rateButtonHidden = false + let titleText: String = author?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? "" + var subtitleText: String + + if let peer = peer { + switch peer { + case .legacyGroup, .channel: + subtitleText = peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder) + default: + subtitleText = strings.Message_VideoMessage + } + } else { + subtitleText = strings.Message_VideoMessage + } + + if titleText == subtitleText { + subtitleText = humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: timestamp).string + } + + titleString = NSAttributedString(string: titleText, font: titleFont, textColor: theme.rootController.navigationBar.primaryTextColor) + subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: theme.rootController.navigationBar.secondaryTextColor) + } + } + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) + + var titleSideInset: CGFloat = 12.0 + if !rateButtonHidden { + titleSideInset += 52.0 + } + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - titleSideInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - titleSideInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let _ = titleApply() + let _ = subtitleApply() + + let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0 + + let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 4.0 + minimizedTitleOffset), size: titleLayout.size) + let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 20.0), size: subtitleLayout.size) + + transition.updateFrame(node: self.titleNode, frame: minimizedTitleFrame) + transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame) + + return (titleString, subtitleString, rateButtonHidden) + } +} + +private func generateMaskImage(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 12.0, height: 2.0), opaque: false, rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let gradientColors = [color.cgColor, color.withAlphaComponent(0.0).cgColor] as CFArray + + var locations: [CGFloat] = [0.0, 1.0] + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 12.0, y: 0.0), options: CGGradientDrawingOptions()) + }) +} + +public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollViewDelegate { + private let context: AccountContext + private var theme: PresentationTheme + private var strings: PresentationStrings + private var dateTimeFormat: PresentationDateTimeFormat + private var nameDisplayOrder: PresentationPersonNameOrder + + private let scrollNode: ASScrollNode + private var initialContentOffset: CGFloat? + + private let leftMaskNode: ASImageNode + private let rightMaskNode: ASImageNode + + private let currentItemNode: MediaHeaderItemNode + private let previousItemNode: MediaHeaderItemNode + private let nextItemNode: MediaHeaderItemNode + + private let closeButton: HighlightableButtonNode + private let actionButton: HighlightTrackingButtonNode + private let playPauseIconNode: PlayPauseIconNode + private let rateButton: AudioRateButton + private let accessibilityAreaNode: AccessibilityAreaNode + + private let scrubbingNode: MediaPlayerScrubbingNode + + private var validLayout: (CGSize, CGFloat, CGFloat)? + + public var displayScrubber: Bool = true { + didSet { + self.scrubbingNode.isHidden = !self.displayScrubber + } + } + + private var tapRecognizer: UITapGestureRecognizer? + + public var tapAction: (() -> Void)? + public var close: (() -> Void)? + public var setRate: ((AudioPlaybackRate, MediaNavigationAccessoryPanel.ChangeType) -> Void)? + public var togglePlayPause: (() -> Void)? + public var playPrevious: (() -> Void)? + public var playNext: (() -> Void)? + + public var getController: (() -> ViewController?)? + public var presentInGlobalOverlay: ((ViewController) -> Void)? + + public var playbackBaseRate: AudioPlaybackRate? = nil { + didSet { + guard self.playbackBaseRate != oldValue, let playbackBaseRate = self.playbackBaseRate else { + return + } + self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate + self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange + self.rateButton.accessibilityValue = playbackBaseRate.stringValue + + self.rateButton.setContent(.image(optionsRateImage(rate: playbackBaseRate.stringValue.uppercased(), color: self.theme.rootController.navigationBar.controlColor))) + } + } + + public var playbackStatus: Signal? { + didSet { + self.scrubbingNode.status = self.playbackStatus + } + } + + public var playbackItems: (SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?)? { + didSet { + if !arePlaylistItemsEqual(self.playbackItems?.0, oldValue?.0) || !arePlaylistItemsEqual(self.playbackItems?.1, oldValue?.1) || !arePlaylistItemsEqual(self.playbackItems?.2, oldValue?.2), let layout = validLayout { + self.updateLayout(size: layout.0, leftInset: layout.1, rightInset: layout.2, transition: .immediate) + } + } + } + + private weak var tooltipController: TooltipScreen? + private let dismissedPromise = ValuePromise(false) + + public init(context: AccountContext, presentationData: PresentationData) { + self.context = context + + self.theme = presentationData.theme + self.strings = presentationData.strings + self.dateTimeFormat = presentationData.dateTimeFormat + self.nameDisplayOrder = presentationData.nameDisplayOrder + + self.scrollNode = ASScrollNode() + + self.currentItemNode = MediaHeaderItemNode() + self.previousItemNode = MediaHeaderItemNode() + self.nextItemNode = MediaHeaderItemNode() + + self.leftMaskNode = ASImageNode() + self.leftMaskNode.contentMode = .scaleToFill + self.rightMaskNode = ASImageNode() + self.rightMaskNode.contentMode = .scaleToFill + + let maskImage = generateMaskImage(color: self.theme.rootController.navigationBar.opaqueBackgroundColor) + self.leftMaskNode.image = maskImage + self.rightMaskNode.image = maskImage + + self.closeButton = HighlightableButtonNode() + self.closeButton.accessibilityLabel = presentationData.strings.VoiceOver_Media_PlaybackStop + self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(presentationData.theme.chat.inputPanel.panelControlColor.cgColor) + context.setLineWidth(1.33) + context.setLineCap(.round) + context.move(to: CGPoint(x: 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0)) + context.strokePath() + }), for: []) + self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) + self.closeButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 2.0) + self.closeButton.displaysAsynchronously = false + + self.rateButton = AudioRateButton() + self.rateButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -4.0, bottom: -8.0, right: -4.0) + self.rateButton.displaysAsynchronously = false + + self.accessibilityAreaNode = AccessibilityAreaNode() + + self.actionButton = HighlightTrackingButtonNode() + self.actionButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) + self.actionButton.displaysAsynchronously = false + + self.playPauseIconNode = PlayPauseIconNode() + self.playPauseIconNode.customColor = self.theme.chat.inputPanel.panelControlColor + + self.scrubbingNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: self.theme.chat.inputPanel.panelControlColor, bufferingColor: self.theme.chat.inputPanel.panelControlColor.withAlphaComponent(0.5), chapters: [])) + + super.init() + + self.clipsToBounds = true + + self.addSubnode(self.scrollNode) + self.scrollNode.addSubnode(self.currentItemNode) + self.scrollNode.addSubnode(self.previousItemNode) + self.scrollNode.addSubnode(self.nextItemNode) + + self.addSubnode(self.closeButton) + self.addSubnode(self.rateButton) + self.addSubnode(self.accessibilityAreaNode) + + self.actionButton.addSubnode(self.playPauseIconNode) + self.addSubnode(self.actionButton) + + self.closeButton.addTarget(self, action: #selector(self.closeButtonPressed), forControlEvents: .touchUpInside) + self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside) + + self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside) + self.rateButton.contextAction = { [weak self] sourceNode, gesture in + self?.openRateMenu(sourceNode: sourceNode, gesture: gesture) + } + + self.addSubnode(self.scrubbingNode) + + self.actionButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.actionButton.layer.removeAnimation(forKey: "opacity") + strongSelf.actionButton.alpha = 0.4 + } else { + strongSelf.actionButton.alpha = 1.0 + strongSelf.actionButton.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + + self.scrubbingNode.playerStatusUpdated = { [weak self] status in + guard let strongSelf = self else { + return + } + if let status = status { + strongSelf.playbackBaseRate = AudioPlaybackRate(status.baseRate) + } else { + strongSelf.playbackBaseRate = .x1 + } + } + + self.scrubbingNode.playbackStatusUpdated = { [weak self] status in + if let strongSelf = self { + let paused: Bool + if let status = status { + switch status { + case .paused: + paused = true + case let .buffering(_, whilePlaying, _, _): + paused = !whilePlaying + case .playing: + paused = false + } + } else { + paused = true + } + strongSelf.playPauseIconNode.enqueueState(paused ? .play : .pause, animated: true) + strongSelf.actionButton.accessibilityLabel = paused ? strongSelf.strings.VoiceOver_Media_PlaybackPlay : strongSelf.strings.VoiceOver_Media_PlaybackPause + } + } + } + + override public func didLoad() { + super.didLoad() + + self.view.disablesInteractiveTransitionGestureRecognizer = true + self.scrollNode.view.alwaysBounceHorizontal = true + self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.scrollNode.view.isPagingEnabled = true + self.scrollNode.view.showsHorizontalScrollIndicator = false + self.scrollNode.view.showsVerticalScrollIndicator = false + + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + self.tapRecognizer = tapRecognizer + self.view.addGestureRecognizer(tapRecognizer) + } + + public func updatePresentationData(_ presentationData: PresentationData) { + self.theme = presentationData.theme + self.strings = presentationData.strings + self.nameDisplayOrder = presentationData.nameDisplayOrder + self.dateTimeFormat = presentationData.dateTimeFormat + + let maskImage = generateMaskImage(color: self.theme.rootController.navigationBar.opaqueBackgroundColor) + self.leftMaskNode.image = maskImage + self.rightMaskNode.image = maskImage + + self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(presentationData.theme.chat.inputPanel.panelControlColor.cgColor) + context.setLineWidth(1.33) + context.setLineCap(.round) + context.move(to: CGPoint(x: 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0)) + context.strokePath() + }), for: []) + self.playPauseIconNode.customColor = self.theme.chat.inputPanel.panelControlColor + self.scrubbingNode.updateContent(.standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: self.theme.chat.inputPanel.panelControlColor, bufferingColor: self.theme.chat.inputPanel.panelControlColor.withAlphaComponent(0.5), chapters: [])) + + if let playbackBaseRate = self.playbackBaseRate { + self.rateButton.setContent(.image(optionsRateImage(rate: playbackBaseRate.stringValue.uppercased(), color: self.theme.rootController.navigationBar.controlColor))) + } + if let (size, leftInset, rightInset) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate) + } + } + + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + if scrollView.isDecelerating { + self.changeTrack() + } + + self.rateButton.alpha = 0.0 + self.rateButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + } + + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + self.changeTrack() + + self.rateButton.alpha = 1.0 + self.rateButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + guard !decelerate else { + return + } + self.changeTrack() + + self.rateButton.alpha = 1.0 + self.rateButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + private func changeTrack() { + guard let initialContentOffset = self.initialContentOffset, abs(initialContentOffset - self.scrollNode.view.contentOffset.x) > self.bounds.width / 2.0 else { + return + } + if self.scrollNode.view.contentOffset.x < initialContentOffset { + self.playPrevious?() + } else if self.scrollNode.view.contentOffset.x > initialContentOffset { + self.playNext?() + } + } + + public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, leftInset, rightInset) + + let minHeight = 40.0 + + let inset: CGFloat = 45.0 + leftInset + let constrainedSize = CGSize(width: size.width - inset * 2.0, height: size.height) + let (titleString, subtitleString, rateButtonHidden) = self.currentItemNode.updateLayout(size: constrainedSize, leftInset: leftInset, rightInset: rightInset, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, playbackItem: self.playbackItems?.0, transition: transition) + self.accessibilityAreaNode.accessibilityLabel = "\(titleString?.string ?? ""). \(subtitleString?.string ?? "")" + self.rateButton.isHidden = rateButtonHidden + + let _ = self.previousItemNode.updateLayout(size: constrainedSize, leftInset: 0.0, rightInset: 0.0, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, playbackItem: self.playbackItems?.1, transition: transition) + let _ = self.nextItemNode.updateLayout(size: constrainedSize, leftInset: 0.0, rightInset: 0.0, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, playbackItem: self.playbackItems?.2, transition: transition) + + let constrainedBounds = CGRect(origin: CGPoint(), size: constrainedSize) + transition.updateFrame(node: self.scrollNode, frame: constrainedBounds.offsetBy(dx: inset, dy: 0.0)) + + var contentSize = constrainedSize + var contentOffset: CGFloat = 0.0 + if self.playbackItems?.1 != nil { + contentSize.width += constrainedSize.width + contentOffset = constrainedSize.width + } + if self.playbackItems?.2 != nil { + contentSize.width += constrainedSize.width + } + + self.previousItemNode.frame = constrainedBounds.offsetBy(dx: contentOffset - constrainedSize.width, dy: 0.0) + self.currentItemNode.frame = constrainedBounds.offsetBy(dx: contentOffset, dy: 0.0) + self.nextItemNode.frame = constrainedBounds.offsetBy(dx: contentOffset + constrainedSize.width, dy: 0.0) + + self.leftMaskNode.frame = CGRect(x: inset, y: 0.0, width: 12.0, height: minHeight) + self.rightMaskNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0) + self.rightMaskNode.frame = CGRect(x: size.width - inset - 12.0, y: 0.0, width: 12.0, height: minHeight) + + if !self.scrollNode.view.isTracking && !self.scrollNode.view.isTracking { + self.scrollNode.view.contentSize = contentSize + self.scrollNode.view.contentOffset = CGPoint(x: contentOffset, y: 0.0) + self.initialContentOffset = contentOffset + } + + let bounds = CGRect(origin: CGPoint(), size: size) + let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) + transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 44.0 - rightInset, y: 0.0), size: CGSize(width: 44.0, height: minHeight))) + let rateButtonSize = CGSize(width: 30.0, height: minHeight) + transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 27.0 - closeButtonSize.width - rateButtonSize.width - rightInset, y: -2.0), size: rateButtonSize)) + + transition.updateFrame(node: self.playPauseIconNode, frame: CGRect(origin: CGPoint(x: 6.0, y: 6.0 + UIScreenPixel), size: CGSize(width: 28.0, height: 28.0))) + transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 40.0))) + transition.updateFrame(node: self.scrubbingNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 40.0 - 2.0), size: CGSize(width: size.width, height: 2.0))) + + self.accessibilityAreaNode.frame = CGRect(origin: CGPoint(x: self.actionButton.frame.maxX, y: 0.0), size: CGSize(width: self.rateButton.frame.minX - self.actionButton.frame.maxX, height: minHeight)) + } + + @objc public func closeButtonPressed() { + self.close?() + } + + @objc public func rateButtonPressed() { + var changeType: MediaNavigationAccessoryPanel.ChangeType = .preset + let nextRate: AudioPlaybackRate + if let rate = self.playbackBaseRate { + switch rate { + case .x0_5, .x2: + nextRate = .x1 + case .x1: + nextRate = .x1_5 + case .x1_5: + nextRate = .x2 + default: + if rate.doubleValue < 0.5 { + nextRate = .x0_5 + } else if rate.doubleValue < 1.0 { + nextRate = .x1 + } else if rate.doubleValue < 1.5 { + nextRate = .x1_5 + } else if rate.doubleValue < 2.0 { + nextRate = .x2 + } else { + nextRate = .x1 + } + changeType = .sliderCommit(rate.doubleValue, nextRate.doubleValue) + } + } else { + nextRate = .x1_5 + } + self.setRate?(nextRate, changeType) + + let frame = self.rateButton.view.convert(self.rateButton.bounds, to: nil) + + let _ = (ApplicationSpecificNotice.incrementAudioRateOptionsTip(accountManager: self.context.sharedContext.accountManager) + |> deliverOnMainQueue).start(next: { [weak self] value in + if let strongSelf = self, let controller = strongSelf.getController?(), value == 2 { + let tooltipController = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: strongSelf.strings.Conversation_AudioRateOptionsTooltip), style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _, _ in + return .dismiss(consume: false) + }) + controller.present(tooltipController, in: .window(.root)) + strongSelf.tooltipController = tooltipController + } + }) + } + + private func speedList(strings: PresentationStrings) -> [(String, String, AudioPlaybackRate)] { + let speedList: [(String, String, AudioPlaybackRate)] = [ + ("0.5x", "0.5x", .x0_5), + (strings.PlaybackSpeed_Normal, "1x", .x1), + ("1.5x", "1.5x", .x1_5), + ("2x", "2x", .x2) + ] + return speedList + } + + private func contextMenuSpeedItems(scheduleTooltip: @escaping (MediaNavigationAccessoryPanel.ChangeType?) -> Void) -> Signal { + var presetItems: [ContextMenuItem] = [] + + let previousRate = self.playbackBaseRate + let previousValue = self.playbackBaseRate?.doubleValue ?? 1.0 + let sliderValuePromise = ValuePromise(nil) + let sliderItem: ContextMenuItem = .custom(SliderContextItem(minValue: 0.2, maxValue: 2.5, value: previousValue, valueChanged: { [weak self] newValue, finished in + let newValue = normalizeValue(newValue) + self?.setRate?(AudioPlaybackRate(newValue), .sliderChange) + sliderValuePromise.set(newValue) + if finished { + scheduleTooltip(.sliderCommit(previousValue, newValue)) + } + }), true) + + let theme = self.context.sharedContext.currentPresentationData.with { $0 }.theme + for (text, _, rate) in self.speedList(strings: self.strings) { + let isSelected = self.playbackBaseRate == rate + presetItems.append(.action(ContextMenuActionItem(text: text, icon: { _ in return nil }, iconSource: ContextMenuActionItemIconSource(size: CGSize(width: 24.0, height: 24.0), signal: sliderValuePromise.get() + |> map { value in + if isSelected && value == nil { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return nil + } + }), action: { [weak self] _, f in + scheduleTooltip(nil) + f(.default) + + if let previousRate, previousRate.isPreset { + self?.setRate?(rate, .preset) + } else { + self?.setRate?(rate, .sliderCommit(previousValue, rate.doubleValue)) + } + }))) + } + + return .single(ContextController.Items(content: .twoLists(presetItems, [sliderItem]))) + } + + private func openRateMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) { + guard let controller = self.getController?() else { + return + } + var scheduledTooltip: MediaNavigationAccessoryPanel.ChangeType? + let items = self.contextMenuSpeedItems(scheduleTooltip: { change in + scheduledTooltip = change + }) + let contextController = ContextController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items, gesture: gesture) + contextController.dismissed = { [weak self] in + if let scheduledTooltip, let self, let rate = self.playbackBaseRate { + self.setRate?(rate, scheduledTooltip) + } + } + self.presentInGlobalOverlay?(contextController) + } + + @objc public func actionButtonPressed() { + self.togglePlayPause?() + } + + @objc public func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.tapAction?() + } + } +} + +private enum PlayPauseIconNodeState: Equatable { + case play + case pause +} + +private final class PlayPauseIconNode: ManagedAnimationNode { + private let duration: Double = 0.35 + private var iconState: PlayPauseIconNodeState = .pause + + init() { + super.init(size: CGSize(width: 28.0, height: 28.0)) + + self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 41), duration: 0.01)) + } + + func enqueueState(_ state: PlayPauseIconNodeState, animated: Bool) { + guard self.iconState != state else { + return + } + + let previousState = self.iconState + self.iconState = state + + switch previousState { + case .pause: + switch state { + case .play: + if animated { + self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 83), duration: self.duration)) + } else { + self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01)) + } + case .pause: + break + } + case .play: + switch state { + case .pause: + if animated { + self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 41), duration: self.duration)) + } else { + self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 41), duration: 0.01)) + } + case .play: + break + } + } + } +} + +private func optionsRateImage(rate: String, color: UIColor = .white) -> UIImage? { + let isLarge = "".isEmpty + return generateImage(isLarge ? CGSize(width: 30.0, height: 30.0) : CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in + UIGraphicsPushContext(context) + + context.clear(CGRect(origin: CGPoint(), size: size)) + + if let image = generateTintedImage(image: UIImage(bundleImageName: isLarge ? "Chat/Context Menu/Playspeed30" : "Chat/Context Menu/Playspeed24"), color: color) { + image.draw(at: CGPoint(x: 0.0, y: 0.0)) + } + + let string = NSMutableAttributedString(string: rate, font: Font.with(size: isLarge ? 11.0 : 10.0, design: .round, weight: .semibold), textColor: color) + + var offset = CGPoint(x: 1.0, y: 0.0) + if rate.count >= 3 { + if rate == "0.5x" { + string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string)) + offset.x += -0.5 + } else { + string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string)) + offset.x += -0.3 + } + } else { + offset.x += -0.3 + } + + if !isLarge { + offset.x *= 0.5 + offset.y *= 0.5 + } + + let boundingRect = string.boundingRect(with: size, options: [], context: nil) + string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + floor((size.height - boundingRect.height) / 2.0))) + + UIGraphicsPopContext() + }) +} + +public final class AudioRateButton: HighlightableButtonNode { + public enum Content { + case image(UIImage?) + } + + public let referenceNode: ContextReferenceContentNode + let containerNode: ContextControllerSourceNode + private let iconNode: ASImageNode + + public var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? + + private let wide: Bool + + public init(wide: Bool = false) { + self.wide = wide + + self.referenceNode = ContextReferenceContentNode() + self.containerNode = ContextControllerSourceNode() + self.containerNode.animateScale = false + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + self.iconNode.contentMode = .scaleToFill + + super.init() + + self.containerNode.addSubnode(self.referenceNode) + self.referenceNode.addSubnode(self.iconNode) + self.addSubnode(self.containerNode) + + self.containerNode.shouldBegin = { [weak self] location in + guard let strongSelf = self, let _ = strongSelf.contextAction else { + return false + } + return true + } + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self else { + return + } + strongSelf.contextAction?(strongSelf.containerNode, gesture) + } + + self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0)) + self.referenceNode.frame = self.containerNode.bounds + + if let image = self.iconNode.image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -4.0, bottom: 0.0, right: -4.0) + } + + private var content: Content? + public func setContent(_ content: Content, animated: Bool = false) { + if animated { + if let snapshotView = self.referenceNode.view.snapshotContentTree() { + snapshotView.frame = self.referenceNode.frame + self.view.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false) + + self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3) + } + + switch content { + case let .image(image): + if let image = image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.iconNode.image = image + self.iconNode.isHidden = false + } + } else { + self.content = content + switch content { + case let .image(image): + if let image = image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.iconNode.image = image + self.iconNode.isHidden = false + } + } + } + + public override func didLoad() { + super.didLoad() + self.view.isOpaque = false + } + + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: wide ? 32.0 : 22.0, height: 44.0) + } + + func onLayout() { + } +} + +private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceNode: ContextReferenceContentNode + + var shouldBeDismissed: Signal + + init(controller: ViewController, sourceNode: ContextReferenceContentNode, shouldBeDismissed: Signal) { + self.controller = controller + self.sourceNode = sourceNode + self.shouldBeDismissed = shouldBeDismissed + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryPanel.swift b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryPanel.swift new file mode 100644 index 00000000..bf1939fd --- /dev/null +++ b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryPanel.swift @@ -0,0 +1,91 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import TelegramCore +import AccountContext +import TelegramUIPreferences +import TelegramPresentationData + +public final class MediaNavigationAccessoryPanel: ASDisplayNode { + public let containerNode: MediaNavigationAccessoryContainerNode + + public enum ChangeType { + case preset + case sliderChange + case sliderCommit(CGFloat, CGFloat) + } + + public var close: (() -> Void)? + public var setRate: ((AudioPlaybackRate, ChangeType) -> Void)? + public var togglePlayPause: (() -> Void)? + public var tapAction: (() -> Void)? + public var playPrevious: (() -> Void)? + public var playNext: (() -> Void)? + + public var getController: (() -> ViewController?)? + public var presentInGlobalOverlay: ((ViewController) -> Void)? + + public init(context: AccountContext, presentationData: PresentationData) { + self.containerNode = MediaNavigationAccessoryContainerNode(context: context, presentationData: presentationData) + + super.init() + + self.clipsToBounds = true + + self.addSubnode(self.containerNode) + + self.containerNode.headerNode.close = { [weak self] in + if let strongSelf = self, let close = strongSelf.close { + close() + } + } + self.containerNode.headerNode.setRate = { [weak self] rate, type in + self?.setRate?(rate, type) + } + self.containerNode.headerNode.togglePlayPause = { [weak self] in + if let strongSelf = self, let togglePlayPause = strongSelf.togglePlayPause { + togglePlayPause() + } + } + self.containerNode.headerNode.tapAction = { [weak self] in + if let strongSelf = self, let tapAction = strongSelf.tapAction { + tapAction() + } + } + self.containerNode.headerNode.playPrevious = { [weak self] in + if let strongSelf = self, let playPrevious = strongSelf.playPrevious { + playPrevious() + } + } + self.containerNode.headerNode.playNext = { [weak self] in + if let strongSelf = self, let playNext = strongSelf.playNext { + playNext() + } + } + + self.containerNode.headerNode.getController = { [weak self] in + if let strongSelf = self, let getController = strongSelf.getController { + return getController() + } else { + return nil + } + } + + self.containerNode.headerNode.presentInGlobalOverlay = { [weak self] c in + if let strongSelf = self, let presentInGlobalOverlay = strongSelf.presentInGlobalOverlay { + presentInGlobalOverlay(c) + } + } + } + + public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) + transition.updateAlpha(node: self.containerNode, alpha: isHidden ? 0.0 : 1.0) + self.containerNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: transition) + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return self.containerNode.hitTest(point, with: event) + } +} diff --git a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift new file mode 100644 index 00000000..c8275e69 --- /dev/null +++ b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift @@ -0,0 +1,362 @@ +import Foundation +import UIKit +import Display +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import AccountContext +import TelegramCore +import GlobalControlPanelsContext +import SwiftSignalKit +import UniversalMediaPlayer +import UndoUI +import OverlayStatusController +import TelegramUIPreferences +import Postbox +import PresentationDataUtils + +public final class MediaPlaybackHeaderPanelComponent: Component { + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + public let data: GlobalControlPanelsContext.MediaPlayback + public let controller: () -> ViewController? + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + data: GlobalControlPanelsContext.MediaPlayback, + controller: @escaping () -> ViewController? + ) { + self.context = context + self.theme = theme + self.strings = strings + self.data = data + self.controller = controller + } + + public static func ==(lhs: MediaPlaybackHeaderPanelComponent, rhs: MediaPlaybackHeaderPanelComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.data != rhs.data { + return false + } + return true + } + + public final class View: UIView { + private var panel: MediaNavigationAccessoryPanel? + + private var component: MediaPlaybackHeaderPanelComponent? + private weak var state: EmptyComponentState? + + private var playlistPreloadDisposable: Disposable? + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.playlistPreloadDisposable?.dispose() + } + + func update(component: MediaPlaybackHeaderPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let themeUpdated = self.component?.theme !== component.theme + + self.component = component + self.state = state + + let panel: MediaNavigationAccessoryPanel + if let current = self.panel { + panel = current + } else { + panel = MediaNavigationAccessoryPanel(context: component.context, presentationData: PresentationData( + strings: component.strings, + theme: component.theme, + autoNightModeTriggered: false, + chatWallpaper: .builtin(WallpaperSettings()), + chatFontSize: .regular, + chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: true), + listsFontSize: .regular, + dateTimeFormat: PresentationDateTimeFormat(), + nameDisplayOrder: .firstLast, + nameSortOrder: .firstLast, + reduceMotion: false, + largeEmoji: false + )) + self.panel = panel + self.addSubview(panel.view) + + let delayedStatus = component.context.sharedContext.mediaManager.globalMediaPlayerState + |> mapToSignal { value -> Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> in + guard let value = value else { + return .single(nil) + } + switch value.1 { + case .state: + return .single(value) + case .loading: + return .single(value) |> delay(0.1, queue: .mainQueue()) + } + } + + panel.containerNode.headerNode.playbackStatus = delayedStatus + |> map { state -> MediaPlayerStatus in + if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading { + return state.status + } else { + return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) + } + } + + panel.containerNode.headerNode.displayScrubber = component.data.item.playbackData?.type != .instantVideo + panel.getController = { [weak self] in + guard let self, let component = self.component else { + return nil + } + return component.controller() + } + panel.presentInGlobalOverlay = { [weak self] c in + guard let self, let component = self.component else { + return + } + component.controller()?.presentInGlobalOverlay(c) + } + panel.close = { [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.setPlaylist(nil, type: component.data.kind, control: SharedMediaPlayerControlAction.playback(.pause)) + } + panel.setRate = { [weak self] rate, changeType in + guard let self, let component = self.component else { + return + } + let _ = (component.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in + let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings)?.get(MusicPlaybackSettings.self) ?? MusicPlaybackSettings.defaultSettings + + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in + return PreferencesEntry(settings.withUpdatedVoicePlaybackRate(rate)) + }) + return rate + } + |> deliverOnMainQueue).start(next: { [weak self] baseRate in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: component.data.kind) + + var hasTooltip = false + component.controller()?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + hasTooltip = true + controller.dismissWithCommitAction() + } + return true + }) + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let text: String? + let rate: CGFloat? + if case let .sliderCommit(previousValue, newValue) = changeType { + let value = String(format: "%0.1f", baseRate.doubleValue) + if baseRate == .x1 { + text = presentationData.strings.Conversation_AudioRateTooltipNormal + } else { + text = presentationData.strings.Conversation_AudioRateTooltipCustom(value).string + } + if newValue > previousValue { + rate = .infinity + } else if newValue < previousValue { + rate = -.infinity + } else { + rate = nil + } + } else if baseRate == .x1 { + text = presentationData.strings.Conversation_AudioRateTooltipNormal + rate = 1.0 + } else if baseRate == .x1_5 { + text = presentationData.strings.Conversation_AudioRateTooltip15X + rate = 1.5 + } else if baseRate == .x2 { + text = presentationData.strings.Conversation_AudioRateTooltipSpeedUp + rate = 2.0 + } else { + text = nil + rate = nil + } + var showTooltip = true + if case .sliderChange = changeType { + showTooltip = false + } + if let rate, let text, showTooltip { + let controller = UndoOverlayController( + presentationData: presentationData, + content: .audioRate( + rate: rate, + text: text + ), + elevatedLayout: false, + animateInAsReplacement: hasTooltip, + action: { action in + return true + } + ) + //strongSelf.audioRateTooltipController = controller + component.controller()?.present(controller, in: .current) + } + }) + } + panel.togglePlayPause = { [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: component.data.kind) + } + panel.playPrevious = { [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.playlistControl(.next, type: component.data.kind) + } + panel.playNext = { [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.playlistControl(.previous, type: component.data.kind) + } + panel.tapAction = { [weak self] in + guard let self, let component = self.component, let controller = component.controller(), let navigationController = controller.navigationController as? NavigationController else { + return + } + + if let id = component.data.item.id as? PeerMessagesMediaPlaylistItemId, let playlistLocation = component.data.playlistLocation as? PeerMessagesPlaylistLocation { + if case .music = component.data.kind { + switch playlistLocation { + case .custom, .savedMusic: + let controllerContext: AccountContext + if component.data.account.id == component.context.account.id { + controllerContext = component.context + } else { + controllerContext = component.context.sharedContext.makeTempAccountContext(account: component.data.account) + } + let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: playlistLocation, parentNavigationController: navigationController) + self.window?.endEditing(true) + controller.present(playerController, in: .window(.root)) + case let .messages(chatLocation, _, _): + let signal = component.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId)), count: 60, highlight: true, setupReply: false), id: 0), context: component.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic(value: nil), tag: .tag(MessageTags.music)) + + var cancelImpl: (() -> Void)? + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.component?.controller()?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = MetaDisposable() + var progressStarted = false + self.playlistPreloadDisposable?.dispose() + self.playlistPreloadDisposable = (signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + |> deliverOnMainQueue).start(next: { [weak self] index in + guard let self, let component = self.component else { + return + } + if let _ = index.0 { + let controllerContext: AccountContext + if component.data.account.id == component.context.account.id { + controllerContext = component.context + } else { + controllerContext = component.context.sharedContext.makeTempAccountContext(account: component.data.account) + } + let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: nil, parentNavigationController: navigationController) + self.window?.endEditing(true) + controller.present(playerController, in: .window(.root)) + } else if index.1 { + if !progressStarted { + progressStarted = true + progressDisposable.set(progressSignal.start()) + } + } + }, completed: { + }) + cancelImpl = { [weak self] in + self?.playlistPreloadDisposable?.dispose() + } + default: + break + } + } else { + component.context.sharedContext.navigateToChat(accountId: component.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId) + } + } + } + } + + let size = CGSize(width: availableSize.width, height: 40.0) + let panelFrame = CGRect(origin: CGPoint(), size: size) + transition.setFrame(view: panel.view, frame: panelFrame) + panel.updateLayout(size: panelFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition) + + switch component.data.playbackOrder { + case .regular: + panel.containerNode.headerNode.playbackItems = (component.data.item, component.data.previousItem, component.data.nextItem) + case .reversed: + panel.containerNode.headerNode.playbackItems = (component.data.item, component.data.nextItem, component.data.previousItem) + case .random: + panel.containerNode.headerNode.playbackItems = (component.data.item, nil, nil) + } + + if themeUpdated { + panel.containerNode.updatePresentationData(PresentationData( + strings: component.strings, + theme: component.theme, + autoNightModeTriggered: false, + chatWallpaper: .builtin(WallpaperSettings()), + chatFontSize: .regular, + chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: true), + listsFontSize: .regular, + dateTimeFormat: PresentationDateTimeFormat(), + nameDisplayOrder: .firstLast, + nameSortOrder: .firstLast, + reduceMotion: false, + largeEmoji: false + )) + } + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/MeshTransform/BUILD b/submodules/TelegramUI/Components/MeshTransform/BUILD new file mode 100644 index 00000000..cde3e516 --- /dev/null +++ b/submodules/TelegramUI/Components/MeshTransform/BUILD @@ -0,0 +1,18 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "MeshTransform", + module_name = "MeshTransform", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/TelegramUI/Components/MeshTransform/MeshTransformApi" + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/MeshTransform/MeshTransformApi/BUILD b/submodules/TelegramUI/Components/MeshTransform/MeshTransformApi/BUILD new file mode 100644 index 00000000..f1f97638 --- /dev/null +++ b/submodules/TelegramUI/Components/MeshTransform/MeshTransformApi/BUILD @@ -0,0 +1,22 @@ + +objc_library( + name = "MeshTransformApi", + enable_modules = True, + module_name = "MeshTransformApi", + srcs = glob([ + "Sources/*.m", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + sdk_frameworks = [ + "Foundation", + "UIKit", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/MeshTransform/MeshTransformApi/PublicHeaders/MeshTransformApi/MeshTransformApi.h b/submodules/TelegramUI/Components/MeshTransform/MeshTransformApi/PublicHeaders/MeshTransformApi/MeshTransformApi.h new file mode 100644 index 00000000..3691bb61 --- /dev/null +++ b/submodules/TelegramUI/Components/MeshTransform/MeshTransformApi/PublicHeaders/MeshTransformApi/MeshTransformApi.h @@ -0,0 +1,33 @@ +#ifndef MeshTransformApi_h +#define MeshTransformApi_h + +#import +#import + +typedef struct MeshTransformMeshFace { + unsigned int indices[4]; + float w[4]; +} MeshTransformMeshFace; + +typedef struct MeshTransformPoint3D { + CGFloat x; + CGFloat y; + CGFloat z; +} MeshTransformPoint3D; + +typedef struct MeshTransformMeshVertex { + CGPoint from; + MeshTransformPoint3D to; +} MeshTransformMeshVertex; + +@protocol MeshTransformClass + +- (id)meshTransformWithVertexCount:(NSUInteger)vertexCount + vertices:(MeshTransformMeshVertex *)vertices + faceCount:(NSUInteger)faceCount + faces:(MeshTransformMeshFace *)faces + depthNormalization:(NSString *)depthNormalization; + +@end + +#endif diff --git a/submodules/TelegramUI/Components/MeshTransform/MeshTransformApi/Sources/MeshTransformApi.m b/submodules/TelegramUI/Components/MeshTransform/MeshTransformApi/Sources/MeshTransformApi.m new file mode 100644 index 00000000..a3272be0 --- /dev/null +++ b/submodules/TelegramUI/Components/MeshTransform/MeshTransformApi/Sources/MeshTransformApi.m @@ -0,0 +1,2 @@ +#import + diff --git a/submodules/TelegramUI/Components/MeshTransform/Sources/GenerateMesh.swift b/submodules/TelegramUI/Components/MeshTransform/Sources/GenerateMesh.swift new file mode 100644 index 00000000..e6aa5704 --- /dev/null +++ b/submodules/TelegramUI/Components/MeshTransform/Sources/GenerateMesh.swift @@ -0,0 +1,627 @@ +import Foundation +import UIKit + +private func a(_ a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return 1.0 - 3.0 * a2 + 3.0 * a1 +} + +private func b(_ a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return 3.0 * a2 - 6.0 * a1 +} + +private func c(_ a1: CGFloat) -> CGFloat +{ + return 3.0 * a1 +} + +private func calcBezier(_ t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return ((a(a1, a2)*t + b(a1, a2))*t + c(a1)) * t +} + +private func calcSlope(_ t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return 3.0 * a(a1, a2) * t * t + 2.0 * b(a1, a2) * t + c(a1) +} + +private func getTForX(_ x: CGFloat, _ x1: CGFloat, _ x2: CGFloat) -> CGFloat { + var t = x + var i = 0 + while i < 4 { + let currentSlope = calcSlope(t, x1, x2) + if currentSlope == 0.0 { + return t + } else { + let currentX = calcBezier(t, x1, x2) - x + t -= currentX / currentSlope + } + + i += 1 + } + + return t +} + +private func bezierPoint(_ x1: CGFloat, _ y1: CGFloat, _ x2: CGFloat, _ y2: CGFloat, _ x: CGFloat) -> CGFloat +{ + var value = calcBezier(getTForX(x, x1, x2), y1, y2) + if value >= 0.997 { + value = 1.0 + } + return value +} + +/// Bezier control points for displacement easing curve +public struct DisplacementBezier { + var x1: CGFloat + var y1: CGFloat + var x2: CGFloat + var y2: CGFloat + + public init(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) { + self.x1 = x1 + self.y1 = y1 + self.x2 = x2 + self.y2 = y2 + } +} + +/// Computes signed distance from a point to the edge of a rounded rectangle. +/// Returns negative inside, zero on edge, positive outside. +/// All values in points. +public func roundedRectSDF(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat, cornerRadius: CGFloat) -> CGFloat { + // Center the point (SDF formula assumes center at origin) + let px = x - width / 2 + let py = y - height / 2 + + // Half extents of the box + let bx = width / 2 + let by = height / 2 + + // Standard rounded box SDF (Inigo Quilez formula) + let qx = abs(px) - bx + cornerRadius + let qy = abs(py) - by + cornerRadius + + let outsideDist = hypot(max(qx, 0), max(qy, 0)) + let insideDist = min(max(qx, qy), 0) + + return outsideDist + insideDist - cornerRadius +} + +/// Computes the gradient (outward normal) of the rounded rect SDF. +/// Returns normalized direction perpendicular to the nearest edge point. +public func roundedRectGradient(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat, cornerRadius: CGFloat) -> (nx: CGFloat, ny: CGFloat) { + // Center the point + let px = x - width / 2 + let py = y - height / 2 + + // Half extents + let bx = width / 2 + let by = height / 2 + + // q values from SDF formula + let qx = abs(px) - bx + cornerRadius + let qy = abs(py) - by + cornerRadius + + var nx: CGFloat = 0 + var ny: CGFloat = 0 + + if qx > 0 && qy > 0 { + // Corner region - normal points radially from corner arc center + let d = hypot(qx, qy) + if d > 0 { + nx = qx / d + ny = qy / d + } + } else if qx > qy { + // Nearest point is on vertical edge (left or right) + nx = 1 + ny = 0 + } else { + // Nearest point is on horizontal edge (top or bottom) + nx = 0 + ny = 1 + } + + // Restore sign based on which side of center we're on + if px < 0 { nx = -nx } + if py < 0 { ny = -ny } + + return (nx, ny) +} + +/// Generates a displacement map image as a signed distance field from rounded rect edges. +/// - edgeDistance: The distance (in points) over which displacement is applied +/// - R channel: X displacement (127 = neutral, 0 = max left, 255 = max right) +/// - G channel: Y displacement (127 = neutral, 0 = max up, 255 = max down) +/// - B channel: Unused (always 0) +/// Displacement is maximum at the edge and fades linearly to zero at edgeDistance. +/// Actual displacement magnitude is applied when sampling the map. +public func generateDisplacementMap(size: CGSize, cornerRadius: CGFloat, edgeDistance: CGFloat, scale: CGFloat) -> CGImage? { + let width = Int(size.width * scale) + let height = Int(size.height * scale) + + // Clamp corner radius + let maxCornerRadius = min(size.width, size.height) / 2.0 + let clampedRadius = min(cornerRadius, maxCornerRadius) + + // Create bitmap context + var pixelData = [UInt8](repeating: 0, count: width * height * 4) + + for py in 0 ..< height { + for px in 0 ..< width { + // Convert pixel to point coordinates + let x = CGFloat(px) / scale + let y = CGFloat(py) / scale + + // Get signed distance (negative inside, positive outside) + let sdf = roundedRectSDF(x: x, y: y, width: size.width, height: size.height, cornerRadius: clampedRadius) + + // Get gradient (outward normal direction) + let (nx, ny) = roundedRectGradient(x: x, y: y, width: size.width, height: size.height, cornerRadius: clampedRadius) + + // Inward normal (content moves away from edge, toward center) + let inwardX = -nx + let inwardY = -ny + + // Distance from edge (positive inside the shape) + let distFromEdge = -sdf + + // Weight: 1 at edge, 0 at edgeDistance (linear falloff) + let weight = max(0, min(1, 1.0 - distFromEdge / edgeDistance)) + + // Displacement modulated by distance from edge + let displacementX = inwardX * weight + let displacementY = inwardY * weight + + // Encode in R/G: 127 = neutral, map -1..1 to 0..254 + let r = UInt8(max(0, min(255, Int(127 + displacementX * 127)))) + let g = UInt8(max(0, min(255, Int(127 + displacementY * 127)))) + + let idx = (py * width + px) * 4 + pixelData[idx + 0] = r // X displacement + pixelData[idx + 1] = g // Y displacement + pixelData[idx + 2] = 0 // Unused + pixelData[idx + 3] = 255 // A + } + } + + let colorSpace = CGColorSpaceCreateDeviceRGB() + guard let context = CGContext( + data: &pixelData, + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: width * 4, + space: colorSpace, + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue + ) else { + return nil + } + + return context.makeImage() +} + +/// Samples displacement from a displacement map with bilinear interpolation and bezier easing +/// - Parameters: +/// - x, y: Coordinates in the displacement map's pixel space +/// - pixels: Pointer to displacement map pixel data +/// - width, height: Displacement map dimensions +/// - bytesPerRow, bytesPerPixel: Displacement map layout +/// - bezier: Bezier control points for easing curve +/// - Returns: Displacement (dx, dy) in range -1..1 with bezier easing applied +public func sampleDisplacement( + x: CGFloat, + y: CGFloat, + pixels: UnsafePointer, + width: Int, + height: Int, + bytesPerRow: Int, + bytesPerPixel: Int, + bezier: DisplacementBezier +) -> (dx: CGFloat, dy: CGFloat) { + let clampedX = max(0, min(CGFloat(width - 1), x)) + let clampedY = max(0, min(CGFloat(height - 1), y)) + + let x0 = Int(clampedX) + let y0 = Int(clampedY) + let x1 = min(x0 + 1, width - 1) + let y1 = min(y0 + 1, height - 1) + + let fx = clampedX - CGFloat(x0) + let fy = clampedY - CGFloat(y0) + + func sample(_ sx: Int, _ sy: Int) -> (r: CGFloat, g: CGFloat) { + let offset = sy * bytesPerRow + sx * bytesPerPixel + return (CGFloat(pixels[offset + 0]), CGFloat(pixels[offset + 1])) + } + + let c00 = sample(x0, y0) + let c10 = sample(x1, y0) + let c01 = sample(x0, y1) + let c11 = sample(x1, y1) + + let r = (c00.r * (1 - fx) + c10.r * fx) * (1 - fy) + (c01.r * (1 - fx) + c11.r * fx) * fy + let g = (c00.g * (1 - fx) + c10.g * fx) * (1 - fy) + (c01.g * (1 - fx) + c11.g * fx) * fy + + // Decode: 127 = neutral, map 0..254 to -1..1 + var dx = (r - 127.0) / 127.0 + var dy = (g - 127.0) / 127.0 + + // Apply bezier easing to vector magnitude, preserving direction + let mag = hypot(dx, dy) + if mag > 0 { + let newMag = bezierPoint(bezier.x1, bezier.y1, bezier.x2, bezier.y2, mag) + let scale = newMag / mag + dx *= scale + dy *= scale + } + + return (dx, dy) +} + +/// Generates a glass mesh with corner-aware topology. +/// - 4 radial corner wedges sampled in polar space +/// - 4 edge strips aligned with the rectangle sides +/// - 1 center patch +/// Corner/edge seams share the same coordinates (but do not reuse vertices) so +/// the neighbouring faces fit perfectly without T-junctions. +public func generateGlassMeshFromDisplacementMap( + size: CGSize, + cornerRadius: CGFloat, + displacementMap: CGImage, + displacementMagnitudeU: CGFloat, + displacementMagnitudeV: CGFloat, + cornerResolution: Int, + outerEdgeDistance: CGFloat, + bezier: DisplacementBezier, + generateWireframe: Bool = false +) -> (mesh: MeshTransform, wireframe: CGPath?) { + guard let dispDataProvider = displacementMap.dataProvider, + let dispData = dispDataProvider.data, + let dispPixels = CFDataGetBytePtr(dispData) else { + return (mesh: MeshTransform(), wireframe: nil) + } + + let dispWidth = displacementMap.width + let dispHeight = displacementMap.height + let dispBytesPerRow = displacementMap.bytesPerRow + let dispBytesPerPixel = displacementMap.bitsPerPixel / 8 + + let clampedRadius = min(cornerRadius, min(size.width, size.height) / 2) + + let transform = MeshTransform() + var wireframe: CGMutablePath? + if generateWireframe { + wireframe = CGMutablePath() + } + + // Debug flags + let debugNoDisplacement = false + let debugLogCorner = false + + // Inset the mesh slightly (1 pixel) to clear the clip mask + let insetPoints = -1.0 + let usableWidth = max(1.0, size.width - insetPoints * 2) + let usableHeight = max(1.0, size.height - insetPoints * 2) + let insetUOffset = insetPoints / size.width + let insetVOffset = insetPoints / size.height + let usableUNorm = usableWidth / size.width + let usableVNorm = usableHeight / size.height + + // Helper to sample displacement and create vertex + func makeVertex(u: CGFloat, v: CGFloat, depth: CGFloat = 0) -> (vertex: MeshTransform.Vertex, point: CGPoint) { + let mappedU = insetUOffset + u * usableUNorm + let mappedV = insetVOffset + v * usableVNorm + let fromX: CGFloat + let fromY: CGFloat + + if debugNoDisplacement { + fromX = mappedU + fromY = mappedV + } else { + let (dispX, dispY) = sampleDisplacement( + x: mappedU * CGFloat(dispWidth - 1), + y: mappedV * CGFloat(dispHeight - 1), + pixels: dispPixels, + width: dispWidth, + height: dispHeight, + bytesPerRow: dispBytesPerRow, + bytesPerPixel: dispBytesPerPixel, + bezier: bezier + ) + + // Slight boost near the edge to emphasize the outer strip (rounded-corner aware) + let worldX = insetPoints + u * usableWidth + let worldY = insetPoints + v * usableHeight + let sdf = roundedRectSDF(x: worldX, y: worldY, width: size.width, height: size.height, cornerRadius: clampedRadius) + let distToEdge = max(0.0, -sdf) // distance inside the rounded rect to the edge + let edgeBand = max(0.0, outerEdgeDistance) + let edgeBoostGain: CGFloat = 0.5 // up to +50% displacement at the edge, fades inside + let edgeBoost: CGFloat + if edgeBand > 0 { + let t = max(0.0, min(1.0, (edgeBand - distToEdge) / edgeBand)) + let eased = t * t * (3 - 2 * t) // smoothstep + edgeBoost = 1.0 + eased * edgeBoostGain + } else { + edgeBoost = 1.0 + } + + fromX = max(0.0, min(1.0, mappedU + dispX * displacementMagnitudeU * edgeBoost)) + fromY = max(0.0, min(1.0, mappedV + dispY * displacementMagnitudeV * edgeBoost)) + } + + let vertex = MeshTransform.Vertex(from: CGPoint(x: fromX, y: fromY), to: MeshTransform.Point3D(x: mappedU, y: mappedV, z: depth)) + return (vertex, CGPoint(x: mappedU * size.width, y: mappedV * size.height)) + } + + var vertexIndex = 0 + var vertexPoints: [CGPoint] = [] + + func addVertex(u: CGFloat, v: CGFloat, depth: CGFloat = 0) -> Int { + let (vertex, point) = makeVertex(u: u, v: v, depth: depth) + transform.add(vertex) + vertexPoints.append(point) + let idx = vertexIndex + vertexIndex += 1 + return idx + } + + func addVertex(point: CGPoint, depth: CGFloat = 0) -> Int { + let u = point.x / size.width + let v = point.y / size.height + return addVertex(u: u, v: v, depth: depth) + } + + func addQuadFace(_ i0: Int, _ i1: Int, _ i2: Int, _ i3: Int) { + let p0 = vertexPoints[i0] + let p1 = vertexPoints[i1] + let p2 = vertexPoints[i2] + let p3 = vertexPoints[i3] + + let sdf0 = roundedRectSDF(x: p0.x, y: p0.y, width: size.width, height: size.height, cornerRadius: clampedRadius) + let sdf1 = roundedRectSDF(x: p1.x, y: p1.y, width: size.width, height: size.height, cornerRadius: clampedRadius) + let sdf2 = roundedRectSDF(x: p2.x, y: p2.y, width: size.width, height: size.height, cornerRadius: clampedRadius) + let sdf3 = roundedRectSDF(x: p3.x, y: p3.y, width: size.width, height: size.height, cornerRadius: clampedRadius) + + if sdf0 > 0 && sdf1 > 0 && sdf2 > 0 && sdf3 > 0 { + return + } + + transform.add(MeshTransform.Face(indices: (UInt32(i0), UInt32(i1), UInt32(i2), UInt32(i3)), w: (0.0, 0.0, 0.0, 0.0))) + + if let wireframe { + wireframe.move(to: p0) + wireframe.addLine(to: p1) + wireframe.addLine(to: p2) + wireframe.addLine(to: p3) + wireframe.closeSubpath() + } + } + + // Utility to build a grid of vertices from 2D points and emit quads + func buildGrid(points: [[CGPoint]]) { + guard !points.isEmpty else { return } + + var indexGrid: [[Int]] = [] + for row in points { + var rowIndices: [Int] = [] + for point in row { + rowIndices.append(addVertex(point: point)) + } + indexGrid.append(rowIndices) + } + + let numRows = indexGrid.count - 1 + let numCols = indexGrid.first!.count - 1 + for row in 0.. [CGFloat] { + guard count > 0, maxRadius > 0 else { return [0, 1] } + let bandNorm = max(0, min(1, band / maxRadius)) + + // Evenly distribute inner rings up to (1 - bandNorm), then insert the outer strip edge and 1.0. + let innerSegments = max(1, count - 1) + let innerMax = max(0, 1 - bandNorm) + + var factors: [CGFloat] = (0...innerSegments).map { i in + innerMax * CGFloat(i) / CGFloat(innerSegments) + } + + func appendUnique(_ value: CGFloat) { + if let last = factors.last, abs(last - value) < 1e-4 { return } + factors.append(value) + } + + appendUnique(innerMax) + appendUnique(1.0) + + return factors + } + + let depthFactors = depthFactorsWithOuterBand(count: radialSteps, band: outerEdgeDistance, maxRadius: clampedRadius) // 0...1 + let angularFactors = (0...angularSteps).map { CGFloat($0) / CGFloat(angularSteps) } // 0...1 + + // Edge segmentation along the long axes; even spacing + let horizontalSegments = max(2, cornerResolution / 2 + 1) + let verticalSegments = max(2, cornerResolution / 2 + 1) + + func linearPositions(count: Int, start: CGFloat, end: CGFloat) -> [CGFloat] { + return (0...count).map { i in + let t = CGFloat(i) / CGFloat(count) + return start + (end - start) * t + } + } + + // Shared tangential coordinates for strips/center + let topXPositions: [CGFloat] = linearPositions( + count: horizontalSegments, + start: clampedRadius, + end: width - clampedRadius + ) + let sideYPositions: [CGFloat] = linearPositions( + count: verticalSegments, + start: clampedRadius, + end: height - clampedRadius + ) + + // Shared depth coordinates (outer -> inner) so seams line up without T-junctions + let outerToInner = depthFactors.reversed() + let topYPositions: [CGFloat] = outerToInner.map { clampedRadius * (1 - $0) } // 0 ... radius + let bottomYPositions: [CGFloat] = depthFactors.map { height - clampedRadius + clampedRadius * $0 } // (h-r) ... h + let leftXPositions: [CGFloat] = outerToInner.map { clampedRadius * (1 - $0) } // 0 ... radius + let rightXPositions: [CGFloat] = depthFactors.map { width - clampedRadius + clampedRadius * $0 } // (w-r) ... w + + // Corner wedges in polar space with an explicit center fan to avoid zero-area quads + func buildCorner(center: CGPoint, startAngle: CGFloat, endAngle: CGFloat) { + let ringRadials = outerToInner.filter { $0 > 0 } + guard !ringRadials.isEmpty else { return } + + func formatVertex(_ idx: Int) -> String { + let p = vertexPoints[idx] + return "\(idx)=\(String(format: "(%.2f, %.2f)", p.x, p.y))" + } + + // Generate ring vertices from outer arc toward the center point + var ringIndices: [[Int]] = [] + for radial in ringRadials { + let r = clampedRadius * radial + var row: [Int] = [] + for t in angularFactors { + let angle = startAngle + (endAngle - startAngle) * t + let x = center.x + r * cos(angle) + let y = center.y + r * sin(angle) + row.append(addVertex(point: CGPoint(x: x, y: y))) + } + ringIndices.append(row) + } + + // Quad rings between concentric samples + for r in 0..<(ringIndices.count - 1) { + let outerRing = ringIndices[r] + let innerRing = ringIndices[r + 1] + for i in 0..<(outerRing.count - 1) { + addQuadFace( + outerRing[i], + outerRing[i + 1], + innerRing[i + 1], + innerRing[i] + ) + } + } + + // Final collapse: merge two wedge slices into one quad anchored at the center. + // Each quad spans a double-width wedge: center -> v0 -> v1 -> v2 (contiguous along the arc). + if let innermostRing = ringIndices.last { + let ringSegments = innermostRing.count - 1 // last point is the arc end (not wrapped) + guard ringSegments >= 2 else { return } + + if debugLogCorner { + let formatted = innermostRing.map { formatVertex($0) }.joined(separator: ", ") + print("Corner collapse ringSegments=\(ringSegments) stride=2 angularSteps=\(angularSteps)") + print("Innermost ring vertices: \(formatted)") + } + + let centerAnchor = addVertex(point: center, depth: -0.02) + let stride = 2 + + // Each quad covers two arc segments: (vi, vi+1) and (vi+1, vi+2) + var i = 0 + while i + 2 <= ringSegments { + let v0 = innermostRing[i] + let v1 = innermostRing[i + 1] + let v2 = innermostRing[i + 2] + + if debugLogCorner { + print("Quad indices: [\(centerAnchor), \(v0), \(v1), \(v2)]") + } + + addQuadFace( + centerAnchor, + v0, + v1, + v2 + ) + + i += stride + } + + // Safety: if an odd segment remains, cap it with a final quad + if i < ringSegments { + let v0 = innermostRing[ringSegments - 1] + let v1 = innermostRing[ringSegments] + let v2 = innermostRing[ringSegments] // duplicate to keep quad valid + if debugLogCorner { + print("Quad indices (odd tail): [\(centerAnchor), \(v0), \(v1), \(v2)]") + } + addQuadFace(centerAnchor, v0, v1, v2) + } + } + } + + // Edge strips + func buildStrip(xPositions: [CGFloat], yPositions: [CGFloat]) { + var points: [[CGPoint]] = [] + for y in yPositions { + let row = xPositions.map { CGPoint(x: $0, y: y) } + points.append(row) + } + buildGrid(points: points) + } + + // Top / bottom strips + buildStrip(xPositions: topXPositions, yPositions: topYPositions) + buildStrip(xPositions: topXPositions, yPositions: bottomYPositions) + + // Left / right strips + buildStrip(xPositions: leftXPositions, yPositions: sideYPositions) + buildStrip(xPositions: rightXPositions, yPositions: sideYPositions) + + // Center patch uses the same tangential sampling to meet edges cleanly + buildStrip(xPositions: topXPositions, yPositions: sideYPositions) + + // Corners (angles chosen to keep columns increasing along +x) + buildCorner( + center: CGPoint(x: clampedRadius, y: clampedRadius), + startAngle: .pi, + endAngle: 1.5 * .pi + ) + buildCorner( + center: CGPoint(x: width - clampedRadius, y: clampedRadius), + startAngle: 1.5 * .pi, + endAngle: 2 * .pi + ) + buildCorner( + center: CGPoint(x: width - clampedRadius, y: height - clampedRadius), + startAngle: .pi / 2, + endAngle: 0 + ) + buildCorner( + center: CGPoint(x: clampedRadius, y: height - clampedRadius), + startAngle: .pi, + endAngle: .pi / 2 + ) + + return (mesh: transform, wireframe: wireframe) +} diff --git a/submodules/TelegramUI/Components/MeshTransform/Sources/MeshTransform.swift b/submodules/TelegramUI/Components/MeshTransform/Sources/MeshTransform.swift new file mode 100644 index 00000000..b625a71c --- /dev/null +++ b/submodules/TelegramUI/Components/MeshTransform/Sources/MeshTransform.swift @@ -0,0 +1,150 @@ +import Foundation +import UIKit +import MeshTransformApi + +private let transformClass: NSObject? = { + let name = ("CAMutable" as NSString).appendingFormat("MeshTransform") + if let cls = NSClassFromString(name as String) as AnyObject as? NSObject { + return cls + } + return nil +}() + +private let immutableTransformClass: NSObject? = { + let name = ("CA" as NSString).appendingFormat("MeshTransform") + if let cls = NSClassFromString(name as String) as AnyObject as? NSObject { + return cls + } + return nil +}() + +@inline(__always) +private func getMethod(object: NSObject, selector: String) -> T? { + guard let method = object.method(for: NSSelectorFromString(selector)) else { + return nil + } + return unsafeBitCast(method, to: T.self) +} + +private var cachedTransformCreateMethod: (@convention(c) (AnyObject, Selector) -> NSObject?, Selector)? +private func invokeTransformCreateMethod() -> NSObject? { + guard let transformClass = transformClass else { + return nil + } + if let cachedTransformCreateMethod { + return cachedTransformCreateMethod.0(transformClass, cachedTransformCreateMethod.1) + } else { + let method: (@convention(c) (AnyObject, Selector) -> NSObject?)? = getMethod(object: transformClass, selector: "meshTransform") + if let method { + let selector = NSSelectorFromString("meshTransform") + cachedTransformCreateMethod = (method, selector) + return method(transformClass, selector) + } else { + return nil + } + } +} + +private var cachedTransformCreateCustomMethod: (@convention(c) (AnyObject, Selector, _ vertexCount: UInt, _ vertices: UnsafeMutablePointer, _ faceCount: UInt, _ faces: UnsafeMutablePointer, _ depthNormalization: NSString) -> NSObject?, Selector)? +private func invokeTransformCreateCustomMethod(vertexCount: UInt, vertices: UnsafeMutablePointer, faceCount: UInt, faces: UnsafeMutablePointer, depthNormalization: String) -> NSObject? { + guard let transformClass = transformClass else { + return nil + } + if let cachedTransformCreateCustomMethod { + return cachedTransformCreateCustomMethod.0(transformClass, cachedTransformCreateCustomMethod.1, vertexCount, vertices, faceCount, faces, depthNormalization as NSString) + } else { + let selectorName = ("meshTransf" as NSString).appending("ormWithVertexCount:vertices:faceCount:faces:depthNormalization:") + + let method: (@convention(c) (AnyObject, Selector, _ vertexCount: UInt, _ vertices: UnsafeMutablePointer, _ faceCount: UInt, _ faces: UnsafeMutablePointer, _ depthNormalization: NSString) -> NSObject?)? = getMethod(object: transformClass, selector: selectorName) + if let method { + let selector = NSSelectorFromString(selectorName) + cachedTransformCreateCustomMethod = (method, selector) + return method(transformClass, selector, vertexCount, vertices, faceCount, faces, depthNormalization as NSString) + } else { + return nil + } + } +} + +private var cachedTransformAddFaceMethod: (@convention(c) (AnyObject, Selector, MeshTransformMeshFace) -> Void, Selector)? +private func invokeTransformAddFaceMethod(object: NSObject, face: MeshTransformMeshFace) { + if let cachedTransformAddFaceMethod { + return cachedTransformAddFaceMethod.0(object, cachedTransformAddFaceMethod.1, face) + } else { + let method: (@convention(c) (AnyObject, Selector, MeshTransformMeshFace) -> Void)? = getMethod(object: object, selector: "addFace:") + if let method { + let selector = NSSelectorFromString("addFace:") + cachedTransformAddFaceMethod = (method, selector) + method(object, selector, face) + } + } +} + +private var cachedTransformAddVertexMethod: (@convention(c) (AnyObject, Selector, MeshTransformMeshVertex) -> Void, Selector)? +private func invokeTransformAddVertexMethod(object: NSObject, vertex: MeshTransformMeshVertex) { + if let cachedTransformAddVertexMethod { + return cachedTransformAddVertexMethod.0(object, cachedTransformAddVertexMethod.1, vertex) + } else { + let method: (@convention(c) (AnyObject, Selector, MeshTransformMeshVertex) -> Void)? = getMethod(object: object, selector: "addVertex:") + if let method { + let selector = NSSelectorFromString("addVertex:") + cachedTransformAddVertexMethod = (method, selector) + method(object, selector, vertex) + } + } +} + +private var cachedTransformSetSubdivisionStepsMethod: (@convention(c) (AnyObject, Selector, Int) -> Void, Selector)? +private func invokeTransformSetSubdivisionStepsMethod(object: NSObject, value: Int) { + if let cachedTransformSetSubdivisionStepsMethod { + return cachedTransformSetSubdivisionStepsMethod.0(object, cachedTransformSetSubdivisionStepsMethod.1, value) + } else { + let method: (@convention(c) (AnyObject, Selector, Int) -> Void)? = getMethod(object: object, selector: "setSubdivisionSteps:") + if let method { + let selector = NSSelectorFromString("setSubdivisionSteps:") + cachedTransformSetSubdivisionStepsMethod = (method, selector) + method(object, selector, value) + } + } +} + +public final class MeshTransform { + public typealias Value = NSObject + + public typealias Point3D = MeshTransformPoint3D + public typealias Vertex = MeshTransformMeshVertex + public typealias Face = MeshTransformMeshFace + + private var vertices: ContiguousArray = [] + private var faces: ContiguousArray = [] + + public init() { + } + + public func add(_ vertex: Vertex) { + self.vertices.append(vertex) + } + + public func add(_ face: Face) { + self.faces.append(face) + } + + public consuming func makeValue() -> Value? { + let result = self.vertices.withUnsafeMutableBufferPointer { vertices -> NSObject? in + return self.faces.withUnsafeMutableBufferPointer { faces -> NSObject? in + return invokeTransformCreateCustomMethod( + vertexCount: UInt(vertices.count), + vertices: vertices.baseAddress!, + faceCount: UInt(faces.count), + faces: faces.baseAddress!, + depthNormalization: "none" + ) + } + } + if let result { + invokeTransformSetSubdivisionStepsMethod(object: result, value: 0) + } + + return result + } +} diff --git a/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/BUILD new file mode 100644 index 00000000..17916486 --- /dev/null +++ b/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/BUILD @@ -0,0 +1,31 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "MessageFeeHeaderPanelComponent", + module_name = "MessageFeeHeaderPanelComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/PresentationDataUtils", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramUIPreferences", + "//submodules/TelegramStringFormatting", + "//submodules/TextFormat", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/Sources/ChatFeePanelNode.swift b/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/Sources/ChatFeePanelNode.swift new file mode 100644 index 00000000..a4a3262b --- /dev/null +++ b/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/Sources/ChatFeePanelNode.swift @@ -0,0 +1,155 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import TelegramStringFormatting +import TextFormat +import TextNodeWithEntities + +final class ChatFeePanelNode: ASDisplayNode { + private let context: AccountContext + private let removeFee: () -> Void + + private let contextContainer: ContextControllerSourceNode + private let clippingContainer: ASDisplayNode + private let contentContainer: ASDisplayNode + + private let textContainer: ASDisplayNode + private let textNode: ImmediateTextNodeWithEntities + + private let removeButtonNode: HighlightTrackingButtonNode + private let removeTextNode: ImmediateTextNode + + private var currentLayout: (CGFloat, CGFloat, CGFloat)? + + init(context: AccountContext, removeFee: @escaping () -> Void) { + self.context = context + self.removeFee = removeFee + + self.contextContainer = ContextControllerSourceNode() + + self.clippingContainer = ASDisplayNode() + self.clippingContainer.clipsToBounds = true + + self.contentContainer = ASDisplayNode() + self.contextContainer.isGestureEnabled = false + + self.textContainer = ASDisplayNode() + + self.textNode = ImmediateTextNodeWithEntities() + self.textNode.anchorPoint = CGPoint() + self.textNode.displaysAsynchronously = false + self.textNode.isUserInteractionEnabled = false + self.textNode.maximumNumberOfLines = 2 + self.textNode.textAlignment = .center + + self.removeButtonNode = HighlightTrackingButtonNode() + + self.removeTextNode = ImmediateTextNode() + self.removeTextNode.anchorPoint = CGPoint() + self.removeTextNode.displaysAsynchronously = false + self.removeTextNode.isUserInteractionEnabled = false + + super.init() + + self.addSubnode(self.contextContainer) + + self.contextContainer.addSubnode(self.clippingContainer) + self.clippingContainer.addSubnode(self.contentContainer) + + self.contextContainer.addSubnode(self.textContainer) + self.textContainer.addSubnode(self.textNode) + self.contextContainer.addSubnode(self.removeTextNode) + self.contextContainer.addSubnode(self.removeButtonNode) + + self.removeButtonNode.addTarget(self, action: #selector(self.removePressed), forControlEvents: [.touchUpInside]) + self.removeButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.removeTextNode.layer.removeAnimation(forKey: "opacity") + strongSelf.removeTextNode.alpha = 0.4 + } else { + strongSelf.removeTextNode.alpha = 1.0 + strongSelf.removeTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + + private var theme: PresentationTheme? + + func updateLayout(width: CGFloat, theme: PresentationTheme, strings: PresentationStrings, info: MessageFeeHeaderPanelComponent.Info, transition: ContainedViewLayoutTransition) -> CGFloat { + let leftInset: CGFloat = 0.0 + let rightInset: CGFloat = 0.0 + + if self.theme !== theme { + self.theme = theme + self.removeTextNode.attributedText = NSAttributedString(string: strings.Chat_PaidMessageFee_RemoveFee, font: Font.regular(17.0), textColor: theme.chat.inputPanel.panelControlColor) + } + + let attributedText = NSMutableAttributedString(string: strings.Chat_PaidMessageFee_Text(info.peer.compactDisplayTitle, "โญ๏ธ\(info.value)").string, font: Font.regular(12.0), textColor: theme.rootController.navigationBar.secondaryTextColor) + let range = (attributedText.string as NSString).range(of: "โญ๏ธ") + if range.location != NSNotFound { + attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: range) + attributedText.addAttribute(.baselineOffset, value: 0.0, range: range) + } + self.textNode.attributedText = attributedText + + self.textNode.visibility = true + self.textNode.arguments = TextNodeWithEntities.Arguments( + context: self.context, + cache: self.context.animationCache, + renderer: self.context.animationRenderer, + placeholderColor: UIColor(white: 1.0, alpha: 0.1), + attemptSynchronous: false + ) + + let sideInset = 12.0 + + let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - sideInset * 2.0, height: .greatestFiniteMagnitude)) + let textFrame = CGRect(origin: CGPoint(x: leftInset + floorToScreenPixels((width - leftInset - rightInset - textSize.width) / 2.0), y: 9.0), size: textSize) + + transition.updateFrame(node: self.textContainer, frame: textFrame) + + if self.textNode.bounds.size.width != 0.0, transition.isAnimated { + if let snapshotLayer = self.textNode.layer.snapshotContentTree() { + self.textContainer.layer.addSublayer(snapshotLayer) + snapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in + snapshotLayer?.removeFromSuperlayer() + }) + + self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + + transition.updatePosition(node: self.textNode, position: CGPoint()) + self.textNode.bounds = CGRect(origin: CGPoint(), size: textFrame.size) + + let panelHeight: CGFloat = 48.0 + textSize.height + + let removeSize = self.removeTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude)) + let removeFrame = CGRect(origin: CGPoint(x: leftInset + floorToScreenPixels((width - leftInset - rightInset - removeSize.width) / 2.0), y: panelHeight - removeSize.height - 9.0), size: removeSize) + transition.updatePosition(node: self.removeTextNode, position: removeFrame.origin) + self.removeTextNode.bounds = CGRect(origin: CGPoint(), size: removeFrame.size) + transition.updateFrame(node: self.removeButtonNode, frame: removeFrame.insetBy(dx: -8.0, dy: -4.0)) + + self.contextContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) + + self.clippingContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) + self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) + + self.currentLayout = (width, leftInset, rightInset) + + return panelHeight + } + + @objc func removePressed() { + self.removeFee() + } +} diff --git a/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/Sources/MessageFeeHeaderPanelComponent.swift b/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/Sources/MessageFeeHeaderPanelComponent.swift new file mode 100644 index 00000000..117e6456 --- /dev/null +++ b/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/Sources/MessageFeeHeaderPanelComponent.swift @@ -0,0 +1,113 @@ +import Foundation +import UIKit +import Display +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import AccountContext +import PresentationDataUtils +import TelegramCore + +public final class MessageFeeHeaderPanelComponent: Component { + public struct Info: Equatable { + public let value: Int64 + public let peer: EnginePeer + + public init(value: Int64, peer: EnginePeer) { + self.value = value + self.peer = peer + } + } + + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + public let info: Info + public let removeFee: () -> Void + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + info: Info, + removeFee: @escaping () -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.info = info + self.removeFee = removeFee + } + + public static func ==(lhs: MessageFeeHeaderPanelComponent, rhs: MessageFeeHeaderPanelComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.info != rhs.info { + return false + } + return true + } + + public final class View: UIView { + private var panel: ChatFeePanelNode? + + private var component: MessageFeeHeaderPanelComponent? + private weak var state: EmptyComponentState? + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func update(component: MessageFeeHeaderPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let panel: ChatFeePanelNode + if let current = self.panel { + panel = current + } else { + panel = ChatFeePanelNode( + context: component.context, + removeFee: component.removeFee + ) + self.panel = panel + self.addSubview(panel.view) + } + + let height = panel.updateLayout( + width: availableSize.width, + theme: component.theme, + strings: component.strings, + info: component.info, + transition: transition.containedViewLayoutTransition + ) + let size = CGSize(width: availableSize.width, height: height) + let panelFrame = CGRect(origin: CGPoint(), size: size) + transition.setFrame(view: panel.view, frame: panelFrame) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift b/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift index 579bd7b8..811661b6 100644 --- a/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift +++ b/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift @@ -266,8 +266,7 @@ final class MiniAppListScreenComponent: Component { } ))) : nil, rightButtons: rightButtons, - backTitle: isModal ? nil : strings.Common_Back, - backPressed: { [weak self] in + backPressed: isModal ? nil : { [weak self] in guard let self else { return } @@ -286,14 +285,15 @@ final class MiniAppListScreenComponent: Component { strings: strings, statusBarHeight: statusBarHeight, sideInset: insets.left, - isSearchActive: self.isSearchDisplayControllerActive, - isSearchEnabled: true, + search: ChatListNavigationBar.Search(isEnabled: true), + activeSearch: self.isSearchDisplayControllerActive ? ChatListNavigationBar.ActiveSearch(isExternal: false) : nil, primaryContent: headerContent, secondaryContent: nil, secondaryTransition: 0.0, storySubscriptions: nil, storiesIncludeHidden: false, uploadProgress: [:], + headerPanels: nil, tabsNode: nil, tabsNodeIsSearch: false, accessoryPanelContainer: nil, @@ -460,6 +460,7 @@ final class MiniAppListScreenComponent: Component { let searchBarTheme = SearchBarNodeTheme(theme: environment.theme, hasSeparator: false) searchBarNode = SearchBarNode( theme: searchBarTheme, + presentationTheme: environment.theme, strings: environment.strings, fieldStyle: .modern, displayBackground: false diff --git a/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift b/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift index e559c373..6879454e 100644 --- a/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift +++ b/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift @@ -52,7 +52,7 @@ public final class MoreHeaderButton: HighlightableButtonNode { strongSelf.contextAction?(strongSelf.containerNode, gesture) } - self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 30.0, height: 44.0)) + self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 44.0, height: 44.0)) self.referenceNode.frame = self.containerNode.bounds //self.iconNode.image = MoreHeaderButton.optionsCircleImage(color: color) @@ -68,6 +68,15 @@ public final class MoreHeaderButton: HighlightableButtonNode { @objc private func pressed() { self.onPressed?() } + + public func updateColor(color: UIColor) { + if self.color != color { + self.color = color + if let content = self.content { + self.setContent(content, animated: false) + } + } + } private var content: Content? public func setContent(_ content: Content, animated: Bool = false) { @@ -165,7 +174,7 @@ public final class MoreHeaderButton: HighlightableButtonNode { } override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - return CGSize(width: 22.0, height: 44.0) + return CGSize(width: 44.0, height: 44.0) } public func onLayout() { diff --git a/submodules/TelegramUI/Components/NavigationBarImpl/BUILD b/submodules/TelegramUI/Components/NavigationBarImpl/BUILD new file mode 100644 index 00000000..e7559917 --- /dev/null +++ b/submodules/TelegramUI/Components/NavigationBarImpl/BUILD @@ -0,0 +1,25 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "NavigationBarImpl", + module_name = "NavigationBarImpl", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/AppBundle", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift new file mode 100644 index 00000000..a0bee779 --- /dev/null +++ b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift @@ -0,0 +1,1241 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import GlassBackgroundComponent +import AsyncDisplayKit +import EdgeEffect +import ComponentDisplayAdapters + +public final class NavigationBarImpl: ASDisplayNode, NavigationBar { + public static var defaultSecondaryContentHeight: CGFloat { + return 38.0 + } + + public static let thinBackArrowImage = generateTintedImage(image: UIImage(bundleImageName: "Navigation/BackArrow"), color: .white)?.withRenderingMode(.alwaysTemplate) + + public static let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]) + + var presentationData: NavigationBarPresentationData + + private var validLayout: (size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool)? + private var requestedLayout: Bool = false + public var requestContainerLayout: ((ContainedViewLayoutTransition) -> Void)? + + public var backPressed: () -> () = { } + + public var userInfo: Any? + public var makeCustomTransitionNode: ((NavigationBar, Bool) -> CustomNavigationTransitionNode?)? + public var allowsCustomTransition: (() -> Bool)? + + public let stripeNode: ASDisplayNode + public let clippingNode: SparseNode + private let buttonsContainerNode: ASDisplayNode + + public private(set) var contentNode: NavigationBarContentNode? + public private(set) var secondaryContentNode: ASDisplayNode? + public var secondaryContentNodeDisplayFraction: CGFloat = 1.0 + + private var itemTitleListenerKey: Int? + private var itemTitleViewListenerKey: Int? + + private var itemLeftButtonListenerKey: Int? + private var itemLeftButtonSetEnabledListenerKey: Int? + + private var itemRightButtonListenerKey: Int? + private var itemRightButtonsListenerKey: Int? + + private var itemBadgeListenerKey: Int? + + private var hintAnimateTitleNodeOnNextLayout: Bool = false + + private var _item: UINavigationItem? + public var item: UINavigationItem? { + get { + return self._item + } set(value) { + if let previousValue = self._item { + if let itemTitleListenerKey = self.itemTitleListenerKey { + previousValue.removeSetTitleListener(itemTitleListenerKey) + self.itemTitleListenerKey = nil + } + + if let itemLeftButtonListenerKey = self.itemLeftButtonListenerKey { + previousValue.removeSetLeftBarButtonItemListener(itemLeftButtonListenerKey) + self.itemLeftButtonListenerKey = nil + } + + if let itemLeftButtonSetEnabledListenerKey = self.itemLeftButtonSetEnabledListenerKey { + previousValue.leftBarButtonItem?.removeSetEnabledListener(itemLeftButtonSetEnabledListenerKey) + self.itemLeftButtonSetEnabledListenerKey = nil + } + + if let itemRightButtonListenerKey = self.itemRightButtonListenerKey { + previousValue.removeSetRightBarButtonItemListener(itemRightButtonListenerKey) + self.itemRightButtonListenerKey = nil + } + + if let itemRightButtonsListenerKey = self.itemRightButtonsListenerKey { + previousValue.removeSetMultipleRightBarButtonItemsListener(itemRightButtonsListenerKey) + self.itemRightButtonsListenerKey = nil + } + + if let itemBadgeListenerKey = self.itemBadgeListenerKey { + previousValue.removeSetBadgeListener(itemBadgeListenerKey) + self.itemBadgeListenerKey = nil + } + } + self._item = value + + self.leftButtonNodeImpl.view.removeFromSuperview() + self.rightButtonNodeImpl.view.removeFromSuperview() + + if let item = value { + self.title = item.title + self.itemTitleListenerKey = item.addSetTitleListener { [weak self] text, animated in + if let strongSelf = self { + let animateIn = animated && (strongSelf.title?.isEmpty ?? true) + strongSelf.title = text + if animateIn { + strongSelf.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } + } + + let itemTitleView = item.titleView + if self.titleView !== itemTitleView { + if let oldTitleView = self.titleView as? NavigationBarTitleView { + oldTitleView.requestUpdate = nil + } + self.titleView = itemTitleView + if let titleView = self.titleView as? NavigationBarTitleView { + titleView.requestUpdate = { [weak self, weak titleView] transition in + guard let self, let titleView, self.titleView === titleView else { + return + } + if let requestContainerLayout = self.requestContainerLayout { + requestContainerLayout(transition) + } else { + self.requestLayout() + } + } + } + } + self.itemTitleViewListenerKey = item.addSetTitleViewListener { [weak self] itemTitleView in + guard let self else { + return + } + + if let oldTitleView = self.titleView as? NavigationBarTitleView { + oldTitleView.requestUpdate = nil + } + self.titleView = itemTitleView + if let titleView = self.titleView as? NavigationBarTitleView { + titleView.requestUpdate = { [weak self, weak titleView] transition in + guard let self, let titleView, self.titleView === titleView else { + return + } + if let requestContainerLayout = self.requestContainerLayout { + requestContainerLayout(transition) + } else { + self.requestLayout() + } + } + } + } + + self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] previousItem, _, animated in + if let strongSelf = self { + if let itemLeftButtonSetEnabledListenerKey = strongSelf.itemLeftButtonSetEnabledListenerKey { + previousItem?.removeSetEnabledListener(itemLeftButtonSetEnabledListenerKey) + strongSelf.itemLeftButtonSetEnabledListenerKey = nil + } + + strongSelf.updateLeftButton(animated: animated) + strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() + } + } + + self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] previousItem, currentItem, animated in + if let strongSelf = self { + strongSelf.updateRightButton(animated: animated) + strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() + } + } + + self.itemRightButtonsListenerKey = item.addSetMultipleRightBarButtonItemsListener { [weak self] items, animated in + if let strongSelf = self { + strongSelf.updateRightButton(animated: animated) + strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() + } + } + + self.itemBadgeListenerKey = item.addSetBadgeListener { [weak self] text in + if let strongSelf = self { + strongSelf.updateBadgeText(text: text) + } + } + self.updateBadgeText(text: item.badge) + + self.updateLeftButton(animated: false) + self.updateRightButton(animated: false) + } else { + self.title = nil + self.updateLeftButton(animated: false) + self.updateRightButton(animated: false) + } + self.invalidateCalculatedLayout() + self.requestLayout() + } + } + + public var customBackButtonText: String? + + private var title: String? { + didSet { + if let title = self.title { + self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBarImpl.titleFont, textColor: self.presentationData.theme.primaryTextColor) + self.titleNode.accessibilityLabel = title + if self.titleNode.supernode == nil { + self.buttonsContainerNode.addSubnode(self.titleNode) + } + } else { + self.titleNode.removeFromSupernode() + } + + self.updateAccessibilityElements() + self.invalidateCalculatedLayout() + self.requestLayout() + } + } + + public private(set) var titleView: UIView? { + didSet { + if let oldValue = oldValue { + oldValue.removeFromSuperview() + } + + if let titleView = self.titleView { + if let backgroundContainer = self.backgroundContainer { + backgroundContainer.contentView.addSubview(titleView) + } else { + self.buttonsContainerNode.view.addSubview(titleView) + } + } + + self.invalidateCalculatedLayout() + self.requestLayout() + } + } + + public var layoutSuspended: Bool = false + + private let titleNode: ImmediateTextNode + + var previousItemListenerKey: Int? + var previousItemBackListenerKey: Int? + + private func updateAccessibilityElements() { + } + + override public var accessibilityElements: [Any]? { + get { + var accessibilityElements: [Any] = [] + if self.backButtonNodeImpl.view.superview != nil { + addAccessibilityChildren(of: self.backButtonNodeImpl, container: self, to: &accessibilityElements) + } + if self.leftButtonNodeImpl.view.superview != nil { + addAccessibilityChildren(of: self.leftButtonNodeImpl, container: self, to: &accessibilityElements) + } + if self.titleNode.view.superview != nil { + addAccessibilityChildren(of: self.titleNode, container: self, to: &accessibilityElements) + accessibilityElements.append(self.titleNode) + } + if let titleView = self.titleView, titleView.superview != nil { + titleView.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(titleView.bounds, in: titleView) + accessibilityElements.append(titleView) + } + if self.rightButtonNodeImpl.supernode != nil { + addAccessibilityChildren(of: self.rightButtonNodeImpl, container: self, to: &accessibilityElements) + } + if let contentNode = self.contentNode { + addAccessibilityChildren(of: contentNode, container: self, to: &accessibilityElements) + } + if let secondaryContentNode = self.secondaryContentNode { + addAccessibilityChildren(of: secondaryContentNode, container: self, to: &accessibilityElements) + } + return accessibilityElements + } set(value) { + } + } + + override public func didLoad() { + super.didLoad() + + self.updateAccessibilityElements() + } + + public var enableAutomaticBackButton: Bool = true + + var _previousItem: NavigationPreviousAction? + public var previousItem: NavigationPreviousAction? { + get { + if !self.enableAutomaticBackButton { + return nil + } + return self._previousItem + } set(value) { + if !self.enableAutomaticBackButton { + self._previousItem = nil + return + } + if self._previousItem != value { + if let previousValue = self._previousItem, case let .item(itemValue) = previousValue { + if let previousItemListenerKey = self.previousItemListenerKey { + itemValue.removeSetTitleListener(previousItemListenerKey) + self.previousItemListenerKey = nil + } + if let previousItemBackListenerKey = self.previousItemBackListenerKey { + itemValue.removeSetBackBarButtonItemListener(previousItemBackListenerKey) + self.previousItemBackListenerKey = nil + } + } + self._previousItem = value + + if let previousItem = value { + switch previousItem { + case let .item(itemValue): + self.previousItemListenerKey = itemValue.addSetTitleListener { [weak self] _, _ in + if let strongSelf = self, let previousItem = strongSelf.previousItem, case let .item(itemValue) = previousItem { + if case .glass = strongSelf.presentationData.theme.style { + strongSelf.backButtonNodeImpl.updateManualText("", isBack: true) + } else if let customBackButtonText = strongSelf.customBackButtonText { + strongSelf.backButtonNodeImpl.updateManualText(customBackButtonText, isBack: true) + } else if let backBarButtonItem = itemValue.backBarButtonItem { + strongSelf.backButtonNodeImpl.updateManualText(backBarButtonItem.title ?? "", isBack: true) + } else { + strongSelf.backButtonNodeImpl.updateManualText(itemValue.title ?? "", isBack: true) + } + strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() + } + } + + self.previousItemBackListenerKey = itemValue.addSetBackBarButtonItemListener { [weak self] _, _, _ in + if let strongSelf = self, let previousItem = strongSelf.previousItem, case let .item(itemValue) = previousItem { + if case .glass = strongSelf.presentationData.theme.style { + strongSelf.backButtonNodeImpl.updateManualText("", isBack: true) + } else if let customBackButtonText = strongSelf.customBackButtonText { + strongSelf.backButtonNodeImpl.updateManualText(customBackButtonText, isBack: true) + } else if let backBarButtonItem = itemValue.backBarButtonItem { + strongSelf.backButtonNodeImpl.updateManualText(backBarButtonItem.title ?? "", isBack: true) + } else { + strongSelf.backButtonNodeImpl.updateManualText(itemValue.title ?? "", isBack: true) + } + strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() + } + } + case .close: + break + } + } + self.updateLeftButton(animated: false) + + self.invalidateCalculatedLayout() + self.requestLayout() + } + } + } + + private func updateBadgeText(text: String?) { + let actualText = text ?? "" + if self.badgeNode.text != actualText { + self.badgeNode.text = actualText + self.badgeNode.isHidden = actualText.isEmpty + self.backButtonNodeImpl.manualAlpha = self.badgeNode.isHidden ? 1.0 : 0.0 + + self.invalidateCalculatedLayout() + self.requestLayout() + } + } + + private func updateLeftButton(animated: Bool) { + if let item = self.item { + var needsLeftButton = false + if let leftBarButtonItem = item.leftBarButtonItem, !leftBarButtonItem.backButtonAppearance { + needsLeftButton = true + } else if let previousItem = self.previousItem, case .close = previousItem { + needsLeftButton = true + } + + if needsLeftButton { + if animated { + if self.backButtonNodeImpl.view.superview != nil { + if let snapshotView = self.backButtonNodeImpl.view.snapshotContentTree() { + snapshotView.frame = self.backButtonNodeImpl.frame + self.backButtonNodeImpl.view.superview?.insertSubview(snapshotView, aboveSubview: self.backButtonNodeImpl.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + + if self.backButtonArrow.view.superview != nil { + if let snapshotView = self.backButtonArrow.view.snapshotContentTree() { + snapshotView.frame = self.backButtonArrow.frame + self.backButtonArrow.view.superview?.insertSubview(snapshotView, aboveSubview: self.backButtonArrow.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + + if self.badgeNode.view.superview != nil { + if let snapshotView = self.badgeNode.view.snapshotContentTree() { + snapshotView.frame = self.badgeNode.frame + self.badgeNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.badgeNode.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + } + + self.backButtonNodeImpl.view.removeFromSuperview() + self.backButtonArrow.view.removeFromSuperview() + self.badgeNode.view.removeFromSuperview() + + if let leftBarButtonItem = item.leftBarButtonItem { + self.leftButtonNodeImpl.updateItems([], animated: animated) + self.leftButtonNodeImpl.updateItems([leftBarButtonItem], animated: animated) + } else { + self.leftButtonNodeImpl.updateItems([], animated: animated) + self.leftButtonNodeImpl.updateItems([UIBarButtonItem(title: "___close", style: .plain, target: nil, action: nil)], animated: animated) + } + + if self.leftButtonNodeImpl.supernode == nil { + if let leftButtonsBackgroundView = self.leftButtonsBackgroundView { + leftButtonsBackgroundView.container.addSubview(self.leftButtonNodeImpl.view) + } else { + self.buttonsContainerNode.view.addSubview(self.leftButtonNodeImpl.view) + } + } + + if animated { + self.leftButtonNodeImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } else { + if animated, self.leftButtonNodeImpl.view.superview != nil { + if let snapshotView = self.leftButtonNodeImpl.view.snapshotContentTree() { + snapshotView.frame = self.leftButtonNodeImpl.frame + self.leftButtonNodeImpl.view.superview?.insertSubview(snapshotView, aboveSubview: self.leftButtonNodeImpl.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + self.leftButtonNodeImpl.view.removeFromSuperview() + + var backTitle: String? + if case .glass = self.presentationData.theme.style { + backTitle = "" + } else if let customBackButtonText = self.customBackButtonText { + backTitle = customBackButtonText + } else if let leftBarButtonItem = item.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { + backTitle = leftBarButtonItem.title + } else if let previousItem = self.previousItem { + switch previousItem { + case let .item(itemValue): + if let backBarButtonItem = itemValue.backBarButtonItem { + backTitle = backBarButtonItem.title ?? self.presentationData.strings.back + } else { + backTitle = itemValue.title ?? self.presentationData.strings.back + } + case .close: + backTitle = nil + } + } + + if let backTitle { + self.backButtonNodeImpl.updateManualText(backTitle, isBack: true) + if self.backButtonNodeImpl.supernode == nil { + if let leftButtonsBackgroundView = self.leftButtonsBackgroundView { + leftButtonsBackgroundView.container.addSubview(self.backButtonNodeImpl.view) + leftButtonsBackgroundView.container.addSubview(self.backButtonArrow.view) + leftButtonsBackgroundView.container.addSubview(self.badgeNode.view) + } else { + self.buttonsContainerNode.view.addSubview(self.backButtonNodeImpl.view) + self.buttonsContainerNode.view.addSubview(self.backButtonArrow.view) + self.buttonsContainerNode.view.addSubview(self.badgeNode.view) + } + } + + if animated { + self.backButtonNodeImpl.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + self.backButtonArrow.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + self.badgeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } else { + self.backButtonNodeImpl.view.removeFromSuperview() + } + } + } else { + self.leftButtonNodeImpl.view.removeFromSuperview() + self.backButtonNodeImpl.view.removeFromSuperview() + self.backButtonArrow.view.removeFromSuperview() + self.badgeNode.view.removeFromSuperview() + } + + self.updateAccessibilityElements() + } + + private func updateRightButton(animated: Bool) { + if let item = self.item { + var items: [UIBarButtonItem] = [] + if let rightBarButtonItems = item.rightBarButtonItems, !rightBarButtonItems.isEmpty { + items = rightBarButtonItems + } else if let rightBarButtonItem = item.rightBarButtonItem { + items = [rightBarButtonItem] + } + + self.rightButtonNodeUpdated = true + + if !items.isEmpty { + if self.rightButtonNodeImpl.isEmpty { + self.rightButtonNodeImpl.updateItems(items, animated: false) + } else { + self.rightButtonNodeImpl.updateItems([], animated: animated) + self.rightButtonNodeImpl.updateItems(items, animated: animated) + } + if self.rightButtonNodeImpl.view.superview == nil { + if let rightButtonsBackgroundView = self.rightButtonsBackgroundView { + rightButtonsBackgroundView.container.addSubview(self.rightButtonNodeImpl.view) + } else { + self.buttonsContainerNode.view.addSubview(self.rightButtonNodeImpl.view) + } + } + } else { + if animated, self.rightButtonNodeImpl.view.superview != nil { + if let snapshotView = self.rightButtonNodeImpl.view.snapshotContentTree() { + snapshotView.frame = self.rightButtonNodeImpl.frame + self.rightButtonNodeImpl.view.superview?.insertSubview(snapshotView, aboveSubview: self.rightButtonNodeImpl.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + self.rightButtonNodeImpl.view.removeFromSuperview() + self.rightButtonNodeImpl.updateItems([], animated: false) + } + } else { + if animated, self.rightButtonNodeImpl.view.superview != nil { + if let snapshotView = self.rightButtonNodeImpl.view.snapshotContentTree() { + snapshotView.frame = self.rightButtonNodeImpl.frame + self.rightButtonNodeImpl.view.superview?.insertSubview(snapshotView, aboveSubview: self.rightButtonNodeImpl.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + self.rightButtonNodeImpl.view.removeFromSuperview() + self.rightButtonNodeImpl.updateItems([], animated: false) + } + + self.updateAccessibilityElements() + } + + public let backgroundNode: NavigationBackgroundNode + + private var leftButtonsBackgroundView: (background: GlassBackgroundView, container: UIView)? + private var rightButtonsBackgroundView: (background: GlassBackgroundView, container: UIView)? + + private let backButtonNodeImpl: NavigationButtonNodeImpl + public var backButtonNode: NavigationButtonNode { + return self.backButtonNodeImpl + } + public let badgeNode: NavigationBarBadgeNode + public let backButtonArrow: ASImageNode + private let leftButtonNodeImpl: NavigationButtonNodeImpl + public var leftButtonNode: NavigationButtonNode { + return self.leftButtonNodeImpl + } + private let rightButtonNodeImpl: NavigationButtonNodeImpl + public var rightButtonNode: NavigationButtonNode { + return self.rightButtonNodeImpl + } + private var rightButtonNodeUpdated: Bool = false + public let additionalContentNode: SparseNode + + public func reattachAdditionalContentNode() { + if self.additionalContentNode.supernode !== self { + self.insertSubnode(self.additionalContentNode, aboveSubnode: self.clippingNode) + } + } + + public var secondaryContentHeight: CGFloat + + private var edgeEffectExtension: CGFloat = 0.0 + private var edgeEffectView: EdgeEffectView? + private var backgroundContainer: GlassBackgroundContainerView? + + public var backgroundView: UIView { + if let edgeEffectView = self.edgeEffectView { + return edgeEffectView + } else { + return self.backgroundNode.view + } + } + + public let customOverBackgroundContentView: UIView + + public init(presentationData: NavigationBarPresentationData) { + self.presentationData = presentationData + self.stripeNode = ASDisplayNode() + + self.titleNode = ImmediateTextNode() + self.titleNode.isAccessibilityElement = true + self.titleNode.accessibilityTraits = .header + + self.backButtonNodeImpl = NavigationButtonNodeImpl(isGlass: presentationData.theme.style == .glass) + if case .glass = presentationData.theme.style { + } else { + self.backButtonNodeImpl.hitTestSlop = UIEdgeInsets(top: 0.0, left: -20.0, bottom: 0.0, right: 0.0) + } + + self.badgeNode = NavigationBarBadgeNode(fillColor: self.presentationData.theme.buttonColor, strokeColor: self.presentationData.theme.buttonColor, textColor: self.presentationData.theme.badgeTextColor) + self.badgeNode.isUserInteractionEnabled = false + self.badgeNode.isHidden = true + self.backButtonArrow = ASImageNode() + self.backButtonArrow.displayWithoutProcessing = true + self.backButtonArrow.displaysAsynchronously = false + self.backButtonArrow.isUserInteractionEnabled = false + self.leftButtonNodeImpl = NavigationButtonNodeImpl(isGlass: presentationData.theme.style == .glass) + self.rightButtonNodeImpl = NavigationButtonNodeImpl(isGlass: presentationData.theme.style == .glass) + if case .glass = presentationData.theme.style { + } else { + self.rightButtonNodeImpl.hitTestSlop = UIEdgeInsets(top: -4.0, left: -4.0, bottom: -4.0, right: -10.0) + } + + self.clippingNode = SparseNode() + if case .glass = presentationData.theme.style { + } else { + self.clippingNode.clipsToBounds = true + } + + self.buttonsContainerNode = SparseNode() + self.buttonsContainerNode.clipsToBounds = true + + self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor + self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor + self.leftButtonNodeImpl.color = self.presentationData.theme.buttonColor + self.leftButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor + self.rightButtonNodeImpl.color = self.presentationData.theme.buttonColor + self.rightButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor + self.backButtonArrow.image = presentationData.theme.style == .glass ? generateTintedImage(image: glassBackArrowImage, color: self.presentationData.theme.buttonColor) : navigationBarBackArrowImage(color: self.presentationData.theme.buttonColor) + if let title = self.title { + self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBarImpl.titleFont, textColor: self.presentationData.theme.primaryTextColor) + self.titleNode.accessibilityLabel = title + } + self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor + + self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.backgroundColor, enableBlur: self.presentationData.theme.enableBackgroundBlur) + self.additionalContentNode = SparseNode() + + self.secondaryContentHeight = NavigationBarImpl.defaultSecondaryContentHeight + + self.customOverBackgroundContentView = SparseContainerView() + + super.init() + + if case .glass = presentationData.theme.style { + let edgeEffectView = EdgeEffectView() + self.edgeEffectView = edgeEffectView + self.view.addSubview(edgeEffectView) + + let backgroundContainer = GlassBackgroundContainerView() + self.backgroundContainer = backgroundContainer + self.view.addSubview(backgroundContainer) + + backgroundContainer.contentView.addSubview(self.customOverBackgroundContentView) + + let leftButtonsBackgroundView: (background: GlassBackgroundView, container: UIView) = (GlassBackgroundView(), UIView()) + leftButtonsBackgroundView.background.contentView.addSubview(leftButtonsBackgroundView.container) + self.leftButtonsBackgroundView = leftButtonsBackgroundView + backgroundContainer.contentView.addSubview(leftButtonsBackgroundView.background) + + let rightButtonsBackgroundView: (background: GlassBackgroundView, container: UIView) = (GlassBackgroundView(), UIView()) + rightButtonsBackgroundView.background.contentView.addSubview(rightButtonsBackgroundView.container) + self.rightButtonsBackgroundView = rightButtonsBackgroundView + backgroundContainer.contentView.addSubview(rightButtonsBackgroundView.background) + } else { + self.addSubnode(self.backgroundNode) + self.view.addSubview(self.customOverBackgroundContentView) + } + self.addSubnode(self.buttonsContainerNode) + self.addSubnode(self.clippingNode) + self.addSubnode(self.additionalContentNode) + + self.stripeNode.isLayerBacked = true + self.stripeNode.displaysAsynchronously = false + if case .legacy = presentationData.theme.style { + self.addSubnode(self.stripeNode) + } + + self.backgroundColor = nil + self.isOpaque = false + + self.titleNode.displaysAsynchronously = false + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.truncationType = .end + self.titleNode.isOpaque = false + + self.backButtonNodeImpl.highlightChanged = { [weak self] index, highlighted in + if let strongSelf = self, index == 0 { + strongSelf.backButtonArrow.alpha = (highlighted ? 0.4 : 1.0) + strongSelf.badgeNode.alpha = (highlighted ? 0.4 : 1.0) + } + } + self.backButtonNodeImpl.pressed = { [weak self] index in + if let strongSelf = self, index == 0 { + if let leftBarButtonItem = strongSelf.item?.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { + leftBarButtonItem.performActionOnTarget() + } else { + strongSelf.backPressed() + } + } + } + + self.leftButtonNodeImpl.pressed = { [weak self] index in + if let item = self?.item { + if index == 0 { + if let leftBarButtonItem = item.leftBarButtonItem { + leftBarButtonItem.performActionOnTarget() + } else if let previousItem = self?.previousItem, case .close = previousItem { + self?.backPressed() + } + } + } + } + + self.rightButtonNodeImpl.pressed = { [weak self] index in + if let item = self?.item { + if let rightBarButtonItems = item.rightBarButtonItems, !rightBarButtonItems.isEmpty { + if index < rightBarButtonItems.count { + rightBarButtonItems[index].performActionOnTarget() + } + } else if let rightBarButtonItem = item.rightBarButtonItem { + rightBarButtonItem.performActionOnTarget() + } + } + } + } + + public var isBackgroundVisible: Bool { + return self.backgroundNode.alpha == 1.0 + } + + public func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { + let alpha = max(0.0, min(1.0, alpha)) + transition.updateAlpha(node: self.backgroundNode, alpha: alpha, delay: 0.15) + transition.updateAlpha(node: self.stripeNode, alpha: alpha, delay: 0.15) + } + + public func updatePresentationData(_ presentationData: NavigationBarPresentationData, transition: ContainedViewLayoutTransition = .immediate) { + if presentationData.theme !== self.presentationData.theme || presentationData.strings !== self.presentationData.strings { + self.presentationData = presentationData + + self.backgroundNode.updateColor(color: self.presentationData.theme.backgroundColor, transition: transition) + + self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor + self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor + self.leftButtonNodeImpl.color = self.presentationData.theme.buttonColor + self.leftButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor + self.rightButtonNodeImpl.color = self.presentationData.theme.buttonColor + self.rightButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor + self.backButtonArrow.image = self.presentationData.theme.style == .glass ? generateTintedImage(image: glassBackArrowImage, color: self.presentationData.theme.buttonColor) : navigationBarBackArrowImage(color: self.presentationData.theme.buttonColor) + if let title = self.title { + self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBarImpl.titleFont, textColor: self.presentationData.theme.primaryTextColor) + self.titleNode.accessibilityLabel = title + } + self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor + + self.badgeNode.updateTheme(fillColor: self.presentationData.theme.buttonColor, strokeColor: self.presentationData.theme.buttonColor, textColor: self.presentationData.theme.badgeTextColor) + + self.updateLeftButton(animated: false) + self.requestLayout() + } + } + + private func requestLayout() { + self.requestedLayout = true + self.setNeedsLayout() + } + + override public func layout() { + super.layout() + + if let validLayout = self.validLayout, self.requestedLayout { + self.requestedLayout = false + self.updateLayout(size: validLayout.size, defaultHeight: validLayout.defaultHeight, additionalTopHeight: validLayout.additionalTopHeight, additionalContentHeight: validLayout.additionalContentHeight, additionalBackgroundHeight: validLayout.additionalBackgroundHeight, additionalCutout: validLayout.additionalCutout, leftInset: validLayout.leftInset, rightInset: validLayout.rightInset, appearsHidden: validLayout.appearsHidden, isLandscape: validLayout.isLandscape, transition: .immediate) + } + } + + public func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool, transition: ContainedViewLayoutTransition) { + if self.layoutSuspended { + return + } + + self.validLayout = (size, defaultHeight, additionalTopHeight, additionalContentHeight, additionalBackgroundHeight, additionalCutout, leftInset, rightInset, appearsHidden, isLandscape) + + var contentVerticalOrigin = additionalTopHeight + if case .glass = self.presentationData.theme.style { + contentVerticalOrigin += 2.0 + } + + let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + additionalBackgroundHeight)) + if self.backgroundNode.frame != backgroundFrame { + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + self.backgroundNode.update(size: backgroundFrame.size, transition: transition) + } + + if let backgroundContainer = self.backgroundContainer { + var backgroundContainerFrame = backgroundFrame + backgroundContainerFrame.size.height += 44.0 + transition.updateFrame(view: backgroundContainer, frame: backgroundContainerFrame) + backgroundContainer.update(size: backgroundContainerFrame.size, isDark: self.presentationData.theme.overallDarkAppearance, transition: ComponentTransition(transition)) + } + + if let edgeEffectView = self.edgeEffectView { + if let edgeEffectColor = self.presentationData.theme.edgeEffectColor, edgeEffectColor.alpha == 0.0 { + edgeEffectView.isHidden = true + } else { + edgeEffectView.isHidden = false + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: size.width, height: size.height + additionalBackgroundHeight + 20.0 + 20.0)) + transition.updatePosition(layer: edgeEffectView.layer, position: edgeEffectFrame.center) + transition.updateBounds(layer: edgeEffectView.layer, bounds: CGRect(origin: CGPoint(), size: edgeEffectFrame.size)) + edgeEffectView.update(content: self.presentationData.theme.edgeEffectColor ?? .white, blur: true, rect: CGRect(origin: CGPoint(), size: edgeEffectFrame.size), edge: .top, edgeSize: 50.0, transition: ComponentTransition(transition)) + } + } + + let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? (self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction) : 0.0 + + let leftButtonInset: CGFloat = leftInset + 16.0 + let backButtonInset: CGFloat = leftInset + 27.0 + + transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: self.additionalContentNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + additionalBackgroundHeight))) + transition.updateFrame(node: self.buttonsContainerNode, frame: CGRect(origin: CGPoint(), size: size)) + var expansionHeight: CGFloat = 0.0 + if let contentNode = self.contentNode { + var contentNodeFrame: CGRect + switch contentNode.mode { + case .replacement: + expansionHeight = contentNode.height - defaultHeight + if case .glass = self.presentationData.theme.style { + contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: contentVerticalOrigin), size: CGSize(width: size.width, height: contentNode.nominalHeight)) + } else { + contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - additionalContentHeight)) + } + + transition.updateFrame(node: contentNode, frame: contentNodeFrame) + let _ = contentNode.updateLayout(size: contentNodeFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + case .expansion: + expansionHeight = contentNode.height + + let additionalExpansionHeight: CGFloat = self.secondaryContentNode != nil && appearsHidden ? (self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction) : 0.0 + contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - (appearsHidden ? 0.0 : additionalContentHeight) - expansionHeight - apparentAdditionalHeight - additionalExpansionHeight), size: CGSize(width: size.width, height: expansionHeight)) + if appearsHidden { + contentNodeFrame.origin.y = size.height - contentNode.height + contentVerticalOrigin + } + if appearsHidden { + if self.secondaryContentNode != nil { + contentNodeFrame.origin.y += self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction + } + } + + transition.updateFrame(node: contentNode, frame: contentNodeFrame) + let _ = contentNode.updateLayout(size: contentNodeFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + } + } + + transition.updateFrame(node: self.stripeNode, frame: CGRect(x: (additionalCutout?.width ?? 0.0), y: size.height + additionalBackgroundHeight, width: size.width - (additionalCutout?.width ?? 0.0), height: UIScreenPixel)) + + let nominalHeight: CGFloat = 60.0 + + var leftTitleInset: CGFloat = leftInset + var rightTitleInset: CGFloat = rightInset + + var leftButtonsWidth: CGFloat = 0.0 + if self.backButtonNodeImpl.view.superview != nil { + let backButtonSize = self.backButtonNodeImpl.updateLayout(constrainedSize: CGSize(width: size.width, height: 44.0), isLandscape: isLandscape, isLeftAligned: true) + leftTitleInset = backButtonSize.width + backButtonInset + + if case .glass = self.presentationData.theme.style { + } else { + let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 + self.backButtonNodeImpl.hitTestSlop = UIEdgeInsets(top: -topHitTestSlop, left: -27.0, bottom: -topHitTestSlop, right: -8.0) + } + + do { + self.backButtonNodeImpl.alpha = 1.0 + if case .glass = self.presentationData.theme.style { + } else { + transition.updateFrame(node: self.backButtonNodeImpl, frame: CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize)) + } + + self.backButtonArrow.alpha = 1.0 + + let backButtonArrowFrame: CGRect + if case .glass = self.presentationData.theme.style { + let backButtonArrowSize = CGSize(width: 44.0, height: 44.0) + backButtonArrowFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backButtonArrowSize) + } else { + let backButtonArrowSize = CGSize(width: 13.0, height: 22.0) + backButtonArrowFrame = CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - backButtonArrowSize.height) / 2.0)), size: backButtonArrowSize) + } + leftButtonsWidth += backButtonArrowFrame.width + transition.updateFrame(node: self.backButtonArrow, frame: backButtonArrowFrame) + self.badgeNode.alpha = 1.0 + } + } else if self.leftButtonNodeImpl.view.superview != nil { + let leftButtonSize = self.leftButtonNodeImpl.updateLayout(constrainedSize: CGSize(width: size.width, height: 44.0), isLandscape: isLandscape, isLeftAligned: true) + leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0 + + var transition = transition + if self.leftButtonNodeImpl.frame.width.isZero { + transition = .immediate + } + + self.leftButtonNodeImpl.alpha = 1.0 + if case .glass = self.presentationData.theme.style { + transition.updateFrame(node: self.leftButtonNodeImpl, frame: CGRect(origin: CGPoint(x: leftButtonsWidth, y: floor((44.0 - leftButtonSize.height) / 2.0)), size: leftButtonSize)) + } else { + transition.updateFrame(node: self.leftButtonNodeImpl, frame: CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize)) + } + + if !self.leftButtonNodeImpl.isEmpty { + leftButtonsWidth += leftButtonSize.width + } + } + + let badgeSize = self.badgeNode.measure(CGSize(width: 200.0, height: 100.0)) + let backButtonArrowFrame = self.backButtonArrow.frame + if case .glass = self.presentationData.theme.style, self.badgeNode.view.superview != nil, !self.badgeNode.isHidden { + transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: CGPoint(x: leftButtonsWidth - 14.0, y: floor((44.0 - badgeSize.height) * 0.5)), size: badgeSize)) + leftButtonsWidth += badgeSize.width - 3.0 + } else { + transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 16.0, dy: 2.0), size: badgeSize)) + } + + var rightButtonsWidth: CGFloat = 0.0 + if self.rightButtonNodeImpl.view.superview != nil { + let rightButtonSize = self.rightButtonNodeImpl.updateLayout(constrainedSize: (CGSize(width: size.width, height: 44.0)), isLandscape: isLandscape, isLeftAligned: false) + if !self.rightButtonNodeImpl.isEmpty { + rightButtonsWidth += rightButtonSize.width + } + self.rightButtonNodeImpl.alpha = 1.0 + + var transition = transition + if self.rightButtonNodeImpl.frame.width.isZero || self.rightButtonNodeUpdated { + transition = .immediate + } + if case .glass = self.presentationData.theme.style { + transition.updateFrame(node: self.rightButtonNodeImpl, frame: CGRect(origin: CGPoint(x: 0.0, y: floor((44.0 - rightButtonSize.height) / 2.0)), size: rightButtonSize)) + } else { + rightTitleInset = rightButtonSize.width + leftButtonInset + 4.0 + transition.updateFrame(node: self.rightButtonNodeImpl, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize)) + } + } + self.rightButtonNodeUpdated = false + + if let leftButtonsBackgroundView = self.leftButtonsBackgroundView { + if leftButtonsWidth != 0.0 { + leftTitleInset = leftInset + 16.0 + leftButtonsWidth + 10.0 + } + + if self.backButtonNodeImpl.view.superview != nil { + transition.updateFrame(node: self.backButtonNodeImpl, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: leftButtonsWidth, height: 44.0))) + } + + let leftButtonsBackgroundFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: contentVerticalOrigin + floor((nominalHeight - 44.0) * 0.5)), size: CGSize(width: max(44.0, leftButtonsWidth), height: 44.0)) + var leftButtonsBackgroundTransition = ComponentTransition(transition) + if leftButtonsBackgroundView.background.alpha == 0.0 { + leftButtonsBackgroundTransition = .immediate + } + + leftButtonsBackgroundTransition.setPosition(view: leftButtonsBackgroundView.background, position: leftButtonsBackgroundFrame.center) + leftButtonsBackgroundTransition.setBounds(view: leftButtonsBackgroundView.background, bounds: CGRect(origin: CGPoint(), size: leftButtonsBackgroundFrame.size)) + leftButtonsBackgroundTransition.setFrame(view: leftButtonsBackgroundView.container, frame: CGRect(origin: CGPoint(), size: leftButtonsBackgroundFrame.size)) + ComponentTransition(transition).setAlpha(view: leftButtonsBackgroundView.background, alpha: leftButtonsWidth == 0.0 ? 0.0 : 1.0) + leftButtonsBackgroundView.background.update(size: leftButtonsBackgroundFrame.size, cornerRadius: leftButtonsBackgroundFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, isVisible: leftButtonsWidth != 0.0, transition: leftButtonsBackgroundTransition) + } + + if let rightButtonsBackgroundView = self.rightButtonsBackgroundView { + if rightButtonsWidth != 0.0 { + rightTitleInset = rightInset + 16.0 + rightButtonsWidth + 10.0 + + let rightButtonsBackgroundFrame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - rightButtonsWidth, y: contentVerticalOrigin + floor((nominalHeight - 44.0) * 0.5)), size: CGSize(width: rightButtonsWidth, height: 44.0)) + var rightButtonsBackgroundTransition = ComponentTransition(transition) + if rightButtonsBackgroundView.background.isHidden { + rightButtonsBackgroundTransition = .immediate + } + rightButtonsBackgroundView.container.layer.cornerRadius = 44.0 * 0.5 + + rightButtonsBackgroundTransition.setFrame(view: rightButtonsBackgroundView.background, frame: rightButtonsBackgroundFrame) + + if rightButtonsBackgroundView.container.bounds.size != rightButtonsBackgroundFrame.size { + rightButtonsBackgroundView.container.clipsToBounds = true + let rightButtonsBackgroundViewContainer = rightButtonsBackgroundView.container + rightButtonsBackgroundTransition.setFrame(view: rightButtonsBackgroundView.container, frame: CGRect(origin: CGPoint(), size: rightButtonsBackgroundFrame.size), completion: { [weak rightButtonsBackgroundViewContainer] flag in + if flag, let rightButtonsBackgroundViewContainer { + rightButtonsBackgroundViewContainer.clipsToBounds = false + } + }) + } + + rightButtonsBackgroundView.background.isHidden = false + rightButtonsBackgroundView.background.update(size: rightButtonsBackgroundFrame.size, cornerRadius: rightButtonsBackgroundFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: rightButtonsBackgroundTransition) + } else { + rightButtonsBackgroundView.background.isHidden = true + } + } + + if self.titleNode.view.superview != nil { + let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)) + + do { + var transition = transition + if self.titleNode.frame.width.isZero { + transition = .immediate + } + self.titleNode.alpha = 1.0 + + let titleOffset: CGFloat = 0.0 + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + titleOffset + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)) + } + } + + if let titleView = self.titleView { + if let titleView = titleView as? NavigationBarTitleView { + var titleViewTransition = transition + if titleView.frame.isEmpty { + titleViewTransition = .immediate + } + + let titleSize = titleView.updateLayout(availableSize: CGSize(width: size.width - leftTitleInset - rightTitleInset, height: nominalHeight), transition: titleViewTransition) + + var titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) * 0.5), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + if titleFrame.origin.x < leftTitleInset { + titleFrame.origin.x = leftTitleInset + floorToScreenPixels((size.width - leftTitleInset - rightTitleInset - titleFrame.width) * 0.5) + } + + titleViewTransition.updateFrame(view: titleView, frame: titleFrame) + } else { + let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight) + let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + var titleViewTransition = transition + if titleView.frame.isEmpty { + titleViewTransition = .immediate + titleView.frame = titleFrame + } + + titleViewTransition.updateFrame(view: titleView, frame: titleFrame) + } + } + } + + public func updateEdgeEffectExtension(value: CGFloat, transition: ContainedViewLayoutTransition) { + if self.edgeEffectExtension == value { + return + } + self.edgeEffectExtension = value + self.applyEdgeEffectExtension(transition: transition) + } + + private func applyEdgeEffectExtension(transition: ContainedViewLayoutTransition) { + if let edgeEffectView = self.edgeEffectView { + transition.updateTransform(layer: edgeEffectView.layer, transform: CATransform3DMakeTranslation(0.0, max(0.0, min(20.0, self.edgeEffectExtension)), 0.0)) + } + } + + public var intrinsicCanTransitionInline: Bool = true + + public var passthroughTouches = true + + public var canTransitionInline: Bool { + if let contentNode = self.contentNode, case .replacement = contentNode.mode { + return false + } else { + return self.intrinsicCanTransitionInline + } + } + + public func contentHeight(defaultHeight: CGFloat) -> CGFloat { + var result: CGFloat = 0.0 + if let contentNode = self.contentNode { + switch contentNode.mode { + case .expansion: + result += defaultHeight + contentNode.height + case .replacement: + result += contentNode.height + } + } else { + result += defaultHeight + } + + if let _ = self.secondaryContentNode { + result += self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction + } + + return result + } + + public func setContentNode(_ contentNode: NavigationBarContentNode?, animated: Bool) { + let transition: ComponentTransition = animated ? .easeInOut(duration: 0.2) : .immediate + + if self.contentNode !== contentNode { + if let previous = self.contentNode { + if animated { + previous.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self, weak previous] _ in + if let strongSelf = self, let previous = previous { + if previous !== strongSelf.contentNode { + previous.removeFromSupernode() + } + } + }) + } else { + previous.removeFromSupernode() + } + } + self.contentNode = contentNode + self.contentNode?.requestContainerLayout = { [weak self] transition in + guard let self else { + return + } + self.requestContainerLayout?(transition) + } + if let contentNode { + contentNode.layer.removeAnimation(forKey: "opacity") + if case .glass = self.presentationData.theme.style { + self.addSubnode(contentNode) + } else { + contentNode.clipsToBounds = true + if self.stripeNode.supernode != nil { + self.insertSubnode(contentNode, belowSubnode: self.stripeNode) + } else { + self.insertSubnode(contentNode, at: 0) + } + } + if animated { + contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + if case .replacement = contentNode.mode { + if !self.buttonsContainerNode.alpha.isZero { + self.buttonsContainerNode.alpha = 0.0 + if animated { + self.buttonsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + } + } + if let backgroundContainer = self.backgroundContainer, !backgroundContainer.alpha.isZero { + transition.setAlpha(view: backgroundContainer, alpha: 0.0) + } + } + + if !self.bounds.size.width.isZero { + self.requestedLayout = true + self.layout() + } else { + self.requestLayout() + } + } else { + if self.buttonsContainerNode.alpha.isZero { + self.buttonsContainerNode.alpha = 1.0 + if animated { + self.buttonsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + if let backgroundContainer = self.backgroundContainer, backgroundContainer.alpha.isZero { + transition.setAlpha(view: backgroundContainer, alpha: 1.0) + } + } + } + } + + public func setSecondaryContentNode(_ secondaryContentNode: ASDisplayNode?, animated: Bool = false) { + if self.secondaryContentNode !== secondaryContentNode { + if let previous = self.secondaryContentNode, previous.supernode === self.clippingNode { + if animated { + previous.layer.animateAlpha(from: previous.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previous] finished in + if finished { + previous?.removeFromSupernode() + previous?.layer.removeAllAnimations() + } + }) + } else { + previous.removeFromSupernode() + } + } + self.secondaryContentNode = secondaryContentNode + if let secondaryContentNode = secondaryContentNode { + self.clippingNode.addSubnode(secondaryContentNode) + + if animated { + secondaryContentNode.layer.animateAlpha(from: 0.0, to: secondaryContentNode.alpha, duration: 0.3) + } + } + } + } + + public func executeBack() -> Bool { + if self.backButtonNodeImpl.isInHierarchy { + self.backButtonNodeImpl.pressed(0) + } else if self.leftButtonNodeImpl.isInHierarchy { + self.leftButtonNodeImpl.pressed(0) + } else { + self.backButtonNodeImpl.pressed(0) + } + return true + } + + public func setHidden(_ hidden: Bool, animated: Bool) { + if let contentNode = self.contentNode, case .replacement = contentNode.mode { + } else { + let targetAlpha: CGFloat = hidden ? 0.0 : 1.0 + let previousAlpha = self.buttonsContainerNode.alpha + if previousAlpha != targetAlpha { + self.buttonsContainerNode.alpha = targetAlpha + if animated { + self.buttonsContainerNode.layer.animateAlpha(from: previousAlpha, to: targetAlpha, duration: 0.2) + } + } + } + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let result = self.additionalContentNode.view.hitTest(self.view.convert(point, to: self.additionalContentNode.view), with: event) { + return result + } + + guard let result = super.hitTest(point, with: event) else { + if !self.bounds.contains(point) { + if let result = self.customOverBackgroundContentView.hitTest(self.view.convert(point, to: self.customOverBackgroundContentView), with: event) { + if result !== self.backgroundContainer?.contentView { + return result + } + } + } + return nil + } + + if self.passthroughTouches && (result == self.view || result == self.buttonsContainerNode.view || result == self.backgroundNode.view || result == self.backgroundNode.backgroundView || result == self.backgroundContainer?.contentView) { + return nil + } + + return result + } +} diff --git a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift new file mode 100644 index 00000000..0446f196 --- /dev/null +++ b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift @@ -0,0 +1,794 @@ +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import MultilineTextComponent +import AppBundle + +let glassBackArrowImage: UIImage? = { + let imageSize = CGSize(width: 44.0, height: 44.0) + let topRightPoint = CGPoint(x: 24.6, y: 14.0) + let centerPoint = CGPoint(x: 17.0, y: imageSize.height * 0.5) + return generateImage(imageSize, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(UIColor.white.cgColor) + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setLineJoin(.round) + context.move(to: topRightPoint) + context.addLine(to: centerPoint) + context.addLine(to: CGPoint(x: topRightPoint.x, y: size.height - topRightPoint.y)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) +}() + +let glassCloseImage: UIImage? = { + return generateTintedImage(image: UIImage(bundleImageName: "Navigation/Close"), color: .white)?.withRenderingMode(.alwaysTemplate) +}() + +private final class ItemComponent: Component { + enum Content: Equatable { + case back + case item(UIBarButtonItem) + } + + let color: UIColor + let content: Content + + init( + color: UIColor, + content: Content + ) { + self.color = color + self.content = content + } + + static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool { + if lhs.color != rhs.color { + return false + } + if lhs.content != rhs.content { + return false + } + return true + } + + final class View: UIView { + private var iconView: UIImageView? + private var title: ComponentView? + + private var component: ItemComponent? + private weak var state: EmptyComponentState? + var isUpdating: Bool = false + + private var setEnabledListener: Int? + private var setTitleListener: Int? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + if component.content != self.component?.content { + if case let .item(item) = self.component?.content { + if let setEnabledListener = self.setEnabledListener { + self.setEnabledListener = nil + item.removeSetEnabledListener(setEnabledListener) + } + if let setTitleListener = self.setTitleListener { + self.setTitleListener = nil + item.removeSetTitleListener(setTitleListener) + } + } + + switch component.content { + case .back: + break + case let .item(item): + self.setEnabledListener = item.addSetEnabledListener { [weak self] _ in + guard let self else { + return + } + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + } + self.setTitleListener = item.addSetTitleListener { [weak self] _ in + guard let self else { + return + } + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + } + } + } + + self.component = component + self.state = state + + var iconImage: UIImage? + var titleString: String? + switch component.content { + case .back: + iconImage = glassBackArrowImage + case let .item(item): + if item.image != nil { + iconImage = item.image + } else if let title = item.title { + if title == "___close" { + iconImage = glassCloseImage + } else { + titleString = title + } + } + } + + var size = CGSize(width: 44.0, height: 44.0) + + if let iconImage { + let iconView: UIImageView + var iconTransition = transition + if let current = self.iconView { + iconView = current + } else { + iconTransition = iconTransition.withAnimation(.none) + iconView = UIImageView() + self.iconView = iconView + } + iconView.image = iconImage + iconView.tintColor = component.color + + let iconFrame = iconImage.size.centered(in: CGRect(origin: CGPoint(), size: size)) + iconTransition.setFrame(view: iconView, frame: iconFrame) + } else if let iconView = self.iconView { + self.iconView = nil + iconView.removeFromSuperview() + } + + if let titleString { + let titleFont: UIFont + if case let .item(item) = component.content, case .done = item.style { + titleFont = Font.bold(17.0) + } else { + titleFont = Font.medium(17.0) + } + + let title: ComponentView + if let current = self.title { + title = current + } else { + title = ComponentView() + self.title = title + } + let titleSize = title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: titleString, font: titleFont, textColor: component.color)) + )), + environment: {}, + containerSize: CGSize(width: 200.0, height: 100.0) + ) + + let titleInset: CGFloat = 6.0 + size.width = titleInset * 2.0 + titleSize.width + + let titleFrame = CGRect(origin: CGPoint(x: titleInset, y: floorToScreenPixels((size.height - titleSize.height) * 0.5)), size: titleSize) + if let titleView = title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + titleView.frame = titleFrame + } + } else if let title = self.title { + self.title = nil + if let titleView = title.view { + titleView.removeFromSuperview() + } + } + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class NavigationButtonItemNode: ImmediateTextNode { + private let isGlass: Bool + + private func fontForCurrentState() -> UIFont { + return self.bold ? Font.semibold(17.0) : Font.medium(17.0) + } + + private func attributesForCurrentState() -> [NSAttributedString.Key: AnyObject] { + return [ + NSAttributedString.Key.font: self.fontForCurrentState(), + NSAttributedString.Key.foregroundColor: self.isEnabled ? self.color : self.disabledColor + ] + } + + private var setEnabledListener: Int? + + var item: UIBarButtonItem? { + didSet { + if self.item !== oldValue { + if let oldValue = oldValue, let setEnabledListener = self.setEnabledListener { + oldValue.removeSetEnabledListener(setEnabledListener) + self.setEnabledListener = nil + } + + if let item = self.item { + self.setEnabledListener = item.addSetEnabledListener { [weak self] value in + self?.isEnabled = value + } + self.accessibilityHint = item.accessibilityHint + self.accessibilityLabel = item.accessibilityLabel + } + } + } + } + + private var _text: String? + public var text: String { + get { + return _text ?? "" + } + set(value) { + _text = value + + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + if _image == nil { + if self.item?.accessibilityLabel == nil { + self.item?.accessibilityLabel = value + } + } + } + } + + private(set) var imageNode: ASImageNode? + + private var _image: UIImage? + public var image: UIImage? { + get { + return _image + } set(value) { + _image = value + + if let _ = value { + if self.imageNode == nil { + let imageNode = ASImageNode() + imageNode.displayWithoutProcessing = true + imageNode.displaysAsynchronously = false + self.imageNode = imageNode + + self.addSubnode(imageNode) + } + self.imageNode?.image = image + if self.imageNode?.image?.renderingMode == .alwaysTemplate { + self.imageNode?.tintColor = self.color + } else { + self.imageNode?.tintColor = nil + } + } else if let imageNode = self.imageNode { + imageNode.removeFromSupernode() + self.imageNode = nil + } + + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } + + public var node: ASDisplayNode? { + didSet { + if self.node !== oldValue { + oldValue?.removeFromSupernode() + if let node = self.node { + self.addSubnode(node) + self.invalidateCalculatedLayout() + self.setNeedsLayout() + self.updatePointerInteraction() + } + } + } + } + + public var color: UIColor = UIColor(rgb: 0x0088ff) { + didSet { + if self.imageNode?.image?.renderingMode == .alwaysTemplate { + self.imageNode?.tintColor = self.color + } + if let text = self._text { + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + } + + public var disabledColor: UIColor = UIColor(rgb: 0xd0d0d0) { + didSet { + if let text = self._text { + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + } + + private var _bold: Bool = false + public var bold: Bool { + get { + return _bold + } + set(value) { + if _bold != value { + _bold = value + + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + } + + private var touchCount = 0 + public var pressed: () -> () = { } + public var highlightChanged: (Bool) -> () = { _ in } + + override public var isAccessibilityElement: Bool { + get { + return true + } set(value) { + super.isAccessibilityElement = true + } + } + + override public var accessibilityLabel: String? { + get { + if let item = self.item, let accessibilityLabel = item.accessibilityLabel { + return accessibilityLabel + } else { + return self.attributedText?.string + } + } set(value) { + + } + } + + override public var accessibilityHint: String? { + get { + if let item = self.item, let accessibilityHint = item.accessibilityHint { + return accessibilityHint + } else { + return nil + } + } set(value) { + + } + } + + var pointerInteraction: PointerInteraction? + + init(isGlass: Bool) { + self.isGlass = isGlass + + super.init() + + self.isAccessibilityElement = true + + self.isUserInteractionEnabled = true + self.isExclusiveTouch = true + if !isGlass { + self.hitTestSlop = UIEdgeInsets(top: -16.0, left: -10.0, bottom: -16.0, right: -10.0) + } + self.displaysAsynchronously = false + + self.verticalAlignment = .middle + + self.accessibilityTraits = .button + } + + override func didLoad() { + super.didLoad() + self.updatePointerInteraction() + } + + func updatePointerInteraction() { + let pointerStyle: PointerStyle + if self.node != nil { + pointerStyle = .lift + } else { + pointerStyle = .insetRectangle(-8.0, 2.0) + } + self.pointerInteraction = PointerInteraction(node: self, style: pointerStyle) + } + + override func updateLayout(_ constrainedSize: CGSize) -> CGSize { + var superSize = super.updateLayout(constrainedSize) + + if let node = self.node { + let nodeSize = node.measure(constrainedSize) + let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) + node.frame = CGRect(origin: CGPoint(), size: nodeSize) + return size + } else if let imageNode = self.imageNode { + let nodeSize = imageNode.image?.size ?? CGSize() + let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(44.0, max(nodeSize.height, superSize.height))) + let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - nodeSize.width) / 2.0), y: floorToScreenPixels((size.height - nodeSize.height) / 2.0)), size: nodeSize) + imageNode.frame = imageFrame + return size + } else { + superSize.height = max(44.0, superSize.height) + } + return superSize + } + + private func touchInsideApparentBounds(_ touch: UITouch) -> Bool { + var apparentBounds = self.bounds + let hitTestSlop = self.hitTestSlop + apparentBounds.origin.x += hitTestSlop.left + apparentBounds.size.width += -hitTestSlop.left - hitTestSlop.right + apparentBounds.origin.y += hitTestSlop.top + apparentBounds.size.height += -hitTestSlop.top - hitTestSlop.bottom + + return apparentBounds.contains(touch.location(in: self.view)) + } + + public override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + self.touchCount += touches.count + self.updateHighlightedState(true, animated: false) + } + + public override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesMoved(touches, with: event) + } + + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + self.updateHighlightedState(false, animated: false) + + let previousTouchCount = self.touchCount + self.touchCount = max(0, self.touchCount - touches.count) + + var touchInside = true + if let touch = touches.first, !self.isGlass { + touchInside = self.touchInsideApparentBounds(touch) + } + if previousTouchCount != 0 && self.touchCount == 0 && self.isEnabled && touchInside { + self.pressed() + } + } + + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let node = self.node as? HighlightableButtonNode { + let result = node.view.hitTest(self.view.convert(point, to: node.view), with: event) + return result + } else { + let previousAlpha = self.alpha + self.alpha = 1.0 + let result = super.hitTest(point, with: event) + self.alpha = previousAlpha + return result + } + } + + public override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + + self.touchCount = max(0, self.touchCount - (touches?.count ?? 0)) + self.updateHighlightedState(false, animated: false) + } + + private var _highlighted = false + private func updateHighlightedState(_ highlighted: Bool, animated: Bool) { + if _highlighted != highlighted { + _highlighted = highlighted + + var shouldChangeHighlight = true + if let node = self.node as? NavigationButtonCustomDisplayNode { + shouldChangeHighlight = node.isHighlightable + } + + if shouldChangeHighlight { + if self.alpha > 0.0 { + self.alpha = !self.isEnabled ? 1.0 : (highlighted ? 0.4 : 1.0) + } + self.highlightChanged(highlighted) + } + } + } + + 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) + } + } + } + } +} + + +public final class NavigationButtonNodeImpl: ContextControllerSourceNode, NavigationButtonNode { + private let isGlass: Bool + private var isBack: Bool = false + + private var nodes: [NavigationButtonItemNode] = [] + + private var disappearingNodes: [(frame: CGRect, size: CGSize, node: NavigationButtonItemNode)] = [] + + public var singleCustomNode: ASDisplayNode? { + for node in self.nodes { + return node.node + } + return nil + } + + public var mainContentNode: ASDisplayNode? { + return self.nodes.first + } + + public var pressed: (Int) -> () = { _ in } + public var highlightChanged: (Int, Bool) -> () = { _, _ in } + + public var color: UIColor = UIColor(rgb: 0x0088ff) { + didSet { + if !self.color.isEqual(oldValue) { + for node in self.nodes { + node.color = self.color + } + } + } + } + + public var disabledColor: UIColor = UIColor(rgb: 0xd0d0d0) { + didSet { + if !self.disabledColor.isEqual(oldValue) { + for node in self.nodes { + node.disabledColor = self.disabledColor + } + } + } + } + + override public var accessibilityElements: [Any]? { + get { + return self.nodes + } set(value) { + } + } + + public init(isGlass: Bool) { + self.isGlass = isGlass + + super.init() + + self.isAccessibilityElement = false + self.isGestureEnabled = false + } + + public var manualText: String { + return self.nodes.first?.text ?? "" + } + + public var manualAlpha: CGFloat = 1.0 { + didSet { + for node in self.nodes { + node.alpha = self.manualAlpha + } + } + } + + public var contentsColor: UIColor? + + public func updateManualAlpha(alpha: CGFloat, transition: ContainedViewLayoutTransition) { + for node in self.nodes { + transition.updateAlpha(node: node, alpha: alpha) + } + } + + public func updateManualText(_ text: String, isBack: Bool = true) { + self.isBack = isBack + + let node: NavigationButtonItemNode + if self.nodes.count > 0 { + node = self.nodes[0] + } else { + node = NavigationButtonItemNode(isGlass: self.isGlass) + node.color = self.color + node.layer.layerTintColor = self.contentsColor?.cgColor + node.highlightChanged = { [weak node, weak self] value in + if let strongSelf = self, let node = node { + if let index = strongSelf.nodes.firstIndex(where: { $0 === node }) { + strongSelf.highlightChanged(index, value) + } + } + } + node.pressed = { [weak self, weak node] in + if let strongSelf = self, let node = node { + if let index = strongSelf.nodes.firstIndex(where: { $0 === node }) { + strongSelf.pressed(index) + } + } + } + self.nodes.append(node) + self.addSubnode(node) + } + node.alpha = self.manualAlpha + node.item = nil + node.image = nil + node.text = text + node.bold = false + node.isEnabled = true + node.node = nil + if !self.isGlass { + node.hitTestSlop = isBack ? UIEdgeInsets(top: 0.0, left: -20.0, bottom: 0.0, right: 0.0) : UIEdgeInsets() + } + + if 1 < self.nodes.count { + for i in 1 ..< self.nodes.count { + self.nodes[i].removeFromSupernode() + } + self.nodes.removeSubrange(1...) + } + } + + public func updateItems(_ items: [UIBarButtonItem], animated: Bool) { + for i in 0 ..< items.count { + let node: NavigationButtonItemNode + if self.nodes.count > i { + node = self.nodes[i] + } else { + node = NavigationButtonItemNode(isGlass: self.isGlass) + node.color = self.color + node.layer.layerTintColor = self.contentsColor?.cgColor + node.highlightChanged = { [weak node, weak self] value in + if let strongSelf = self, let node = node { + if let index = strongSelf.nodes.firstIndex(where: { $0 === node }) { + strongSelf.highlightChanged(index, value) + } + } + } + node.pressed = { [weak self, weak node] in + if let strongSelf = self, let node = node { + if let index = strongSelf.nodes.firstIndex(where: { $0 === node }) { + strongSelf.pressed(index) + } + } + } + self.nodes.append(node) + self.addSubnode(node) + } + node.alpha = self.manualAlpha + node.item = items[i] + if items[i].title == "___close" { + //node.image = glassCloseImage + node.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/Close"), color: self.color) + } else { + node.image = items[i].image + node.text = items[i].title ?? "" + } + node.bold = items[i].style == .done + node.isEnabled = items[i].isEnabled + node.node = items[i].customDisplayNode + + if animated { + node.layer.animateAlpha(from: 0.0, to: self.manualAlpha, duration: 0.16) + node.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) + } + } + if items.count < self.nodes.count { + for i in items.count ..< self.nodes.count { + let itemNode = self.nodes[i] + if animated { + disappearingNodes.append((itemNode.frame, self.bounds.size, itemNode)) + itemNode.layer.animateAlpha(from: self.manualAlpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self, weak itemNode] _ in + guard let itemNode else { + return + } + + itemNode.removeFromSupernode() + + guard let self else { + return + } + if let index = self.disappearingNodes.firstIndex(where: { $0.node === itemNode }) { + self.disappearingNodes.remove(at: index) + } + }) + itemNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + } else { + itemNode.removeFromSupernode() + } + } + self.nodes.removeSubrange(items.count...) + } + } + + public func updateLayout(constrainedSize: CGSize, isLandscape: Bool, isLeftAligned: Bool) -> CGSize { + var nodeOrigin = CGPoint(x: 0.0, y: 0.0) + var totalHeight: CGFloat = 0.0 + for i in 0 ..< self.nodes.count { + if i != 0 && !self.isGlass { + nodeOrigin.x += 15.0 + } + + let node = self.nodes[i] + + var nodeSize = node.updateLayout(constrainedSize) + var nodeInset: CGFloat = 0.0 + if self.isGlass { + if node.image == nil && node.node == nil { + nodeInset += 12.0 + } + if nodeSize.width + nodeInset * 2.0 < 44.0 { + nodeInset = floorToScreenPixels((44.0 - nodeSize.width) * 0.5) + } + } + + nodeSize.width = ceil(nodeSize.width) + nodeSize.height = ceil(nodeSize.height) + totalHeight = max(totalHeight, nodeSize.height) + node.frame = CGRect(origin: CGPoint(x: nodeOrigin.x + nodeInset, y: floor((totalHeight - nodeSize.height) / 2.0)), size: nodeSize) + nodeOrigin.x += nodeInset + node.bounds.width + nodeInset + if isLandscape && !self.isGlass { + nodeOrigin.x += 16.0 + } + + if !self.isGlass && node.node == nil && node.imageNode != nil && i == self.nodes.count - 1 { + nodeOrigin.x -= 5.0 + } + } + + /*if !isLeftAligned { + for disappearingNode in self.disappearingNodes { + disappearingNode.node.frame = disappearingNode.frame.offsetBy(dx: nodeOrigin.x - disappearingNode.size.width, dy: (totalHeight - disappearingNode.size.height) * 0.5) + } + }*/ + + return CGSize(width: nodeOrigin.x, height: totalHeight) + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.nodes.count == 1 { + if self.isGlass && self.isBack { + if self.bounds.contains(point) { + return self.nodes[0].view + } + } + if self.bounds.contains(point) { + return self.nodes[0].view.hitTest(self.view.convert(point, to: self.nodes[0].view), with: event) + } else { + return nil + } + } else { + return super.hitTest(point, with: event) + } + } + + var isEmpty: Bool { + if self.isBack { + return false + } + for node in self.nodes { + if node.bounds.width != 0.0 { + return false + } + } + return true + } +} diff --git a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift index 38b2e173..c9fcea3e 100644 --- a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift +++ b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift @@ -1095,7 +1095,7 @@ public func notificationPeerExceptionController( let presentationData = context.sharedContext.currentPresentationData.with { $0 } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.PeerInfo_DeleteToneTitle, text: presentationData.strings.PeerInfo_DeleteToneText(title).string, actions: [ + controller.present(textAlertController(context: context, title: presentationData.strings.PeerInfo_DeleteToneTitle, text: presentationData.strings.PeerInfo_DeleteToneText(title).string, actions: [ TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: { updateState { state in var state = state diff --git a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift index 0842e3d3..a432b7ce 100644 --- a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift +++ b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift @@ -24,6 +24,7 @@ import PremiumLockButtonSubtitleComponent import ListSectionComponent import ListItemSliderSelectorComponent import ListSwitchItemComponent +import PresentationDataUtils final class PeerAllowedReactionsScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -202,7 +203,7 @@ final class PeerAllowedReactionsScreenComponent: Component { self.applySettings(standalone: true) } else { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChannelReactions_UnsavedChangesAlertTitle, text: presentationData.strings.ChannelReactions_UnsavedChangesAlertText, actions: [ + self.environment?.controller()?.present(textAlertController(context: component.context, title: presentationData.strings.ChannelReactions_UnsavedChangesAlertTitle, text: presentationData.strings.ChannelReactions_UnsavedChangesAlertText, actions: [ TextAlertAction(type: .genericAction, title: presentationData.strings.ChannelReactions_UnsavedChangesAlertDiscard, action: { [weak self] in guard let self else { return @@ -306,7 +307,7 @@ final class PeerAllowedReactionsScreenComponent: Component { self.displayPremiumScreen(reactionCount: customReactions.count) case .generic: let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } }, completed: { [weak self] in diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD index 7f66e412..f1e7a53a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD @@ -34,6 +34,8 @@ swift_library( "//submodules/TelegramUI/Components/Stories/PeerListItemComponent", "//submodules/ContextUI", "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertTableComponent", + "//submodules/TelegramUI/Components/Gifts/TableComponent", "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramUI/Components/ToastComponent", "//submodules/AvatarNode", diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift index 0940ba23..73d83595 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift @@ -26,6 +26,9 @@ import ContextUI import BalancedTextComponent import AlertComponent import PremiumCoinComponent +import AlertComponent +import AlertTableComponent +import TableComponent private func textForTimeout(value: Int32) -> String { if value < 3600 { @@ -64,13 +67,16 @@ final class AffiliateProgramSetupScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let initialContent: AffiliateProgramSetupScreen.Content init( context: AccountContext, + overNavigationContainer: UIView, initialContent: AffiliateProgramSetupScreen.Content ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.initialContent = initialContent } @@ -93,6 +99,7 @@ final class AffiliateProgramSetupScreenComponent: Component { private let coinIcon = ComponentView() private let title = ComponentView() + private let titleContainer: UIView private let titleTransformContainer: UIView private var titleNeutralScale: CGFloat = 1.0 private let subtitle = ComponentView() @@ -157,6 +164,8 @@ final class AffiliateProgramSetupScreenComponent: Component { self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.alwaysBounceVertical = true + self.titleContainer = SparseContainerView() + self.titleTransformContainer = UIView() self.titleTransformContainer.isUserInteractionEnabled = false @@ -208,30 +217,50 @@ final class AffiliateProgramSetupScreenComponent: Component { } else { durationTitle = environment.strings.AffiliateProgram_DurationLifetime } + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: environment.strings.AffiliateSetup_AlertApply_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(environment.strings.AffiliateSetup_AlertApply_Text)) + ) + )) - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) - self.environment?.controller()?.present(tableAlert( - theme: presentationData.theme, - title: environment.strings.AffiliateSetup_AlertApply_Title, - text: environment.strings.AffiliateSetup_AlertApply_Text, - table: TableComponent(theme: environment.theme, items: [ - TableComponent.Item(id: 0, title: environment.strings.AffiliateSetup_AlertApply_SectionCommission, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: commissionTitle, font: Font.regular(17.0), textColor: environment.theme.actionSheet.primaryTextColor)) - ))), - TableComponent.Item(id: 1, title: environment.strings.AffiliateSetup_AlertApply_SectionDuration, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: durationTitle, font: Font.regular(17.0), textColor: environment.theme.actionSheet.primaryTextColor)) - ))) - ]), + let tableItems: [TableComponent.Item] = [ + TableComponent.Item(id: 0, title: environment.strings.AffiliateSetup_AlertApply_SectionCommission, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: commissionTitle, font: Font.regular(15.0), textColor: environment.theme.actionSheet.primaryTextColor)) + ))), + TableComponent.Item(id: 1, title: environment.strings.AffiliateSetup_AlertApply_SectionDuration, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: durationTitle, font: Font.regular(15.0), textColor: environment.theme.actionSheet.primaryTextColor)) + ))) + ] + content.append(AnyComponentWithIdentity( + id: "table", + component: AnyComponent( + AlertTableComponent(items: tableItems) + ) + )) + + let alertController = AlertScreen( + context: component.context, + content: content, actions: [ - ComponentAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), - ComponentAlertAction(type: .defaultAction, title: environment.strings.AffiliateSetup_AlertApply_Action, action: { [weak self] in + .init(title: environment.strings.Common_Cancel), + .init(title: environment.strings.AffiliateSetup_AlertApply_Action, type: .default, action: { [weak self] in guard let self else { return } self.applyProgram() }) ] - ), in: .window(.root)) + ) + self.environment?.controller()?.present(alertController, in: .window(.root)) } private func requestApplyEndProgram() { @@ -239,8 +268,8 @@ final class AffiliateProgramSetupScreenComponent: Component { return } let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) - self.environment?.controller()?.present(standardTextAlertController( - theme: AlertControllerTheme(presentationData: presentationData), + self.environment?.controller()?.present(textAlertController( + context: component.context, title: environment.strings.AffiliateSetup_AlertTerminate_Title, text: environment.strings.AffiliateSetup_AlertTerminate_Text, actions: [ @@ -337,6 +366,8 @@ final class AffiliateProgramSetupScreenComponent: Component { transition.setSublayerTransform(view: self.titleTransformContainer, transform: CATransform3DMakeTranslation(0.0, titleY - self.titleTransformContainer.center.y, 0.0)) + transition.setSublayerTransform(view: self.titleContainer, transform: CATransform3DMakeTranslation(0.0, -self.scrollView.contentOffset.y, 0.0)) + let titleYDistance: CGFloat = titleY - titleCenterY let titleTransformFraction: CGFloat = 1.0 - max(0.0, min(1.0, titleYDistance / titleTransformDistance)) let titleMinScale: CGFloat = 17.0 / 30.0 @@ -625,6 +656,10 @@ final class AffiliateProgramSetupScreenComponent: Component { self.component = component self.state = state + if self.titleContainer.superview == nil { + component.overNavigationContainer.addSubview(self.titleContainer) + } + let topInset: CGFloat = environment.navigationHeight + 90.0 let bottomInset: CGFloat = 8.0 let sideInset: CGFloat = 16.0 + environment.safeInsets.left @@ -648,7 +683,7 @@ final class AffiliateProgramSetupScreenComponent: Component { let coinIconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - coinIconSize.width) * 0.5), y: contentHeight - coinIconSize.height + 30.0), size: coinIconSize) if let coinIconView = self.coinIcon.view { if coinIconView.superview == nil { - self.scrollView.addSubview(coinIconView) + self.titleContainer.addSubview(coinIconView) } transition.setFrame(view: coinIconView, frame: coinIconFrame) } @@ -1158,6 +1193,7 @@ final class AffiliateProgramSetupScreenComponent: Component { transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: environment.theme.list.itemCheckColors.fillColor, foreground: environment.theme.list.itemCheckColors.foregroundColor, pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) @@ -1174,7 +1210,7 @@ final class AffiliateProgramSetupScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width - bottomPanelButtonInsets.left - bottomPanelButtonInsets.right, height: 50.0) + containerSize: CGSize(width: availableSize.width - bottomPanelButtonInsets.left - bottomPanelButtonInsets.right, height: 52.0) ) let bottomPanelHeight: CGFloat = bottomPanelButtonInsets.top + bottomPanelButtonSize.height + bottomPanelButtonInsets.bottom + bottomPanelTextSize.height + 8.0 + environment.safeInsets.bottom @@ -1620,16 +1656,21 @@ public class AffiliateProgramSetupScreen: ViewControllerComponentContainer { private let context: AccountContext private var isDismissed: Bool = false + private let overNavigationContainer: UIView + public init( context: AccountContext, initialContent: AffiliateProgramSetupScreenInitialData ) { self.context = context + self.overNavigationContainer = SparseContainerView() + let initialContent = initialContent as! AffiliateProgramSetupScreen.Content super.init(context: context, component: AffiliateProgramSetupScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, initialContent: initialContent ), navigationBarAppearance: .default, theme: .default) @@ -1647,6 +1688,10 @@ public class AffiliateProgramSetupScreen: ViewControllerComponentContainer { return componentView.attemptNavigation(complete: complete) } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift index d8d473f8..3df157c9 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift @@ -1130,11 +1130,11 @@ private final class JoinAffiliateProgramScreenComponent: Component { )), background: AnyComponent(FilledRoundedRectangleComponent( color: environment.theme.list.itemInputField.backgroundColor, - cornerRadius: .value(8.0), + cornerRadius: .minEdge, smoothCorners: true )), effectAlignment: .center, - minSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0), + minSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 52.0), contentInsets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0), action: { [weak self] in guard let self, case let .active(active) = self.currentMode else { @@ -1148,7 +1148,7 @@ private final class JoinAffiliateProgramScreenComponent: Component { animateContents: false )), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 52.0) ) let linkTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - linkTextSize.width) * 0.5), y: contentHeight), size: linkTextSize) if let linkTextView = self.linkText.view { @@ -1170,6 +1170,7 @@ private final class JoinAffiliateProgramScreenComponent: Component { actionButtonTitle = environment.strings.AffiliateProgram_ActionCopyLink } + let buttonSideInset: CGFloat = 30.0 let actionButtonSize = self.actionButton.update( transition: transition, component: AnyComponent(ButtonComponent( @@ -1208,7 +1209,7 @@ private final class JoinAffiliateProgramScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width - 30.0 * 2.0, height: 52.0) + containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0) ) let bottomTextSize = self.bottomText.update( @@ -1238,7 +1239,7 @@ private final class JoinAffiliateProgramScreenComponent: Component { let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight), size: CGSize(width: availableSize.width, height: bottomPanelHeight)) transition.setFrame(view: self.bottomPanelContainer, frame: bottomPanelFrame) - let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: actionButtonSize) + let actionButtonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: 0.0), size: actionButtonSize) if let actionButtonView = self.actionButton.view { if actionButtonView.superview == nil { self.bottomPanelContainer.addSubview(actionButtonView) diff --git a/submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent/Sources/CollectionTabItemComponent.swift b/submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent/Sources/CollectionTabItemComponent.swift index f4d9a8ba..f325d960 100644 --- a/submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent/Sources/CollectionTabItemComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent/Sources/CollectionTabItemComponent.swift @@ -63,8 +63,8 @@ public final class CollectionTabItemComponent: Component { let iconSpacing: CGFloat = 3.0 - let normalColor = component.theme.list.itemSecondaryTextColor - let selectedColor = component.theme.list.freeTextColor + let normalColor = component.theme.list.itemPrimaryTextColor + let selectedColor = component.theme.list.itemPrimaryTextColor let effectiveColor = normalColor.mixedWith(selectedColor, alpha: environment.selectionFraction) let titleSize = self.title.update( @@ -99,6 +99,7 @@ public final class CollectionTabItemComponent: Component { pointSize: iconSize, loopCount: 1 ) + iconLayer.isVisibleForAnimations = true self.layer.addSublayer(iconLayer) self.iconLayer = iconLayer } @@ -109,7 +110,7 @@ public final class CollectionTabItemComponent: Component { transition: .immediate, component: AnyComponent(BundleIconComponent( name: "Chat/Input/Media/PanelBadgeAdd", - tintColor: component.theme.list.itemSecondaryTextColor + tintColor: component.theme.list.itemPrimaryTextColor )), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) diff --git a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift index 02aa0262..2323498d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift @@ -245,7 +245,7 @@ private class MessagePriceItemNode: ListViewItemNode { self.button = ComponentView() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.leftTextNode) self.addSubnode(self.rightTextNode) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift index 32e80566..ba66c7c4 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift @@ -18,6 +18,7 @@ import PeerInfoPaneNode import ChatListUI import DeleteChatPeerActionSheetItem import UndoUI +import ComponentDisplayAdapters private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNodeNavigationContentNode { private struct Params: Equatable { @@ -58,15 +59,15 @@ private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNod func update(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) -> CGFloat { self.params = Params(width: width, defaultHeight: defaultHeight, insets: insets) - let size = CGSize(width: width, height: defaultHeight) - transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: size)) - self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) + let size = CGSize(width: width, height: 60.0) + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -6.0), size: size)) + let _ = self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) - var contentHeight: CGFloat = size.height + 10.0 + var contentHeight: CGFloat = size.height if self.appliedPanelNode !== self.panelNode { if let previous = self.appliedPanelNode { - transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in + ComponentTransition(transition).setAlpha(view: previous.view, alpha: 0.0, completion: { [weak previous] _ in previous?.removeFromSupernode() }) } @@ -79,9 +80,10 @@ private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNod let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: panelHeight)) panelNode.frame = panelFrame panelNode.alpha = 0.0 - transition.updateAlpha(node: panelNode, alpha: 1.0) + ComponentTransition(transition).setAlpha(view: panelNode.view, alpha: 1.0) - contentHeight += panelHeight - 1.0 + contentHeight += 14.0 + 66.0 + contentHeight += panelHeight } } else if let panelNode = self.panelNode, let chatController = self.chatController { let panelLayout = panelNode.updateLayout(width: width, leftInset: insets.left, rightInset: insets.right, transition: transition, chatController: chatController) @@ -89,7 +91,8 @@ private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNod let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: panelHeight)) transition.updateFrame(node: panelNode, frame: panelFrame) - contentHeight += panelHeight - 1.0 + contentHeight += 14.0 + 66.0 + contentHeight += panelHeight } return contentHeight @@ -256,11 +259,11 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, AS } else if let emptyShimmerEffectNode = self.emptyShimmerEffectNode { self.emptyShimmerEffectNode = nil let emptyNodeTransition = transition.isAnimated ? transition : .animated(duration: 0.3, curve: .easeInOut) - emptyNodeTransition.updateAlpha(node: emptyShimmerEffectNode, alpha: 0.0, completion: { [weak emptyShimmerEffectNode] _ in + ComponentTransition(emptyNodeTransition).setAlpha(view: emptyShimmerEffectNode.view, alpha: 0.0, completion: { [weak emptyShimmerEffectNode] _ in emptyShimmerEffectNode?.removeFromSupernode() }) self.chatListNode.alpha = 0.0 - emptyNodeTransition.updateAlpha(node: self.chatListNode, alpha: 1.0) + ComponentTransition(emptyNodeTransition).setAlpha(view: self.chatListNode.view, alpha: 1.0) } } @@ -443,7 +446,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, AS chatController.displayNode.layer.allowsGroupOpacity = true if transition.isAnimated { - ComponentTransition.easeInOut(duration: 0.2).setAlpha(layer: chatController.displayNode.layer, alpha: 1.0) + ComponentTransition.easeInOut(duration: 0.2).setAlpha(view: chatController.displayNode.view, alpha: 1.0) } if self.searchNavigationContentNode?.contentNode !== contentNode { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift index c3845d1c..f591cb18 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift @@ -54,7 +54,7 @@ private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNod let size = CGSize(width: width, height: defaultHeight) transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: size)) - self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) + let _ = self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) var contentHeight: CGFloat = size.height + 10.0 @@ -182,8 +182,6 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScro self.addSubnode(self.chatController.displayNode) self.chatController.displayNode.clipsToBounds = true - self.view.addSubview(self.coveringView) - self.chatController.stateUpdated = { [weak self] transition in guard let self else { return @@ -276,9 +274,9 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScro public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { self.currentParams = (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) - let fullHeight = navigationHeight + size.height + let fullHeight = size.height - topInset - let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: -navigationHeight), size: CGSize(width: size.width, height: fullHeight)) + let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: fullHeight)) if !self.chatController.displayNode.bounds.isEmpty { if let contextController = self.chatController.visibleContextController as? ContextController { @@ -293,7 +291,7 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScro let combinedBottomInset = bottomInset transition.updateFrame(node: self.chatController.displayNode, frame: chatFrame) self.chatController.updateIsScrollingLockedAtTop(isScrollingLockedAtTop: isScrollingLockedAtTop) - self.chatController.containerLayoutUpdated(ContainerViewLayout(size: chatFrame.size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: topInset + navigationHeight, left: sideInset, bottom: combinedBottomInset, right: sideInset), safeInsets: UIEdgeInsets(top: navigationHeight + topInset + 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) + self.chatController.containerLayoutUpdated(ContainerViewLayout(size: chatFrame.size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), safeInsets: UIEdgeInsets(top: 0.0 + 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index a7d4c39c..532cde91 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -168,6 +168,11 @@ swift_library( "//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist", "//submodules/TelegramUI/Components/TextFieldComponent", "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AvatarComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent", + "//submodules/TelegramUI/Components/HorizontalTabsComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift index bd56a1ee..a4c4781a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift @@ -308,7 +308,7 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode let labelSize = self.labelNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude)) - var topOffset = 10.0 + var topOffset = 15.0 let labelFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: labelSize) if labelSize.height > 0.0 { topOffset += labelSize.height @@ -632,7 +632,7 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode topOffset += dayHeights } - topOffset += 11.0 + topOffset += 15.0 transition.updateFrame(node: self.labelNode, frame: labelFrame) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCallListItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCallListItem.swift index dd685d04..f8577378 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCallListItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCallListItem.swift @@ -74,13 +74,15 @@ private final class PeerInfoScreenCallListItemNode: PeerInfoScreenItemNode { let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0) + let verticalInset: CGFloat = 8.0 + let itemNode: ItemListCallListItemNode if let current = self.itemNode { itemNode = current addressItem.updateNode(async: { $0() }, node: { return itemNode }, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in - let nodeFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: layout.size.height)) + let nodeFrame = CGRect(origin: CGPoint(x: 0.0, y: verticalInset), size: CGSize(width: width, height: layout.size.height)) itemNode.contentSize = layout.contentSize itemNode.insets = layout.insets @@ -100,9 +102,8 @@ private final class PeerInfoScreenCallListItemNode: PeerInfoScreenItemNode { self.addSubnode(itemNode) } - let verticalInset: CGFloat = 8.0 - let height = itemNode.contentSize.height + verticalInset * 2.0 + let height = itemNode.contentSize.height + verticalInset * 2.0 transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalInset), size: itemNode.bounds.size)) let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift index 815b7bfa..80f58c4f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift @@ -183,7 +183,6 @@ public final class LoadingOverlayNode: ASDisplayNode { }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { @@ -541,8 +540,6 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod }, openStarsTopup: { _ in }, - dismissNotice: { _ in - }, editPeer: { _ in }, openWebApp: { _ in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift index e5f376cf..45d59615 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift @@ -500,6 +500,7 @@ private func tagMaskForType(_ type: PeerInfoGifPaneNode.ContentType) -> MessageT private enum ItemsLayout { final class Grid { let containerWidth: CGFloat + let topInset: CGFloat let itemCount: Int let itemSpacing: CGFloat let itemsInRow: Int @@ -507,8 +508,9 @@ private enum ItemsLayout { let rowCount: Int let contentHeight: CGFloat - init(containerWidth: CGFloat, itemCount: Int, bottomInset: CGFloat) { + init(containerWidth: CGFloat, itemCount: Int, topInset: CGFloat, bottomInset: CGFloat) { self.containerWidth = containerWidth + self.topInset = topInset self.itemCount = itemCount self.itemSpacing = 1.0 self.itemsInRow = max(3, min(6, Int(containerWidth / 140.0))) @@ -516,13 +518,13 @@ private enum ItemsLayout { self.rowCount = itemCount / self.itemsInRow + (itemCount % self.itemsInRow == 0 ? 0 : 1) - self.contentHeight = CGFloat(self.rowCount + 1) * self.itemSpacing + CGFloat(rowCount) * itemSize + bottomInset + self.contentHeight = topInset + CGFloat(self.rowCount + 1) * self.itemSpacing + CGFloat(rowCount) * itemSize + bottomInset } func visibleRange(rect: CGRect) -> (Int, Int) { - var minVisibleRow = Int(floor((rect.minY - self.itemSpacing) / (self.itemSize + self.itemSpacing))) + var minVisibleRow = Int(floor((rect.minY - self.topInset - self.itemSpacing) / (self.itemSize + self.itemSpacing))) minVisibleRow = max(0, minVisibleRow) - var maxVisibleRow = Int(ceil((rect.maxY - self.itemSpacing) / (self.itemSize + itemSpacing))) + var maxVisibleRow = Int(ceil((rect.maxY - self.topInset - self.itemSpacing) / (self.itemSize + itemSpacing))) maxVisibleRow = min(self.rowCount - 1, maxVisibleRow) let minVisibleIndex = minVisibleRow * itemsInRow @@ -534,7 +536,7 @@ private enum ItemsLayout { func frame(forItemAt index: Int, sideInset: CGFloat) -> CGRect { let rowIndex = index / Int(self.itemsInRow) let columnIndex = index % Int(self.itemsInRow) - let itemOrigin = CGPoint(x: sideInset + CGFloat(columnIndex) * (self.itemSize + self.itemSpacing), y: self.itemSpacing + CGFloat(rowIndex) * (self.itemSize + self.itemSpacing)) + let itemOrigin = CGPoint(x: sideInset + CGFloat(columnIndex) * (self.itemSize + self.itemSpacing), y: self.topInset + self.itemSpacing + CGFloat(rowIndex) * (self.itemSize + self.itemSpacing)) return CGRect(origin: itemOrigin, size: CGSize(width: columnIndex == self.itemsInRow ? (self.containerWidth - itemOrigin.x) : self.itemSize, height: self.itemSize)) } } @@ -906,7 +908,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe let previousParams = self.currentParams self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: size.height - topInset))) + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) let availableWidth = size.width - sideInset * 2.0 @@ -916,7 +918,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe } else { switch self.contentType { case .photoOrVideo, .gifs: - itemsLayout = .grid(ItemsLayout.Grid(containerWidth: availableWidth, itemCount: self.mediaItems.count, bottomInset: bottomInset)) + itemsLayout = .grid(ItemsLayout.Grid(containerWidth: availableWidth, itemCount: self.mediaItems.count, topInset: topInset, bottomInset: bottomInset)) } self.itemsLayout = itemsLayout } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift index 41133017..2b4c09f8 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift @@ -80,7 +80,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { self.selectedMessages = chatControllerInteraction.selectionState.flatMap { $0.selectedIds } self.selectedMessagesPromise.set(.single(self.selectedMessages)) - self.listNode = context.sharedContext.makeChatHistoryListNode(context: context, updatedPresentationData: updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: .tag(tagMask), source: .default, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, reverseGroups: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false)) + self.listNode = context.sharedContext.makeChatHistoryListNode(context: context, updatedPresentationData: updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: .tag(tagMask), source: .default, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(reversed: false, reverseGroups: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false)) self.listNode.clipsToBounds = true self.listNode.defaultToSynchronousTransactionWhileScrolling = true self.listNode.scroller.bounces = false diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift index ef18776e..6582a26c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift @@ -166,6 +166,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable { actionIcon: .none, index: nil, header: nil, + hideBackground: true, action: member.peer.id == context.account.peerId ? nil : { _ in action(member, .open) }, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift index 3da02f5c..f42fd8cf 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift @@ -13,6 +13,23 @@ private enum MoreIconNodeState: Equatable { case moreToSearch(Float) } +private let glassBackArrowImage: UIImage? = { + let imageSize = CGSize(width: 44.0, height: 44.0) + let topRightPoint = CGPoint(x: 24.6, y: 14.0) + let centerPoint = CGPoint(x: 17.0, y: imageSize.height * 0.5) + return generateImage(imageSize, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(UIColor.white.cgColor) + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setLineJoin(.round) + context.move(to: topRightPoint) + context.addLine(to: centerPoint) + context.addLine(to: CGPoint(x: topRightPoint.x, y: size.height - topRightPoint.y)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) +}() + private final class MoreIconNode: ManagedAnimationNode { private let duration: Double = 0.21 private var iconState: MoreIconNodeState = .more @@ -124,14 +141,11 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { let contextSourceNode: ContextReferenceContentNode private let textNode: ImmediateTextNode private let iconNode: ASImageNode - private let backIconLayer: SimpleShapeLayer private var animationNode: MoreIconNode? - private let backgroundNode: NavigationBackgroundNode private var key: PeerInfoHeaderNavigationButtonKey? private var contentsColor: UIColor = .white - private var canBeExpanded: Bool = false var action: ((ASDisplayNode, ContextGesture?) -> Void)? @@ -146,27 +160,14 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true - self.backIconLayer = SimpleShapeLayer() - self.backIconLayer.lineWidth = 3.0 - self.backIconLayer.lineCap = .round - self.backIconLayer.lineJoin = .round - self.backIconLayer.strokeColor = UIColor.white.cgColor - self.backIconLayer.fillColor = nil - self.backIconLayer.isHidden = true - self.backIconLayer.path = try? convertSvgPath("M10.5,2 L1.5,11 L10.5,20 ") - - self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true) - - super.init(pointerStyle: .insetRectangle(-8.0, 2.0)) + super.init(pointerStyle: .insetRectangle(0.0, 0.0)) self.isAccessibilityElement = true self.accessibilityTraits = .button self.containerNode.addSubnode(self.contextSourceNode) - self.contextSourceNode.addSubnode(self.backgroundNode) self.contextSourceNode.addSubnode(self.textNode) self.contextSourceNode.addSubnode(self.iconNode) - self.contextSourceNode.layer.addSublayer(self.backIconLayer) self.addSubnode(self.containerNode) @@ -178,10 +179,6 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { } self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) - - if isReduceTransparencyEnabled() { - self.backgroundNode.isHidden = true - } } @objc private func pressed() { @@ -190,11 +187,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - var boundingRect = self.bounds - if self.textNode.alpha != 0.0 { - boundingRect = boundingRect.union(self.textNode.frame) - } - boundingRect = boundingRect.insetBy(dx: -8.0, dy: -4.0) + let boundingRect = self.bounds if boundingRect.contains(point) { return super.hitTest(self.bounds.center, with: event) } else { @@ -202,31 +195,11 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { } } - func updateContentsColor(backgroundColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) { + func updateContentsColor(contentsColor: UIColor, transition: ContainedViewLayoutTransition) { self.contentsColor = contentsColor - self.canBeExpanded = canBeExpanded - - self.backgroundNode.updateColor(color: backgroundColor, transition: transition) transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) transition.updateTintColor(view: self.iconNode.view, color: self.contentsColor) - transition.updateStrokeColor(layer: self.backIconLayer, strokeColor: self.contentsColor) - - switch self.key { - case .back: - transition.updateAlpha(layer: self.textNode.layer, alpha: canBeExpanded ? 1.0 : 0.0) - transition.updateTransformScale(node: self.textNode, scale: canBeExpanded ? 1.0 : 0.001) - - var iconTransform = CATransform3DIdentity - iconTransform = CATransform3DScale(iconTransform, canBeExpanded ? 1.0 : 0.8, canBeExpanded ? 1.0 : 0.8, 1.0) - iconTransform = CATransform3DTranslate(iconTransform, canBeExpanded ? -7.0 : 0.0, 0.0, 0.0) - transition.updateTransform(node: self.iconNode, transform: CATransform3DGetAffineTransform(iconTransform)) - - transition.updateTransform(layer: self.backIconLayer, transform: CATransform3DGetAffineTransform(iconTransform)) - transition.updateLineWidth(layer: self.backIconLayer, lineWidth: canBeExpanded ? 3.0 : 2.075) - default: - break - } if let animationNode = self.animationNode { transition.updateTintColor(layer: animationNode.imageNode.layer, color: self.contentsColor) @@ -239,7 +212,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { var iconOffset = CGPoint() switch key { case .back: - iconOffset = CGPoint(x: -1.0, y: 0.0) + iconOffset = CGPoint(x: 0.0, y: 0.0) default: break } @@ -258,9 +231,9 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { var animationState: MoreIconNodeState = .more switch key { case .back: - text = presentationData.strings.Common_Back + text = "" accessibilityText = presentationData.strings.Common_Back - icon = NavigationBar.backArrowImage(color: .white) + icon = glassBackArrowImage case .edit: text = presentationData.strings.Common_Edit accessibilityText = text @@ -323,7 +296,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { self.accessibilityLabel = accessibilityText self.containerNode.isGestureEnabled = isGestureEnabled - let font: UIFont = isBold ? Font.semibold(17.0) : Font.regular(17.0) + let font: UIFont = isBold ? Font.semibold(17.0) : Font.medium(17.0) self.textNode.attributedText = NSAttributedString(string: text, font: font, textColor: .white) transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) @@ -357,70 +330,39 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { textSize = self.textNode.bounds.size } - let inset: CGFloat = 0.0 - var textInset: CGFloat = 0.0 - switch key { - case .back: - textInset += 11.0 - default: - break - } + let textInset: CGFloat = 12.0 let resultSize: CGSize - let textFrame = CGRect(origin: CGPoint(x: inset + textInset, y: floor((height - textSize.height) / 2.0)), size: textSize) + let textFrame = CGRect(origin: CGPoint(x: textInset, y: floor((height - textSize.height) / 2.0)), size: textSize) self.textNode.position = textFrame.center self.textNode.bounds = CGRect(origin: CGPoint(), size: textFrame.size) if let animationNode = self.animationNode { let animationSize = CGSize(width: 30.0, height: 30.0) - animationNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((height - animationSize.height) / 2.0)), size: animationSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y) + animationNode.frame = CGRect(origin: CGPoint(x: floor((height - animationSize.width) * 0.5), y: floor((height - animationSize.height) / 2.0)), size: animationSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y) - let size = CGSize(width: animationSize.width + inset * 2.0, height: height) + let size = CGSize(width: height, height: height) self.containerNode.frame = CGRect(origin: CGPoint(), size: size) self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) resultSize = size } else if let image = self.iconNode.image { - let iconFrame = CGRect(origin: CGPoint(x: inset, y: floor((height - image.size.height) / 2.0)), size: image.size).offsetBy(dx: iconOffset.x, dy: iconOffset.y) + let iconFrame = CGRect(origin: CGPoint(x: floor((height - image.size.width) * 0.5), y: floor((height - image.size.height) / 2.0)), size: image.size).offsetBy(dx: iconOffset.x, dy: iconOffset.y) self.iconNode.position = iconFrame.center self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) - if case .back = key { - self.backIconLayer.position = iconFrame.center - self.backIconLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) - - self.iconNode.isHidden = true - self.backIconLayer.isHidden = false - } else { - self.iconNode.isHidden = false - self.backIconLayer.isHidden = true - } - - let size = CGSize(width: image.size.width + inset * 2.0, height: height) + let size = CGSize(width: height, height: height) self.containerNode.frame = CGRect(origin: CGPoint(), size: size) self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) resultSize = size } else { - let size = CGSize(width: textSize.width + inset * 2.0, height: height) + let size = CGSize(width: textSize.width + textInset * 2.0, height: height) self.containerNode.frame = CGRect(origin: CGPoint(), size: size) self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size) resultSize = size } - let diameter: CGFloat = 32.0 - let backgroundWidth: CGFloat - if self.iconNode.image != nil || self.animationNode != nil { - backgroundWidth = diameter - } else { - backgroundWidth = max(diameter, resultSize.width + 12.0 * 2.0) - } - let backgroundFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - backgroundWidth) * 0.5), y: floor((resultSize.height - diameter) * 0.5)), size: CGSize(width: backgroundWidth, height: diameter)) - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - self.backgroundNode.update(size: backgroundFrame.size, cornerRadius: diameter * 0.5, transition: transition) - - self.hitTestSlop = UIEdgeInsets(top: -2.0, left: -12.0, bottom: -2.0, right: -12.0) - return resultSize } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift index a40ab2c8..07b6d169 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift @@ -4,6 +4,9 @@ import AsyncDisplayKit import ContextUI import TelegramPresentationData import Display +import ComponentFlow +import ComponentDisplayAdapters +import GlassBackgroundComponent enum PeerInfoHeaderNavigationButtonKey { case back @@ -24,101 +27,125 @@ enum PeerInfoHeaderNavigationButtonKey { case postStory } -struct PeerInfoHeaderNavigationButtonSpec: Equatable { +struct PeerInfoHeaderNavigationButtonSpec: Hashable { let key: PeerInfoHeaderNavigationButtonKey let isForExpandedView: Bool } final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { private var presentationData: PresentationData? - private(set) var leftButtonNodes: [PeerInfoHeaderNavigationButtonKey: PeerInfoHeaderNavigationButton] = [:] - private(set) var rightButtonNodes: [PeerInfoHeaderNavigationButtonKey: PeerInfoHeaderNavigationButton] = [:] + + private let backgroundContainer: GlassBackgroundContainerView + private let leftButtonsBackground: GlassBackgroundView + private let rightButtonsBackground: GlassBackgroundView + private let leftButtonsContainer: UIView + private let rightButtonsContainer: UIView + + private(set) var leftButtonNodes: [PeerInfoHeaderNavigationButtonSpec: PeerInfoHeaderNavigationButton] = [:] + private(set) var rightButtonNodes: [PeerInfoHeaderNavigationButtonSpec: PeerInfoHeaderNavigationButton] = [:] private var currentLeftButtons: [PeerInfoHeaderNavigationButtonSpec] = [] private var currentRightButtons: [PeerInfoHeaderNavigationButtonSpec] = [] private var backgroundContentColor: UIColor = .clear + private var isOverColoredContents: Bool = false private var contentsColor: UIColor = .white - private var canBeExpanded: Bool = false var performAction: ((PeerInfoHeaderNavigationButtonKey, ContextReferenceContentNode?, ContextGesture?) -> Void)? - func updateContentsColor(backgroundContentColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) { - self.backgroundContentColor = backgroundContentColor - self.contentsColor = contentsColor - self.canBeExpanded = canBeExpanded + override init() { + self.backgroundContainer = GlassBackgroundContainerView() + self.leftButtonsBackground = GlassBackgroundView() + self.rightButtonsBackground = GlassBackgroundView() - for (_, button) in self.leftButtonNodes { - button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition) - transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: canBeExpanded ? -8.0 : 0.0, y: 0.0)) + self.leftButtonsContainer = UIView() + self.leftButtonsContainer.clipsToBounds = true + self.rightButtonsContainer = UIView() + self.rightButtonsContainer.clipsToBounds = true + + super.init() + + self.view.addSubview(self.backgroundContainer) + self.backgroundContainer.contentView.addSubview(self.leftButtonsBackground) + self.backgroundContainer.contentView.addSubview(self.rightButtonsBackground) + + self.leftButtonsBackground.contentView.addSubview(self.leftButtonsContainer) + self.rightButtonsBackground.contentView.addSubview(self.rightButtonsContainer) + } + + func updateContentsColor(backgroundContentColor: UIColor, contentsColor: UIColor, isOverColoredContents: Bool, transition: ContainedViewLayoutTransition) { + self.backgroundContentColor = backgroundContentColor + self.isOverColoredContents = isOverColoredContents + self.contentsColor = contentsColor + + guard let presentationData = self.presentationData else { + return } - var accumulatedRightButtonOffset: CGFloat = canBeExpanded ? 16.0 : 0.0 - for spec in self.currentRightButtons.reversed() { - guard let button = self.rightButtonNodes[spec.key] else { + let normalButtonContentsColor: UIColor = self.isOverColoredContents ? .white : presentationData.theme.chat.inputPanel.panelControlColor + let expandedButtonContentsColor: UIColor = presentationData.theme.chat.inputPanel.panelControlColor + + for (spec, button) in self.leftButtonNodes { + button.updateContentsColor(contentsColor: spec.isForExpandedView ? expandedButtonContentsColor : normalButtonContentsColor, transition: transition) + } + + for spec in self.currentRightButtons { + guard let button = self.rightButtonNodes[spec] else { continue } - button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition) - if !spec.isForExpandedView { - transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: accumulatedRightButtonOffset, y: 0.0)) - if self.backgroundContentColor.alpha != 0.0 { - accumulatedRightButtonOffset -= 6.0 - } - } - } - for (key, button) in self.rightButtonNodes { - if !self.currentRightButtons.contains(where: { $0.key == key }) { - button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition) - transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: 0.0, y: 0.0)) + button.updateContentsColor(contentsColor: spec.isForExpandedView ? expandedButtonContentsColor : normalButtonContentsColor, transition: transition) + } + for (spec, button) in self.rightButtonNodes { + if !self.currentRightButtons.contains(where: { $0 == spec }) { + button.updateContentsColor(contentsColor: spec.isForExpandedView ? expandedButtonContentsColor : normalButtonContentsColor, transition: transition) } } + + self.updateBackgroundColors(transition: ComponentTransition(transition)) } func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, shouldAnimateIn: Bool, transition: ContainedViewLayoutTransition) { - let sideInset: CGFloat = 24.0 - let expandedSideInset: CGFloat = 16.0 + transition.updateFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size)) - let maximumExpandOffset: CGFloat = 14.0 - let expandOffset: CGFloat = -expandFraction * maximumExpandOffset + let buttonHeight: CGFloat = 44.0 + + let sideInset: CGFloat = 16.0 + + var normalLeftButtonsWidth: CGFloat = 0.0 + var expandedLeftButtonsWidth: CGFloat = 0.0 + + let maxBlur: CGFloat = 5.0 + + let normalButtonContentsColor: UIColor = self.isOverColoredContents ? .white : presentationData.theme.chat.inputPanel.panelControlColor + let expandedButtonContentsColor: UIColor = presentationData.theme.chat.inputPanel.panelControlColor if self.currentLeftButtons != leftButtons || presentationData.strings !== self.presentationData?.strings { self.currentLeftButtons = leftButtons - var nextRegularButtonOrigin = sideInset - var nextExpandedButtonOrigin = sideInset for spec in leftButtons.reversed() { let buttonNode: PeerInfoHeaderNavigationButton var wasAdded = false - if let current = self.leftButtonNodes[spec.key] { + if let current = self.leftButtonNodes[spec] { buttonNode = current } else { wasAdded = true buttonNode = PeerInfoHeaderNavigationButton() - self.leftButtonNodes[spec.key] = buttonNode - self.addSubnode(buttonNode) + self.leftButtonNodes[spec] = buttonNode + self.leftButtonsContainer.addSubview(buttonNode.view) buttonNode.action = { [weak self] _, gesture in - guard let strongSelf = self, let buttonNode = strongSelf.leftButtonNodes[spec.key] else { + guard let strongSelf = self, let buttonNode = strongSelf.leftButtonNodes[spec] else { return } strongSelf.performAction?(spec.key, buttonNode.contextSourceNode, gesture) } } - let buttonSize = buttonNode.update(key: spec.key, presentationData: presentationData, height: size.height) - var nextButtonOrigin = spec.isForExpandedView ? nextExpandedButtonOrigin : nextRegularButtonOrigin + let buttonSize = buttonNode.update(key: spec.key, presentationData: presentationData, height: buttonHeight) + let buttonFrame = CGRect(origin: CGPoint(x: spec.isForExpandedView ? expandedLeftButtonsWidth : normalLeftButtonsWidth, y: 0.0), size: buttonSize) - let buttonY: CGFloat - if case .back = spec.key { - buttonY = 0.0 - } else { - buttonY = expandOffset + (spec.isForExpandedView ? maximumExpandOffset : 0.0) - } - let buttonFrame = CGRect(origin: CGPoint(x: nextButtonOrigin, y: buttonY), size: buttonSize) - - nextButtonOrigin += buttonSize.width + 4.0 if spec.isForExpandedView { - nextExpandedButtonOrigin = nextButtonOrigin + expandedLeftButtonsWidth += buttonSize.width } else { - nextRegularButtonOrigin = nextButtonOrigin + normalLeftButtonsWidth += buttonSize.width } let alphaFactor: CGFloat if case .back = spec.key { @@ -126,48 +153,40 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { } else { alphaFactor = spec.isForExpandedView ? expandFraction : (1.0 - expandFraction) } + if wasAdded { buttonNode.frame = buttonFrame buttonNode.alpha = 0.0 + ComponentTransition.immediate.setBlur(layer: buttonNode.layer, radius: maxBlur) transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) - buttonNode.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: self.canBeExpanded, transition: .immediate) - - transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? -8.0 : 0.0, y: 0.0)) + ComponentTransition(transition).setBlur(layer: buttonNode.layer, radius: 0.0) + buttonNode.updateContentsColor(contentsColor: spec.isForExpandedView ? expandedButtonContentsColor : normalButtonContentsColor, transition: .immediate) } else { transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) + ComponentTransition(transition).setBlur(layer: buttonNode.layer, radius: (1.0 - alphaFactor * alphaFactor) * maxBlur) } } - var removeKeys: [PeerInfoHeaderNavigationButtonKey] = [] - for (key, _) in self.leftButtonNodes { - if !leftButtons.contains(where: { $0.key == key }) { - removeKeys.append(key) + var removeKeys: [PeerInfoHeaderNavigationButtonSpec] = [] + for (spec, _) in self.leftButtonNodes { + if !leftButtons.contains(where: { $0 == spec }) { + removeKeys.append(spec) } } - for key in removeKeys { - if let buttonNode = self.leftButtonNodes.removeValue(forKey: key) { - buttonNode.removeFromSupernode() + for spec in removeKeys { + if let buttonNode = self.leftButtonNodes.removeValue(forKey: spec) { + buttonNode.view.removeFromSuperview() } } } else { - var nextRegularButtonOrigin = sideInset - var nextExpandedButtonOrigin = sideInset for spec in leftButtons.reversed() { - if let buttonNode = self.leftButtonNodes[spec.key] { + if let buttonNode = self.leftButtonNodes[spec] { let buttonSize = buttonNode.bounds.size - var nextButtonOrigin = spec.isForExpandedView ? nextExpandedButtonOrigin : nextRegularButtonOrigin - let buttonY: CGFloat - if case .back = spec.key { - buttonY = 0.0 - } else { - buttonY = expandOffset + (spec.isForExpandedView ? maximumExpandOffset : 0.0) - } - let buttonFrame = CGRect(origin: CGPoint(x: nextButtonOrigin, y: buttonY), size: buttonSize) - nextButtonOrigin += buttonSize.width + 4.0 + let buttonFrame = CGRect(origin: CGPoint(x: spec.isForExpandedView ? expandedLeftButtonsWidth : normalLeftButtonsWidth, y: 0.0), size: buttonSize) if spec.isForExpandedView { - nextExpandedButtonOrigin = nextButtonOrigin + expandedLeftButtonsWidth += buttonSize.width } else { - nextRegularButtonOrigin = nextButtonOrigin + normalLeftButtonsWidth += buttonSize.width } transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) let alphaFactor: CGFloat @@ -182,17 +201,18 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { buttonTransition = .animated(duration: duration * 0.25, curve: curve) } buttonTransition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) + ComponentTransition(buttonTransition).setBlur(layer: buttonNode.layer, radius: (1.0 - alphaFactor * alphaFactor) * maxBlur) } } } - var accumulatedRightButtonOffset: CGFloat = self.canBeExpanded ? 16.0 : 0.0 + var normalRightButtonsWidth: CGFloat = 0.0 + var expandedRightButtonsWidth: CGFloat = 0.0 + if self.currentRightButtons != rightButtons || presentationData.strings !== self.presentationData?.strings { self.currentRightButtons = rightButtons - var nextRegularButtonOrigin = size.width - sideInset - 8.0 - var nextExpandedButtonOrigin = size.width - expandedSideInset - for spec in rightButtons.reversed() { + for spec in rightButtons { let buttonNode: PeerInfoHeaderNavigationButton var wasAdded = false @@ -201,32 +221,30 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { key = .moreSearchSort } - if let current = self.rightButtonNodes[key] { + if let current = self.rightButtonNodes[spec] { buttonNode = current } else { wasAdded = true buttonNode = PeerInfoHeaderNavigationButton() - self.rightButtonNodes[key] = buttonNode - self.addSubnode(buttonNode) + self.rightButtonNodes[spec] = buttonNode + self.rightButtonsContainer.addSubview(buttonNode.view) } buttonNode.action = { [weak self] _, gesture in - guard let strongSelf = self, let buttonNode = strongSelf.rightButtonNodes[key] else { + guard let strongSelf = self, let buttonNode = strongSelf.rightButtonNodes[spec] else { return } strongSelf.performAction?(spec.key, buttonNode.contextSourceNode, gesture) } - let buttonSize = buttonNode.update(key: spec.key, presentationData: presentationData, height: size.height) - var nextButtonOrigin = spec.isForExpandedView ? nextExpandedButtonOrigin : nextRegularButtonOrigin - let buttonFrame = CGRect(origin: CGPoint(x: nextButtonOrigin - buttonSize.width, y: expandOffset + (spec.isForExpandedView ? maximumExpandOffset : 0.0)), size: buttonSize) - nextButtonOrigin -= buttonSize.width + 15.0 + let buttonSize = buttonNode.update(key: spec.key, presentationData: presentationData, height: buttonHeight) + let buttonFrame = CGRect(origin: CGPoint(x: spec.isForExpandedView ? expandedRightButtonsWidth : normalRightButtonsWidth, y: 0.0), size: buttonSize) if spec.isForExpandedView { - nextExpandedButtonOrigin = nextButtonOrigin + expandedRightButtonsWidth += buttonSize.width } else { - nextRegularButtonOrigin = nextButtonOrigin + normalRightButtonsWidth += buttonSize.width } let alphaFactor: CGFloat = spec.isForExpandedView ? expandFraction : (1.0 - expandFraction) if wasAdded { - buttonNode.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: self.canBeExpanded, transition: .immediate) + buttonNode.updateContentsColor(contentsColor: spec.isForExpandedView ? expandedButtonContentsColor : normalButtonContentsColor, transition: .immediate) if shouldAnimateIn { if key == .moreSearchSort || key == .searchWithTags || key == .standaloneSearch { @@ -236,62 +254,51 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { buttonNode.frame = buttonFrame buttonNode.alpha = 0.0 + ComponentTransition.immediate.setBlur(layer: buttonNode.layer, radius: maxBlur) transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) - - if !spec.isForExpandedView { - transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: accumulatedRightButtonOffset, y: 0.0)) - if self.backgroundContentColor.alpha != 0.0 { - accumulatedRightButtonOffset -= 6.0 - } - } else { - transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: .zero) - } + ComponentTransition(transition).setBlur(layer: buttonNode.layer, radius: (1.0 - alphaFactor * alphaFactor) * maxBlur) } else { transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) + ComponentTransition(transition).setBlur(layer: buttonNode.layer, radius: (1.0 - alphaFactor * alphaFactor) * maxBlur) } } - var removeKeys: [PeerInfoHeaderNavigationButtonKey] = [] - for (key, _) in self.rightButtonNodes { - if key == .moreSearchSort { + var removeKeys: [PeerInfoHeaderNavigationButtonSpec] = [] + for (spec, _) in self.rightButtonNodes { + if spec.key == .moreSearchSort { if !rightButtons.contains(where: { $0.key == .more || $0.key == .search || $0.key == .sort }) { - removeKeys.append(key) + removeKeys.append(spec) } - } else if !rightButtons.contains(where: { $0.key == key }) { - removeKeys.append(key) + } else if !rightButtons.contains(where: { $0 == spec }) { + removeKeys.append(spec) } } - for key in removeKeys { - if let buttonNode = self.rightButtonNodes.removeValue(forKey: key) { - if key == .moreSearchSort || key == .searchWithTags || key == .standaloneSearch { + for spec in removeKeys { + if let buttonNode = self.rightButtonNodes.removeValue(forKey: spec) { + if spec.key == .moreSearchSort || spec.key == .searchWithTags || spec.key == .standaloneSearch { buttonNode.layer.animateAlpha(from: buttonNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in - buttonNode?.removeFromSupernode() + buttonNode?.view.removeFromSuperview() }) buttonNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false) } else { - buttonNode.removeFromSupernode() + buttonNode.view.removeFromSuperview() } } } } else { - var nextRegularButtonOrigin = size.width - sideInset - 8.0 - var nextExpandedButtonOrigin = size.width - expandedSideInset - - for spec in rightButtons.reversed() { + for spec in rightButtons { var key = spec.key if key == .more || key == .search || key == .sort { key = .moreSearchSort } - if let buttonNode = self.rightButtonNodes[key] { + if let buttonNode = self.rightButtonNodes[spec] { let buttonSize = buttonNode.bounds.size - var nextButtonOrigin = spec.isForExpandedView ? nextExpandedButtonOrigin : nextRegularButtonOrigin - let buttonFrame = CGRect(origin: CGPoint(x: nextButtonOrigin - buttonSize.width, y: expandOffset + (spec.isForExpandedView ? maximumExpandOffset : 0.0)), size: buttonSize) - nextButtonOrigin -= buttonSize.width + 15.0 + let buttonFrame = CGRect(origin: CGPoint(x: spec.isForExpandedView ? expandedRightButtonsWidth : normalRightButtonsWidth, y: 0.0), size: buttonSize) if spec.isForExpandedView { - nextExpandedButtonOrigin = nextButtonOrigin + expandedRightButtonsWidth += buttonSize.width } else { - nextRegularButtonOrigin = nextButtonOrigin + normalRightButtonsWidth += buttonSize.width } transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) let alphaFactor: CGFloat = spec.isForExpandedView ? expandFraction : (1.0 - expandFraction) @@ -301,9 +308,60 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { buttonTransition = .animated(duration: duration * 0.25, curve: curve) } buttonTransition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) + ComponentTransition(transition).setBlur(layer: buttonNode.layer, radius: (1.0 - alphaFactor * alphaFactor) * maxBlur) } } } self.presentationData = presentationData + + let buttonsY: CGFloat = floor((size.height - buttonHeight) * 0.5) + 2.0 + + let leftButtonsWidth = (1.0 - expandFraction) * normalLeftButtonsWidth + expandFraction * expandedLeftButtonsWidth + let rightButtonsWidth = (1.0 - expandFraction) * normalRightButtonsWidth + expandFraction * expandedRightButtonsWidth + + var leftButtonsFrame = CGRect(origin: CGPoint(x: sideInset, y: buttonsY), size: CGSize(width: max(44.0, leftButtonsWidth), height: buttonHeight)) + if leftButtonsWidth < 44.0 { + let leftFraction = leftButtonsWidth / 44.0 + leftButtonsFrame.origin.x = floorToScreenPixels(leftFraction * sideInset + (1.0 - leftFraction) * (-44.0)) + } + var rightButtonsFrame = CGRect(origin: CGPoint(x: size.width - sideInset - rightButtonsWidth, y: buttonsY), size: CGSize(width: max(44.0, rightButtonsWidth), height: buttonHeight)) + if rightButtonsWidth < 44.0 { + let rightFraction = rightButtonsWidth / 44.0 + rightButtonsFrame.origin.x = floorToScreenPixels(rightFraction * (size.width - sideInset - 44.0) + (1.0 - rightFraction) * size.width) + } + + transition.updateFrame(view: self.leftButtonsBackground, frame: leftButtonsFrame) + transition.updateFrame(view: self.leftButtonsContainer, frame: CGRect(origin: CGPoint(), size: leftButtonsFrame.size)) + self.leftButtonsContainer.layer.cornerRadius = leftButtonsFrame.height * 0.5 + + transition.updateFrame(view: self.rightButtonsBackground, frame: rightButtonsFrame) + transition.updateFrame(view: self.rightButtonsContainer, frame: CGRect(origin: CGPoint(), size: rightButtonsFrame.size)) + self.rightButtonsContainer.layer.cornerRadius = rightButtonsFrame.height * 0.5 + + self.updateBackgroundColors(transition: ComponentTransition(transition)) + } + + private func updateBackgroundColors(transition: ComponentTransition) { + guard let presentationData = self.presentationData else { + return + } + + let leftButtonsSize = self.leftButtonsBackground.bounds.size + let rightButtonsSize = self.rightButtonsBackground.bounds.size + + let tintColor: GlassBackgroundView.TintColor + let tintIsDark: Bool + if self.isOverColoredContents { + tintColor = .init(kind: .custom, color: self.backgroundContentColor) + tintIsDark = presentationData.theme.overallDarkAppearance + } else { + tintColor = .init(kind: .panel, color: UIColor(white: presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)) + tintIsDark = presentationData.theme.overallDarkAppearance + } + + self.backgroundContainer.update(size: self.backgroundContainer.bounds.size, isDark: tintIsDark, transition: transition) + + self.rightButtonsBackground.update(size: rightButtonsSize, cornerRadius: rightButtonsSize.height * 0.5, isDark: tintIsDark, tintColor: tintColor, isInteractive: true, transition: transition) + self.leftButtonsBackground.update(size: leftButtonsSize, cornerRadius: leftButtonsSize.height * 0.5, isDark: tintIsDark, tintColor: tintColor, isInteractive: true, transition: transition) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index 28c5cb6c..d6911343 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -44,6 +44,7 @@ import ProfileLevelInfoScreen import PlainButtonComponent import BundleIconComponent import MarqueeComponent +import EdgeEffect final class PeerInfoHeaderNavigationTransition { let sourceNavigationBar: NavigationBar @@ -63,7 +64,7 @@ final class PeerInfoHeaderNavigationTransition { } } -final class PeerInfoHeaderRegularContentNode: ASDisplayNode { +final class PeerInfoHeaderRegularContentNode: SparseNode { } enum PeerInfoHeaderTextFieldNodeKey: Equatable { @@ -90,6 +91,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { private var state: PeerInfoState? private var peer: Peer? private var threadData: MessageHistoryThreadData? + private var isSearching: Bool = false private var avatarSize: CGFloat? private let isOpenedFromChat: Bool @@ -151,17 +153,14 @@ final class PeerInfoHeaderNode: ASDisplayNode { let usernameNode: MultiScaleTextNode var actionButtonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderActionButtonNode] = [:] var buttonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderButtonNode] = [:] - let backgroundNode: NavigationBackgroundNode - let expandedBackgroundNode: NavigationBackgroundNode - let separatorNode: ASDisplayNode - let navigationBackgroundNode: ASDisplayNode - let navigationBackgroundBackgroundNode: ASDisplayNode + let headerEdgeEffectContainer: UIView + let headerEdgeEffectView: EdgeEffectView var navigationTitle: String? - let navigationTitleNode: ImmediateTextNode - let navigationSeparatorNode: ASDisplayNode let navigationButtonContainer: PeerInfoHeaderNavigationButtonContainerNode - let editingNavigationBackgroundNode: NavigationBackgroundNode - let editingNavigationBackgroundSeparator: ASDisplayNode + let searchContainer: ASDisplayNode + var searchEdgeEffectView: EdgeEffectView? + let searchBarContainer: SparseNode + let editingEdgeEffectView: EdgeEffectView var musicBackground: UIView? var music: ComponentView? @@ -172,6 +171,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { var cancelUpload: (() -> Void)? var requestUpdateLayout: ((Bool) -> Void)? var animateOverlaysFadeIn: (() -> Void)? + var updateUnderHeaderContentsAlpha: ((CGFloat, ContainedViewLayoutTransition) -> Void)? var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)? var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)? @@ -266,6 +266,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.backgroundBannerView = UIView() self.backgroundBannerView.clipsToBounds = true self.backgroundBannerView.isUserInteractionEnabled = false + self.backgroundBannerView.layer.allowsGroupOpacity = true self.buttonsContainerNode = SparseNode() self.buttonsContainerNode.clipsToBounds = true @@ -286,30 +287,18 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.avatarOverlayNode = PeerInfoEditingAvatarOverlayNode(context: context) self.avatarOverlayNode.isUserInteractionEnabled = false - self.navigationBackgroundNode = ASDisplayNode() - self.navigationBackgroundNode.isHidden = true - self.navigationBackgroundNode.isUserInteractionEnabled = false - - self.navigationBackgroundBackgroundNode = ASDisplayNode() - self.navigationBackgroundBackgroundNode.isUserInteractionEnabled = false - - self.navigationTitleNode = ImmediateTextNode() - - self.navigationSeparatorNode = ASDisplayNode() - self.navigationButtonContainer = PeerInfoHeaderNavigationButtonContainerNode() - self.editingNavigationBackgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true) - self.editingNavigationBackgroundSeparator = ASDisplayNode() + self.searchBarContainer = SparseNode() + self.searchContainer = ASDisplayNode() - self.backgroundNode = NavigationBackgroundNode(color: .clear) - self.backgroundNode.isHidden = true - self.backgroundNode.isUserInteractionEnabled = false - self.expandedBackgroundNode = NavigationBackgroundNode(color: .clear) - self.expandedBackgroundNode.isHidden = false - self.expandedBackgroundNode.isUserInteractionEnabled = false + self.headerEdgeEffectView = EdgeEffectView() + self.headerEdgeEffectView.isUserInteractionEnabled = false - self.separatorNode = ASDisplayNode() - self.separatorNode.isLayerBacked = true + self.headerEdgeEffectContainer = UIView() + self.headerEdgeEffectContainer.addSubview(self.headerEdgeEffectView) + + self.editingEdgeEffectView = EdgeEffectView() + self.editingEdgeEffectView.isUserInteractionEnabled = false self.animationCache = context.animationCache self.animationRenderer = context.animationRenderer @@ -320,8 +309,6 @@ final class PeerInfoHeaderNode: ASDisplayNode { self?.requestUpdateLayout?(false) } - self.addSubnode(self.backgroundNode) - self.addSubnode(self.expandedBackgroundNode) self.view.addSubview(self.backgroundBannerView) self.titleNodeContainer.addSubnode(self.titleNode) self.subtitleNodeContainer.addSubnode(self.subtitleNode) @@ -346,14 +333,11 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.addSubnode(self.editingContentNode) self.addSubnode(self.avatarOverlayNode) - self.addSubnode(self.navigationBackgroundNode) - self.navigationBackgroundNode.addSubnode(self.navigationBackgroundBackgroundNode) - self.navigationBackgroundNode.addSubnode(self.navigationTitleNode) - self.navigationBackgroundNode.addSubnode(self.navigationSeparatorNode) - self.addSubnode(self.editingNavigationBackgroundNode) - self.addSubnode(self.editingNavigationBackgroundSeparator) + self.view.addSubview(self.editingEdgeEffectView) self.addSubnode(self.navigationButtonContainer) - self.addSubnode(self.separatorNode) + + self.addSubnode(self.searchContainer) + self.addSubnode(self.searchBarContainer) self.avatarListNode.avatarContainerNode.tapped = { [weak self] in self?.initiateAvatarExpansion(gallery: false, first: false) @@ -509,20 +493,20 @@ final class PeerInfoHeaderNode: ASDisplayNode { private var currentStatusIcon: CredibilityIcon? private var currentPanelStatusData: PeerInfoStatusData? - func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, profileGiftsContext: ProfileGiftsContext?, screenData: PeerInfoScreenData?, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat { + func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, profileGiftsContext: ProfileGiftsContext?, screenData: PeerInfoScreenData?, isSearching: Bool, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat { if self.appliedCustomNavigationContentNode !== self.customNavigationContentNode { if let previous = self.appliedCustomNavigationContentNode { - transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in + ComponentTransition(transition).setAlpha(view: previous.view, alpha: 0.0, completion: { [weak previous] _ in previous?.removeFromSupernode() }) } self.appliedCustomNavigationContentNode = self.customNavigationContentNode if let customNavigationContentNode = self.customNavigationContentNode { - self.addSubnode(customNavigationContentNode) + self.searchBarContainer.addSubnode(customNavigationContentNode) customNavigationContentNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: navigationHeight)) customNavigationContentNode.alpha = 0.0 - transition.updateAlpha(node: customNavigationContentNode, alpha: 1.0) + ComponentTransition(transition).setAlpha(view: customNavigationContentNode.view, alpha: 1.0) } } else if let customNavigationContentNode = self.customNavigationContentNode { transition.updateFrame(node: customNavigationContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: navigationHeight))) @@ -536,11 +520,15 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.state = state self.peer = peer self.threadData = threadData + self.isSearching = isSearching self.avatarListNode.listContainerNode.peer = peer.flatMap(EnginePeer.init) let isFirstTime = self.validLayout == nil self.validLayout = (width, statusBarHeight, deviceMetrics) + self.searchBarContainer.isUserInteractionEnabled = isSearching + self.searchContainer.isUserInteractionEnabled = isSearching + let previousPanelStatusData = self.currentPanelStatusData self.currentPanelStatusData = panelStatusData.0 @@ -637,7 +625,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { transition.updateAlpha(node: self.regularContentNode, alpha: (state.isEditing || self.customNavigationContentNode != nil) ? 0.0 : 1.0) if self.navigationTransition == nil { - transition.updateAlpha(node: self.navigationButtonContainer, alpha: self.customNavigationContentNode != nil ? 0.0 : 1.0) + transition.updateAlpha(node: self.navigationButtonContainer, alpha: (self.customNavigationContentNode != nil || isSearching) ? 0.0 : 1.0) } self.editingContentNode.alpha = state.isEditing ? 1.0 : 0.0 @@ -648,18 +636,14 @@ final class PeerInfoHeaderNode: ASDisplayNode { let avatarOverlayFarme = self.editingContentNode.convert(self.editingContentNode.avatarNode.frame, to: self) transition.updateFrame(node: self.avatarOverlayNode, frame: avatarOverlayFarme) - var transitionSourceHeight: CGFloat = 0.0 - var transitionFraction: CGFloat = 0.0 - var transitionSourceAvatarFrame: CGRect? - var transitionSourceTitleFrame = CGRect() - var transitionSourceSubtitleFrame = CGRect() + let transitionSourceHeight: CGFloat = 0.0 + let transitionFraction: CGFloat = 0.0 + let transitionSourceAvatarFrame: CGRect? = nil + let transitionSourceTitleFrame = CGRect() + let transitionSourceSubtitleFrame = CGRect() let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 22.0), size: CGSize(width: avatarSize, height: avatarSize)) - self.backgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - - let headerBackgroundColor: UIColor = presentationData.theme.list.blocksBackgroundColor - let regularNavigationContentsAccentColor: UIColor = peer?.effectiveProfileColor != nil ? .white : presentationData.theme.list.itemAccentColor let collapsedHeaderNavigationContentsAccentColor = presentationData.theme.list.itemAccentColor let expandedAvatarNavigationContentsAccentColor: UIColor = .white @@ -674,7 +658,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let regularHeaderButtonBackgroundColor: UIColor let collapsedHeaderButtonBackgroundColor: UIColor = .clear - let expandedAvatarHeaderButtonBackgroundColor: UIColor = UIColor(white: 1.0, alpha: 0.1) + let expandedAvatarHeaderButtonBackgroundColor: UIColor = UIColor(white: 0.0, alpha: 0.5) let regularContentButtonForegroundColor: UIColor = peer?.effectiveProfileColor != nil ? UIColor.white : presentationData.theme.list.itemAccentColor let collapsedHeaderContentButtonForegroundColor = presentationData.theme.list.itemAccentColor @@ -745,61 +729,55 @@ final class PeerInfoHeaderNode: ASDisplayNode { navigationTransition = animateHeader ? .animated(duration: 0.2, curve: .easeInOut) : .immediate } + let editingEdgeEffectHeight: CGFloat = 40.0 + let editingEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: navigationHeight + 10.0)) + transition.updateFrame(view: self.editingEdgeEffectView, frame: editingEdgeEffectFrame) + self.editingEdgeEffectView.update(content: presentationData.theme.list.blocksBackgroundColor, blur: true, rect: editingEdgeEffectFrame, edge: .top, edgeSize: editingEdgeEffectHeight, transition: ComponentTransition(transition)) let editingBackgroundAlpha: CGFloat if state.isEditing { editingBackgroundAlpha = max(0.0, min(1.0, contentOffset / 20.0)) } else { editingBackgroundAlpha = 0.0 } + ComponentTransition(transition).setAlpha(view: self.editingEdgeEffectView, alpha: editingBackgroundAlpha) - self.editingNavigationBackgroundSeparator.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor - self.editingNavigationBackgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - - let editingNavigationBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: navigationHeight)) - transition.updateFrame(node: self.editingNavigationBackgroundNode, frame: editingNavigationBackgroundFrame) - self.editingNavigationBackgroundNode.update(size: editingNavigationBackgroundFrame.size, transition: transition) - transition.updateFrame(node: self.editingNavigationBackgroundSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: editingNavigationBackgroundFrame.maxY), size: CGSize(width: width, height: UIScreenPixel))) - - transition.updateAlpha(node: self.editingNavigationBackgroundNode, alpha: editingBackgroundAlpha) - transition.updateAlpha(node: self.editingNavigationBackgroundSeparator, alpha: editingBackgroundAlpha) + if isSearching { + let searchNavigationHeight: CGFloat + if isSettings { + searchNavigationHeight = statusBarHeight + 10.0 + } else { + searchNavigationHeight = navigationHeight + 10.0 + } + + let searchEdgeEffectHeight: CGFloat = 40.0 + let searchEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: searchNavigationHeight)) + + let searchEdgeEffectView: EdgeEffectView + var searchEdgeEffectTransition = ComponentTransition(transition) + if let current = self.searchEdgeEffectView { + searchEdgeEffectView = current + } else { + searchEdgeEffectTransition = .immediate + searchEdgeEffectView = EdgeEffectView() + self.searchEdgeEffectView = searchEdgeEffectView + self.searchContainer.view.superview?.insertSubview(searchEdgeEffectView, aboveSubview: self.searchContainer.view) + if transition.isAnimated { + searchEdgeEffectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + + transition.updateFrame(view: searchEdgeEffectView, frame: searchEdgeEffectFrame) + searchEdgeEffectView.update(content: presentationData.theme.list.plainBackgroundColor, blur: true, rect: searchEdgeEffectFrame, edge: .top, edgeSize: searchEdgeEffectHeight, transition: searchEdgeEffectTransition) + } else if let searchEdgeEffectView = self.searchEdgeEffectView { + self.searchEdgeEffectView = nil + transition.updateAlpha(layer: searchEdgeEffectView.layer, alpha: 0.0, completion: { [weak searchEdgeEffectView] _ in + searchEdgeEffectView?.removeFromSuperview() + }) + } let backgroundBannerAlpha: CGFloat - var effectiveSeparatorAlpha: CGFloat - if let navigationTransition = self.navigationTransition { - transitionSourceHeight = navigationTransition.sourceNavigationBar.backgroundNode.bounds.height - transitionFraction = navigationTransition.fraction - - innerBackgroundTransitionFraction = 0.0 - backgroundBannerAlpha = 1.0 - - if let avatarNavigationNode = navigationTransition.sourceNavigationBar.rightButtonNode.singleCustomNode as? ChatAvatarNavigationNode { - if let statusView = avatarNavigationNode.statusView.view { - transitionSourceAvatarFrame = statusView.convert(statusView.bounds, to: navigationTransition.sourceNavigationBar.view) - } else { - transitionSourceAvatarFrame = avatarNavigationNode.avatarNode.view.convert(avatarNavigationNode.avatarNode.view.bounds, to: navigationTransition.sourceNavigationBar.view) - } - transition.updateAlpha(node: self.avatarListNode.avatarContainerNode.avatarNode, alpha: 1.0 - transitionFraction) - } else { - if deviceMetrics.hasDynamicIsland && statusBarHeight > 0.0 && !isLandscape { - transitionSourceAvatarFrame = CGRect(origin: CGPoint(x: avatarFrame.minX, y: -20.0), size: avatarFrame.size).insetBy(dx: avatarSize * 0.4, dy: avatarSize * 0.4) - } else { - transitionSourceAvatarFrame = avatarFrame.offsetBy(dx: 0.0, dy: -avatarFrame.maxY).insetBy(dx: avatarSize * 0.4, dy: avatarSize * 0.4) - } - } - transitionSourceTitleFrame = navigationTransition.sourceTitleFrame - transitionSourceSubtitleFrame = navigationTransition.sourceSubtitleFrame - - transition.updateAlpha(layer: self.backgroundBannerView.layer, alpha: 1.0 - transitionFraction) - - self.expandedBackgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor.mixedWith(headerBackgroundColor, alpha: 1.0 - transitionFraction), forceKeepBlur: true, transition: transition) - effectiveSeparatorAlpha = transitionFraction - - if self.isAvatarExpanded, case .animated = transition, transitionFraction == 1.0 { - self.avatarListNode.animateAvatarCollapse(transition: transition) - } - self.avatarClippingNode.clipsToBounds = false - } else { + do { let backgroundTransitionStepDistance: CGFloat = 50.0 var backgroundTransitionDistance: CGFloat = navigationHeight + panelWithAvatarHeight - backgroundTransitionStepDistance if self.isSettings || self.isMyProfile { @@ -812,9 +790,6 @@ final class PeerInfoHeaderNode: ASDisplayNode { innerBackgroundTransitionFraction = max(0.0, min(1.0, contentOffset / backgroundTransitionStepDistance)) } - self.expandedBackgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.opaqueBackgroundColor.mixedWith(headerBackgroundColor, alpha: 1.0 - innerBackgroundTransitionFraction), forceKeepBlur: true, transition: transition) - navigationTransition.updateAlpha(node: self.expandedBackgroundNode, alpha: state.isEditing ? 0.0 : 1.0) - if state.isEditing { backgroundBannerAlpha = 0.0 } else { @@ -826,8 +801,6 @@ final class PeerInfoHeaderNode: ASDisplayNode { } navigationTransition.updateAlpha(layer: self.backgroundBannerView.layer, alpha: backgroundBannerAlpha) - effectiveSeparatorAlpha = innerBackgroundTransitionFraction - self.avatarClippingNode.clipsToBounds = true } @@ -1161,7 +1134,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.titleExpandedVerifiedIconSize = expandedIconSize } - self.navigationButtonContainer.updateContentsColor(backgroundContentColor: headerButtonBackgroundColor, contentsColor: navigationContentsAccentColor, canBeExpanded: navigationContentsCanBeExpanded, transition: navigationTransition) + var actualNavigationContentsColor = navigationContentsAccentColor + actualNavigationContentsColor = presentationData.theme.chat.inputPanel.panelControlColor + + self.navigationButtonContainer.updateContentsColor(backgroundContentColor: headerButtonBackgroundColor, contentsColor: actualNavigationContentsColor, isOverColoredContents: !navigationContentsCanBeExpanded, transition: navigationTransition) self.titleNode.updateTintColor(color: navigationContentsPrimaryColor, transition: navigationTransition) self.subtitleNode.updateTintColor(color: navigationContentsSecondaryColor, transition: navigationTransition) @@ -1183,31 +1159,16 @@ final class PeerInfoHeaderNode: ASDisplayNode { var titleBrightness: CGFloat = 0.0 navigationContentsPrimaryColor.getHue(nil, saturation: nil, brightness: &titleBrightness, alpha: nil) - self.controller?.setStatusBarStyle(titleBrightness > 0.5 ? .White : .Black, animated: !isFirstTime && animateHeader) + if isSearching { + self.controller?.setStatusBarStyle(presentationData.theme.overallDarkAppearance ? .White : .Black, animated: !isFirstTime && animateHeader) + } else { + self.controller?.setStatusBarStyle(titleBrightness > 0.5 ? .White : .Black, animated: !isFirstTime && animateHeader) + } self.avatarListNode.avatarContainerNode.updateTransitionFraction(transitionFraction, transition: transition) self.avatarListNode.listContainerNode.currentItemNode?.updateTransitionFraction(transitionFraction, transition: transition) self.avatarOverlayNode.updateTransitionFraction(transitionFraction, transition: transition) - if self.navigationTitle != presentationData.strings.EditProfile_Title || themeUpdated { - self.navigationTitleNode.attributedText = NSAttributedString(string: presentationData.strings.EditProfile_Title, font: Font.semibold(17.0), textColor: .white) - } - - let navigationTitleSize = self.navigationTitleNode.updateLayout(CGSize(width: width, height: navigationHeight)) - self.navigationTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - navigationTitleSize.width) / 2.0), y: navigationHeight - 44.0 + floorToScreenPixels((44.0 - navigationTitleSize.height) / 2.0)), size: navigationTitleSize) - - self.navigationBackgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: navigationHeight)) - self.navigationBackgroundBackgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: navigationHeight)) - self.navigationSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: width, height: UIScreenPixel)) - self.navigationBackgroundBackgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor - self.navigationSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor - - let navigationSeparatorAlpha: CGFloat = 0.0 - transition.updateAlpha(node: self.navigationBackgroundBackgroundNode, alpha: 1.0 - navigationSeparatorAlpha) - transition.updateAlpha(node: self.navigationSeparatorNode, alpha: navigationSeparatorAlpha) - - self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - let expandedAvatarControlsHeight: CGFloat = 61.0 var expandedAvatarListHeight = min(width, containerHeight - expandedAvatarControlsHeight) if self.isSettings || self.isMyProfile { @@ -1638,7 +1599,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let singleTitleLockOffset: CGFloat = ((peer?.id == self.context.account.peerId && !self.isMyProfile) || subtitleSize.height.isZero) ? 8.0 : 0.0 - let titleLockOffset: CGFloat = 7.0 + singleTitleLockOffset + let titleLockOffset: CGFloat = 16.0 + singleTitleLockOffset let titleMaxLockOffset: CGFloat = 7.0 let titleOffset: CGFloat let titleCollapseFraction: CGFloat @@ -1697,38 +1658,37 @@ final class PeerInfoHeaderNode: ASDisplayNode { let paneAreaExpansionDistance: CGFloat = 32.0 let effectiveAreaExpansionFraction: CGFloat + let realAreaExpansionFraction: CGFloat if state.isEditing { effectiveAreaExpansionFraction = 0.0 + realAreaExpansionFraction = effectiveAreaExpansionFraction } else if isSettings || isMyProfile { var paneAreaExpansionDelta = (self.frame.maxY - navigationHeight) - contentOffset paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance)) effectiveAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance + + do { + var paneAreaExpansionDelta = (paneContainerY - navigationHeight) - contentOffset + paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance)) + realAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance + } } else { var paneAreaExpansionDelta = (paneContainerY - navigationHeight) - contentOffset paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance)) effectiveAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance + realAreaExpansionFraction = effectiveAreaExpansionFraction } - let secondarySeparatorAlpha = 1.0 - effectiveAreaExpansionFraction - if self.navigationTransition == nil && !self.isSettings && effectiveSeparatorAlpha == 1.0 && secondarySeparatorAlpha < 1.0 { - effectiveSeparatorAlpha = secondarySeparatorAlpha - } - if self.customNavigationContentNode != nil { - effectiveSeparatorAlpha = 0.0 - } - if state.isEditing { - effectiveSeparatorAlpha = 0.0 - } - transition.updateAlpha(node: self.separatorNode, alpha: effectiveSeparatorAlpha) - self.titleNode.update(stateFractions: [ TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0, TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0 ], transition: transition) - let subtitleAlpha: CGFloat + transition.updateAlpha(node: self.titleNode, alpha: isSearching ? 0.0 : 1.0) + + var subtitleAlpha: CGFloat var subtitleOffset: CGFloat = 0.0 - let panelSubtitleAlpha: CGFloat + var panelSubtitleAlpha: CGFloat var panelSubtitleOffset: CGFloat = 0.0 if self.isSettings { subtitleAlpha = 1.0 - titleCollapseFraction @@ -1755,6 +1715,12 @@ final class PeerInfoHeaderNode: ASDisplayNode { } } } + + if isSearching { + subtitleAlpha = 0.0 + panelSubtitleAlpha = 0.0 + } + self.subtitleNode.update(stateFractions: [ TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0, TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0 @@ -2465,18 +2431,15 @@ final class PeerInfoHeaderNode: ASDisplayNode { } let backgroundFrame: CGRect - let separatorFrame: CGRect var resolvedHeight: CGFloat if state.isEditing { resolvedHeight = editingContentHeight backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + max(navigationHeight, resolvedHeight - contentOffset)), size: CGSize(width: width, height: 2000.0)) - separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: max(navigationHeight, resolvedHeight - contentOffset)), size: CGSize(width: width, height: UIScreenPixel)) } else { resolvedHeight = resolvedRegularHeight backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + apparentHeight), size: CGSize(width: width, height: 2000.0)) - separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: apparentHeight), size: CGSize(width: width, height: UIScreenPixel)) } transition.updateFrame(node: self.regularContentNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: resolvedHeight))) @@ -2582,20 +2545,24 @@ final class PeerInfoHeaderNode: ASDisplayNode { } } + let edgeEffectHeight: CGFloat = 60.0 + var edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: -50.0), size: CGSize(width: backgroundFrame.width, height: navigationHeight + 18.0 + 50.0)) + edgeEffectFrame.origin.y += floorToScreenPixels(realAreaExpansionFraction * 50.0) + if additive { - transition.updateFrameAdditive(node: self.backgroundNode, frame: backgroundFrame) - self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition) - transition.updateFrameAdditive(node: self.expandedBackgroundNode, frame: backgroundFrame) - self.expandedBackgroundNode.update(size: self.expandedBackgroundNode.bounds.size, transition: transition) - transition.updateFrameAdditive(node: self.separatorNode, frame: separatorFrame) + transition.updateFrameAdditive(layer: self.headerEdgeEffectView.layer, frame: edgeEffectFrame) } else { - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition) - transition.updateFrame(node: self.expandedBackgroundNode, frame: backgroundFrame) - self.expandedBackgroundNode.update(size: self.expandedBackgroundNode.bounds.size, transition: transition) - transition.updateFrame(node: self.separatorNode, frame: separatorFrame) + transition.updateFrame(view: self.headerEdgeEffectView, frame: edgeEffectFrame) } + if !isSettings { + self.updateUnderHeaderContentsAlpha?(1.0 - realAreaExpansionFraction, transition) + } + + self.headerEdgeEffectView.update(content: presentationData.theme.list.plainBackgroundColor, blur: true, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectHeight, transition: ComponentTransition(transition)) + + navigationTransition.updateAlpha(layer: self.headerEdgeEffectView.layer, alpha: state.isEditing ? 0.0 : 1.0) + if !state.isEditing { if !isSettings && !isMyProfile { if self.isAvatarExpanded { @@ -2775,15 +2742,19 @@ final class PeerInfoHeaderNode: ASDisplayNode { guard let result = super.hitTest(point, with: event) else { return nil } - if !self.backgroundNode.frame.contains(point) { - return nil + + if self.isSearching { + if !result.isDescendant(of: self.searchBarContainer.view) && !result.isDescendant(of: self.searchContainer.view) { + return self.view + } + + return result } if let customNavigationContentNode = self.customNavigationContentNode { if let result = customNavigationContentNode.view.hitTest(self.view.convert(point, to: customNavigationContentNode.view), with: event) { return result } - return self.view } let setByFrame = self.avatarListNode.listContainerNode.setByYouNode.view.convert(self.avatarListNode.listContainerNode.setByYouNode.bounds, to: self.view).insetBy(dx: -44.0, dy: 0.0) @@ -2836,7 +2807,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { return result } - if self.isSettings { + if self.isSettings && self.buttonsContainerNode.alpha != 0.0 { if self.subtitleNodeRawContainer.bounds.contains(self.view.convert(point, to: self.subtitleNodeRawContainer.view)) { return self.subtitleNodeRawContainer.view } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoInteraction.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoInteraction.swift new file mode 100644 index 00000000..d92de16e --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoInteraction.swift @@ -0,0 +1,238 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import TelegramCore +import Postbox +import SwiftSignalKit +import AccountContext +import StatisticsUI + +final class PeerInfoInteraction { + let openChat: (EnginePeer.Id?) -> Void + let openUsername: (String, Bool, Promise?) -> Void + let openPhone: (String, ASDisplayNode, ContextGesture?, Promise?) -> Void + let editingOpenNotificationSettings: () -> Void + let editingOpenSoundSettings: () -> Void + let editingToggleShowMessageText: (Bool) -> Void + let requestDeleteContact: () -> Void + let suggestBirthdate: () -> Void + let suggestPhoto: () -> Void + let setCustomPhoto: () -> Void + let resetCustomPhoto: () -> Void + let openAddContact: () -> Void + let updateBlocked: (Bool) -> Void + let openReport: (PeerInfoReportType) -> Void + let openShareBot: () -> Void + let openAddBotToGroup: () -> Void + let performBotCommand: (PeerInfoBotCommand) -> Void + let editingOpenPublicLinkSetup: () -> Void + let editingOpenNameColorSetup: () -> Void + let editingOpenInviteLinksSetup: () -> Void + let editingOpenDiscussionGroupSetup: () -> Void + let editingOpenPostSuggestionsSetup: () -> Void + let editingOpenRevenue: () -> Void + let editingOpenStars: () -> Void + let openParticipantsSection: (PeerInfoParticipantsSection) -> Void + let openRecentActions: () -> Void + let openChannelMessages: () -> Void + let openStats: (ChannelStatsSection) -> Void + let editingOpenPreHistorySetup: () -> Void + let editingOpenAutoremoveMesages: () -> Void + let openPermissions: () -> Void + let openLocation: () -> Void + let editingOpenSetupLocation: () -> Void + let openPeerInfo: (Peer, Bool) -> Void + let performMemberAction: (PeerInfoMember, PeerInfoMemberAction) -> Void + let openPeerInfoContextMenu: (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void + let performBioLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void + let requestLayout: (Bool) -> Void + let openEncryptionKey: () -> Void + let openSettings: (PeerInfoSettingsSection) -> Void + let openPaymentMethod: () -> Void + let switchToAccount: (AccountRecordId) -> Void + let logoutAccount: (AccountRecordId) -> Void + let accountContextMenu: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void + let updateBio: (String) -> Void + let updateNote: (NSAttributedString) -> Void + let openDeletePeer: () -> Void + let openFaq: (String?) -> Void + let openAddMember: () -> Void + let openQrCode: () -> Void + let editingOpenReactionsSetup: () -> Void + let dismissInput: () -> Void + let openForumSettings: () -> Void + let displayTopicsLimited: (TopicsLimitedReason) -> Void + let openPeerMention: (String, ChatControllerInteractionNavigateToPeer) -> Void + let openBotApp: (AttachMenuBot) -> Void + let openEditing: () -> Void + let updateBirthdate: (TelegramBirthday??) -> Void + let updateIsEditingBirthdate: (Bool) -> Void + let openBioPrivacy: () -> Void + let openBirthdatePrivacy: () -> Void + let openPremiumGift: () -> Void + let editingOpenPersonalChannel: () -> Void + let openUsernameContextMenu: (ASDisplayNode, ContextGesture?) -> Void + let openBioContextMenu: (ASDisplayNode, ContextGesture?) -> Void + let openNoteContextMenu: (ASDisplayNode, ContextGesture?) -> Void + let openWorkingHoursContextMenu: (ASDisplayNode, ContextGesture?) -> Void + let openBusinessLocationContextMenu: (ASDisplayNode, ContextGesture?) -> Void + let openBirthdayContextMenu: (ASDisplayNode, ContextGesture?) -> Void + let editingOpenAffiliateProgram: () -> Void + let editingOpenVerifyAccounts: () -> Void + let editingToggleAutoTranslate: (Bool) -> Void + let displayAutoTranslateLocked: () -> Void + let getController: () -> ViewController? + + init( + openUsername: @escaping (String, Bool, Promise?) -> Void, + openPhone: @escaping (String, ASDisplayNode, ContextGesture?, Promise?) -> Void, + editingOpenNotificationSettings: @escaping () -> Void, + editingOpenSoundSettings: @escaping () -> Void, + editingToggleShowMessageText: @escaping (Bool) -> Void, + requestDeleteContact: @escaping () -> Void, + suggestBirthdate: @escaping () -> Void, + suggestPhoto: @escaping () -> Void, + setCustomPhoto: @escaping () -> Void, + resetCustomPhoto: @escaping () -> Void, + openChat: @escaping (EnginePeer.Id?) -> Void, + openAddContact: @escaping () -> Void, + updateBlocked: @escaping (Bool) -> Void, + openReport: @escaping (PeerInfoReportType) -> Void, + openShareBot: @escaping () -> Void, + openAddBotToGroup: @escaping () -> Void, + performBotCommand: @escaping (PeerInfoBotCommand) -> Void, + editingOpenPublicLinkSetup: @escaping () -> Void, + editingOpenNameColorSetup: @escaping () -> Void, + editingOpenInviteLinksSetup: @escaping () -> Void, + editingOpenDiscussionGroupSetup: @escaping () -> Void, + editingOpenPostSuggestionsSetup: @escaping () -> Void, + editingOpenRevenue: @escaping () -> Void, + editingOpenStars: @escaping () -> Void, + openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void, + openRecentActions: @escaping () -> Void, + openChannelMessages: @escaping () -> Void, + openStats: @escaping (ChannelStatsSection) -> Void, + editingOpenPreHistorySetup: @escaping () -> Void, + editingOpenAutoremoveMesages: @escaping () -> Void, + openPermissions: @escaping () -> Void, + openLocation: @escaping () -> Void, + editingOpenSetupLocation: @escaping () -> Void, + openPeerInfo: @escaping (Peer, Bool) -> Void, + performMemberAction: @escaping (PeerInfoMember, PeerInfoMemberAction) -> Void, + openPeerInfoContextMenu: @escaping (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void, + performBioLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, + requestLayout: @escaping (Bool) -> Void, + openEncryptionKey: @escaping () -> Void, + openSettings: @escaping (PeerInfoSettingsSection) -> Void, + openPaymentMethod: @escaping () -> Void, + switchToAccount: @escaping (AccountRecordId) -> Void, + logoutAccount: @escaping (AccountRecordId) -> Void, + accountContextMenu: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void, + updateBio: @escaping (String) -> Void, + updateNote: @escaping (NSAttributedString) -> Void, + openDeletePeer: @escaping () -> Void, + openFaq: @escaping (String?) -> Void, + openAddMember: @escaping () -> Void, + openQrCode: @escaping () -> Void, + editingOpenReactionsSetup: @escaping () -> Void, + dismissInput: @escaping () -> Void, + openForumSettings: @escaping () -> Void, + displayTopicsLimited: @escaping (TopicsLimitedReason) -> Void, + openPeerMention: @escaping (String, ChatControllerInteractionNavigateToPeer) -> Void, + openBotApp: @escaping (AttachMenuBot) -> Void, + openEditing: @escaping () -> Void, + updateBirthdate: @escaping (TelegramBirthday??) -> Void, + updateIsEditingBirthdate: @escaping (Bool) -> Void, + openBioPrivacy: @escaping () -> Void, + openBirthdatePrivacy: @escaping () -> Void, + openPremiumGift: @escaping () -> Void, + editingOpenPersonalChannel: @escaping () -> Void, + openUsernameContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, + openBioContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, + openNoteContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, + openWorkingHoursContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, + openBusinessLocationContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, + openBirthdayContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, + editingOpenAffiliateProgram: @escaping () -> Void, + editingOpenVerifyAccounts: @escaping () -> Void, + editingToggleAutoTranslate: @escaping (Bool) -> Void, + displayAutoTranslateLocked: @escaping () -> Void, + getController: @escaping () -> ViewController? + ) { + self.openUsername = openUsername + self.openPhone = openPhone + self.editingOpenNotificationSettings = editingOpenNotificationSettings + self.editingOpenSoundSettings = editingOpenSoundSettings + self.editingToggleShowMessageText = editingToggleShowMessageText + self.requestDeleteContact = requestDeleteContact + self.suggestBirthdate = suggestBirthdate + self.suggestPhoto = suggestPhoto + self.setCustomPhoto = setCustomPhoto + self.resetCustomPhoto = resetCustomPhoto + self.openChat = openChat + self.openAddContact = openAddContact + self.updateBlocked = updateBlocked + self.openReport = openReport + self.openShareBot = openShareBot + self.openAddBotToGroup = openAddBotToGroup + self.performBotCommand = performBotCommand + self.editingOpenPublicLinkSetup = editingOpenPublicLinkSetup + self.editingOpenNameColorSetup = editingOpenNameColorSetup + self.editingOpenInviteLinksSetup = editingOpenInviteLinksSetup + self.editingOpenDiscussionGroupSetup = editingOpenDiscussionGroupSetup + self.editingOpenPostSuggestionsSetup = editingOpenPostSuggestionsSetup + self.editingOpenRevenue = editingOpenRevenue + self.editingOpenStars = editingOpenStars + self.openParticipantsSection = openParticipantsSection + self.openRecentActions = openRecentActions + self.openChannelMessages = openChannelMessages + self.openStats = openStats + self.editingOpenPreHistorySetup = editingOpenPreHistorySetup + self.editingOpenAutoremoveMesages = editingOpenAutoremoveMesages + self.openPermissions = openPermissions + self.openLocation = openLocation + self.editingOpenSetupLocation = editingOpenSetupLocation + self.openPeerInfo = openPeerInfo + self.performMemberAction = performMemberAction + self.openPeerInfoContextMenu = openPeerInfoContextMenu + self.performBioLinkAction = performBioLinkAction + self.requestLayout = requestLayout + self.openEncryptionKey = openEncryptionKey + self.openSettings = openSettings + self.openPaymentMethod = openPaymentMethod + self.switchToAccount = switchToAccount + self.logoutAccount = logoutAccount + self.accountContextMenu = accountContextMenu + self.updateBio = updateBio + self.updateNote = updateNote + self.openDeletePeer = openDeletePeer + self.openFaq = openFaq + self.openAddMember = openAddMember + self.openQrCode = openQrCode + self.editingOpenReactionsSetup = editingOpenReactionsSetup + self.dismissInput = dismissInput + self.openForumSettings = openForumSettings + self.displayTopicsLimited = displayTopicsLimited + self.openPeerMention = openPeerMention + self.openBotApp = openBotApp + self.openEditing = openEditing + self.updateBirthdate = updateBirthdate + self.updateIsEditingBirthdate = updateIsEditingBirthdate + self.openBioPrivacy = openBioPrivacy + self.openBirthdatePrivacy = openBirthdatePrivacy + self.openPremiumGift = openPremiumGift + self.editingOpenPersonalChannel = editingOpenPersonalChannel + self.openUsernameContextMenu = openUsernameContextMenu + self.openBioContextMenu = openBioContextMenu + self.openNoteContextMenu = openNoteContextMenu + self.openWorkingHoursContextMenu = openWorkingHoursContextMenu + self.openBusinessLocationContextMenu = openBusinessLocationContextMenu + self.openBirthdayContextMenu = openBirthdayContextMenu + self.editingOpenAffiliateProgram = editingOpenAffiliateProgram + self.editingOpenVerifyAccounts = editingOpenVerifyAccounts + self.editingToggleAutoTranslate = editingToggleAutoTranslate + self.displayAutoTranslateLocked = displayAutoTranslateLocked + self.getController = getController + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index 47d81e1b..152ed248 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -16,10 +16,14 @@ import PeerInfoChatPaneNode import TextFormat import EmojiTextAttachmentView import ComponentFlow +import ComponentDisplayAdapters import TabSelectorComponent import MultilineTextComponent import BottomButtonPanelComponent import UndoUI +import HorizontalTabsComponent +import GlassBackgroundComponent +import EdgeEffect final class PeerInfoPaneWrapper { let key: PeerInfoPaneKey @@ -44,8 +48,6 @@ final class PeerInfoPaneWrapper { } private final class GiftsTabItemComponent: Component { - typealias EnvironmentType = TabSelectorComponent.ItemEnvironment - let context: AccountContext let icons: [ProfileGiftsContext.State.StarGift] let title: String @@ -83,22 +85,19 @@ private final class GiftsTabItemComponent: Component { private var component: GiftsTabItemComponent? - func update(component: GiftsTabItemComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { + func update(component: GiftsTabItemComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component - let environment = environment[EnvironmentType.self].value - let textSpacing: CGFloat = 2.0 let iconSpacing: CGFloat = 1.0 - let normalColor = component.theme.list.itemSecondaryTextColor - let selectedColor = component.theme.list.itemAccentColor - let effectiveColor = normalColor.mixedWith(selectedColor, alpha: environment.selectionFraction) + let normalColor = component.theme.chat.inputPanel.panelControlColor + let effectiveColor = normalColor let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.title, font: Font.medium(14.0), textColor: effectiveColor)) + text: .plain(NSAttributedString(string: component.title, font: Font.medium(15.0), textColor: effectiveColor)) )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) @@ -399,292 +398,6 @@ private func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGF 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))) } -final class PeerInfoPaneTabsContainerNode: ASDisplayNode { - private let context: AccountContext - private let scrollNode: ASScrollNode - private var paneNodes: [PeerInfoPaneKey: PeerInfoPaneTabsContainerPaneNode] = [:] - private let selectedLineNode: ASImageNode - - private var currentParams: ([PeerInfoPaneSpecifier], PeerInfoPaneKey?, Bool, PresentationData)? - - var requestSelectPane: ((PeerInfoPaneKey) -> Void)? - - init(context: AccountContext) { - self.context = context - self.scrollNode = ASScrollNode() - - self.selectedLineNode = ASImageNode() - self.selectedLineNode.displaysAsynchronously = false - self.selectedLineNode.displayWithoutProcessing = true - - super.init() - - self.scrollNode.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in - guard let strongSelf = self else { - return false - } - return strongSelf.scrollNode.view.contentOffset.x > .ulpOfOne - } - self.scrollNode.view.showsHorizontalScrollIndicator = false - self.scrollNode.view.scrollsToTop = false - if #available(iOS 11.0, *) { - self.scrollNode.view.contentInsetAdjustmentBehavior = .never - } - - self.addSubnode(self.scrollNode) - self.scrollNode.addSubnode(self.selectedLineNode) - } - - func update(size: CGSize, presentationData: PresentationData, paneList: [PeerInfoPaneSpecifier], selectedPane: PeerInfoPaneKey?, disableSwitching: Bool, transitionFraction: CGFloat, transition: ContainedViewLayoutTransition) { - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) - - let focusOnSelectedPane = self.currentParams?.1 != selectedPane - - if self.currentParams?.3.theme !== presentationData.theme { - self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(presentationData.theme.list.itemAccentColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width))) - })?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 1) - } - - if self.currentParams?.0 != paneList || self.currentParams?.1 != selectedPane || self.currentParams?.3 !== presentationData { - for specifier in paneList { - let paneNode: PeerInfoPaneTabsContainerPaneNode - if let current = self.paneNodes[specifier.key] { - paneNode = current - } else { - paneNode = PeerInfoPaneTabsContainerPaneNode(pressed: { [weak self] in - self?.paneSelected(specifier.key) - }) - self.paneNodes[specifier.key] = paneNode - } - paneNode.updateText(context: self.context, title: specifier.title, icons: specifier.icons, isSelected: selectedPane == specifier.key, presentationData: presentationData) - } - var removeKeys: [PeerInfoPaneKey] = [] - for (key, _) in self.paneNodes { - if !paneList.contains(where: { $0.key == key }) { - removeKeys.append(key) - } - } - for key in removeKeys { - if let paneNode = self.paneNodes.removeValue(forKey: key) { - paneNode.removeFromSupernode() - } - } - } - self.currentParams = (paneList, selectedPane, disableSwitching, presentationData) - - var tabSizes: [(PeerInfoPaneKey, CGSize, PeerInfoPaneTabsContainerPaneNode, Bool)] = [] - var totalRawTabSize: CGFloat = 0.0 - var selectionFrames: [CGRect] = [] - - for specifier in paneList { - guard let paneNode = self.paneNodes[specifier.key] else { - continue - } - let wasAdded = paneNode.supernode == nil - if wasAdded { - self.scrollNode.addSubnode(paneNode) - } - let paneNodeWidth = paneNode.updateLayout(height: size.height) - let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height) - tabSizes.append((specifier.key, paneNodeSize, paneNode, wasAdded)) - totalRawTabSize += paneNodeSize.width - } - - let minSpacing: CGFloat = 26.0 - if tabSizes.count <= 1 { - for i in 0 ..< tabSizes.count { - let (paneKey, paneNodeSize, paneNode, wasAdded) = tabSizes[i] - let leftOffset: CGFloat = 16.0 - - let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - - let paneAlpha: CGFloat - if disableSwitching { - paneAlpha = paneKey == selectedPane ? 1.0 : 0.5 - } else { - paneAlpha = 1.0 - } - - if wasAdded { - paneNode.frame = paneFrame - paneNode.alpha = 0.0 - } else { - transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame) - } - transition.updateAlpha(node: paneNode, alpha: paneAlpha) - - let areaSideInset: CGFloat = 16.0 - paneNode.updateArea(size: paneFrame.size, sideInset: areaSideInset) - paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -areaSideInset, bottom: 0.0, right: -areaSideInset) - - selectionFrames.append(paneFrame) - } - self.scrollNode.view.contentSize = CGSize(width: size.width, height: size.height) - } else if totalRawTabSize + CGFloat(tabSizes.count + 1) * minSpacing <= size.width { - let availableSpace = size.width - let availableSpacing = availableSpace - totalRawTabSize - let perTabSpacing = floor(availableSpacing / CGFloat(tabSizes.count + 1)) - - let normalizedPerTabWidth = floor(availableSpace / CGFloat(tabSizes.count)) - var maxSpacing: CGFloat = 0.0 - var minSpacing: CGFloat = .greatestFiniteMagnitude - for i in 0 ..< tabSizes.count - 1 { - let distanceToNextBoundary = (normalizedPerTabWidth - tabSizes[i].1.width) / 2.0 - let nextDistanceToBoundary = (normalizedPerTabWidth - tabSizes[i + 1].1.width) / 2.0 - let distance = nextDistanceToBoundary + distanceToNextBoundary - maxSpacing = max(distance, maxSpacing) - minSpacing = min(distance, minSpacing) - } - - if minSpacing >= 100.0 || (maxSpacing / minSpacing) < 0.2 { - for i in 0 ..< tabSizes.count { - let (paneKey, paneNodeSize, paneNode, wasAdded) = tabSizes[i] - - let paneFrame = CGRect(origin: CGPoint(x: CGFloat(i) * normalizedPerTabWidth + floor((normalizedPerTabWidth - paneNodeSize.width) / 2.0), y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - - let paneAlpha: CGFloat - if disableSwitching { - paneAlpha = paneKey == selectedPane ? 1.0 : 0.5 - } else { - paneAlpha = 1.0 - } - - if wasAdded { - paneNode.frame = paneFrame - paneNode.alpha = 0.0 - } else { - transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame) - } - - transition.updateAlpha(node: paneNode, alpha: paneAlpha) - - let areaSideInset = floor((normalizedPerTabWidth - paneNodeSize.width) / 2.0) - paneNode.updateArea(size: paneFrame.size, sideInset: areaSideInset) - paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -areaSideInset, bottom: 0.0, right: -areaSideInset) - - selectionFrames.append(paneFrame) - } - } else { - var leftOffset = perTabSpacing - for i in 0 ..< tabSizes.count { - let (paneKey, paneNodeSize, paneNode, wasAdded) = tabSizes[i] - - let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - - let paneAlpha: CGFloat - if disableSwitching { - paneAlpha = paneKey == selectedPane ? 1.0 : 0.5 - } else { - paneAlpha = 1.0 - } - - if wasAdded { - paneNode.frame = paneFrame - paneNode.alpha = 0.0 - } else { - transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame) - } - - transition.updateAlpha(node: paneNode, alpha: paneAlpha) - - let areaSideInset = floor(perTabSpacing / 2.0) - paneNode.updateArea(size: paneFrame.size, sideInset: areaSideInset) - paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -areaSideInset, bottom: 0.0, right: -areaSideInset) - - leftOffset += paneNodeSize.width + perTabSpacing - - selectionFrames.append(paneFrame) - } - } - self.scrollNode.view.contentSize = CGSize(width: size.width, height: size.height) - } else { - let sideInset: CGFloat = 16.0 - var leftOffset: CGFloat = sideInset - for i in 0 ..< tabSizes.count { - let (paneKey, paneNodeSize, paneNode, wasAdded) = tabSizes[i] - let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - - let paneAlpha: CGFloat - if disableSwitching { - paneAlpha = paneKey == selectedPane ? 1.0 : 0.5 - } else { - paneAlpha = 1.0 - } - - if wasAdded { - paneNode.frame = paneFrame - paneNode.alpha = 0.0 - } else { - transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame) - } - - transition.updateAlpha(node: paneNode, alpha: paneAlpha) - - paneNode.updateArea(size: paneFrame.size, sideInset: minSpacing) - paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing, bottom: 0.0, right: -minSpacing) - - selectionFrames.append(paneFrame) - - leftOffset += paneNodeSize.width + minSpacing - } - self.scrollNode.view.contentSize = CGSize(width: leftOffset - minSpacing + sideInset, height: size.height) - } - - var selectedFrame: CGRect? - if let selectedPane = selectedPane, let currentIndex = paneList.firstIndex(where: { $0.key == selectedPane }) { - if currentIndex != 0 && transitionFraction > 0.0 { - let currentFrame = selectionFrames[currentIndex] - let previousFrame = selectionFrames[currentIndex - 1] - selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) - } else if currentIndex != paneList.count - 1 && transitionFraction < 0.0 { - let currentFrame = selectionFrames[currentIndex] - let previousFrame = selectionFrames[currentIndex + 1] - selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) - } else { - selectedFrame = selectionFrames[currentIndex] - } - } - - if let selectedFrame = selectedFrame { - let wasAdded = self.selectedLineNode.isHidden - self.selectedLineNode.isHidden = false - let lineFrame = CGRect(origin: CGPoint(x: selectedFrame.minX, y: size.height - 4.0), size: CGSize(width: selectedFrame.width, height: 4.0)) - 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 focusOnSelectedPane { - if selectedPane == paneList.first?.key { - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size)) - } else if selectedPane == paneList.last?.key { - 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)) - } 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)) - } - } - } else { - self.selectedLineNode.isHidden = true - } - } - - private func paneSelected(_ key: PeerInfoPaneKey) { - guard let currentParams = self.currentParams else { - return - } - if currentParams.2 { - return - } - self.requestSelectPane?(key) - } -} - private final class PeerInfoPendingPane { let pane: PeerInfoPaneWrapper private var disposable: Disposable? @@ -874,11 +587,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat weak var parentController: ViewController? - private let coveringBackgroundNode: NavigationBackgroundNode - private let additionalBackgroundNode: ASDisplayNode - private let separatorNode: ASDisplayNode + let headerContainer: UIView + + private let tabsBackgroundContainer: GlassBackgroundContainerView + private let tabsBackgroundView: GlassBackgroundView private let tabsContainer = ComponentView() - private let tabsSeparatorNode: ASDisplayNode private var didJustReorderTabs = false private var actionPanel: ComponentView? @@ -886,7 +599,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat let isReady = Promise() var didSetIsReady = false - private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, areTabsHidden: Bool, disableTabSwitching: Bool, navigationHeight: CGFloat)? + private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, areTabsHidden: Bool, disableTabSwitching: Bool, navigationHeight: CGFloat)? private(set) var currentPaneKey: PeerInfoPaneKey? var pendingSwitchToPaneKey: PeerInfoPaneKey? @@ -914,6 +627,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat private var initialStoryFolderId: Int64? private var initialGiftCollectionId: Int64? + private var isDraggingTabs: Bool = false private var transitionFraction: CGFloat = 0.0 var selectionPanelNode: PeerInfoSelectionPanelNode? @@ -951,22 +665,15 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat self.initialStoryFolderId = initialStoryFolderId self.initialGiftCollectionId = initialGiftCollectionId - self.additionalBackgroundNode = ASDisplayNode() + self.tabsBackgroundContainer = GlassBackgroundContainerView() + self.tabsBackgroundView = GlassBackgroundView() - self.separatorNode = ASDisplayNode() - self.separatorNode.isLayerBacked = true - - self.coveringBackgroundNode = NavigationBackgroundNode(color: .clear) - self.coveringBackgroundNode.isUserInteractionEnabled = false - - self.tabsSeparatorNode = ASDisplayNode() + self.headerContainer = SparseContainerView() super.init() -// self.addSubnode(self.separatorNode) - self.addSubnode(self.additionalBackgroundNode) - self.addSubnode(self.coveringBackgroundNode) - self.addSubnode(self.tabsSeparatorNode) + self.tabsBackgroundContainer.contentView.addSubview(self.tabsBackgroundView) + self.headerContainer.addSubview(self.tabsBackgroundContainer) } override func didLoad() { @@ -1031,6 +738,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { switch recognizer.state { case .began: + self.isDraggingTabs = true + func cancelContextGestures(view: UIView) { if let gestureRecognizers = view.gestureRecognizers { for gesture in gestureRecognizers { @@ -1046,7 +755,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat cancelContextGestures(view: self.view) case .changed: - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { + if let (size, sideInset, topInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { let translation = recognizer.translation(in: self.view) var transitionFraction = translation.x / size.width if currentIndex <= 0 { @@ -1061,11 +770,13 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat // print(transitionFraction) self.paneTransitionPromise.set(transitionFraction) - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .immediate) + self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .immediate) self.currentPaneUpdated?(false) } case .cancelled, .ended: - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { + self.isDraggingTabs = false + + if let (size, sideInset, topInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { let translation = recognizer.translation(in: self.view) let velocity = recognizer.velocity(in: self.view) var directionIsToRight: Bool? @@ -1089,7 +800,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat } } self.transitionFraction = 0.0 - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.35, curve: .spring)) + self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.35, curve: .spring)) self.currentPaneUpdated?(false) self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil)) @@ -1158,8 +869,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat } } - func openTabContextMenu(key: PeerInfoPaneKey, sourceNode: ASDisplayNode, gesture: ContextGesture?) { - guard let params = self.currentParams, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else { + func openTabContextMenu(key: PeerInfoPaneKey, sourceView: UIView, gesture: ContextGesture?) { + guard let params = self.currentParams, let sourceView = sourceView as? ContextExtractedContentContainingView else { return } @@ -1190,7 +901,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat let contextController = ContextController( presentationData: params.presentationData, - source: .extracted(TabsExtractedContentSource(sourceNode: sourceNode)), + source: .reference(TabsReferenceContentSource(sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture @@ -1198,7 +909,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat self.parentController?.presentInGlobalOverlay(contextController) } - func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, areTabsHidden: Bool, disableTabSwitching: Bool, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, sideInset: CGFloat, topInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, areTabsHidden: Bool, disableTabSwitching: Bool, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { let previousAvailablePanes = self.currentAvailablePanes let availablePanes = data?.availablePanes ?? [] self.currentAvailablePanes = data?.availablePanes @@ -1242,24 +953,23 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat currentIndex = nil } - self.currentParams = (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) + self.currentParams = (size, sideInset, topInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) - transition.updateAlpha(node: self.coveringBackgroundNode, alpha: expansionFraction) + let backgroundColor: UIColor + if self.currentPaneKey == .gifts { + backgroundColor = presentationData.theme.list.blocksBackgroundColor + } else { + backgroundColor = presentationData.theme.list.blocksBackgroundColor.mixedWith(presentationData.theme.list.plainBackgroundColor, alpha: expansionFraction) + } -// transition.updateAlpha(node: self.additionalBackgroundNode, alpha: 1.0 - expansionFraction) - - self.backgroundColor = presentationData.theme.list.plainBackgroundColor - self.coveringBackgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.opaqueBackgroundColor, transition: .immediate) - self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - self.additionalBackgroundNode.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor - self.tabsSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + self.backgroundColor = backgroundColor let isScrollingLockedAtTop = expansionFraction < 1.0 - CGFloat.ulpOfOne - let tabsHeight: CGFloat = 48.0 - let effectiveTabsHeight: CGFloat = areTabsHidden ? 0.0 : tabsHeight + let tabsHeight: CGFloat = 40.0 + let effectiveTabsHeight: CGFloat = areTabsHidden ? 0.0 : (10.0 + tabsHeight + 10.0 + 6.0) - let paneFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) + let paneFrame = CGRect(origin: CGPoint(x: 0.0, y: -topInset), size: CGSize(width: size.width, height: topInset + size.height)) var visiblePaneIndices: [Int] = [] var requiredPendingKeys: [PeerInfoPaneKey] = [] @@ -1330,12 +1040,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat guard let strongSelf = self else { return } - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams { + if let (size, sideInset, topInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams { var transition: ContainedViewLayoutTransition = .immediate if strongSelf.pendingSwitchToPaneKey == key && strongSelf.currentPaneKey != nil { transition = .animated(duration: 0.4, curve: .spring) } - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: transition) + strongSelf.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: transition) } } if leftScope { @@ -1376,14 +1086,14 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat ) self.pendingPanes[key] = pane pane.pane.node.frame = paneFrame - pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: true, transition: .immediate) + pane.pane.update(size: paneFrame.size, topInset: topInset + effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: true, transition: .immediate) let paneNode = pane.pane.node pane.pane.node.tabBarOffsetUpdated = { [weak self, weak paneNode] transition in guard let strongSelf = self, let paneNode = paneNode, let currentPane = strongSelf.currentPane, paneNode === currentPane.node else { return } - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: transition) + if let (size, sideInset, topInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: transition) } } leftScope = true @@ -1392,7 +1102,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat for (key, pane) in self.pendingPanes { pane.pane.node.frame = paneFrame - pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate) + pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight + topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate) if pane.isReady { self.pendingPanes.removeValue(forKey: key) @@ -1445,7 +1155,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat if let index = availablePanes.firstIndex(of: key), let updatedCurrentIndex = updatedCurrentIndex { var paneWasAdded = false if pane.node.supernode == nil { - self.insertSubnode(pane.node, belowSubnode: self.coveringBackgroundNode) + self.insertSubnode(pane.node, at: 0) paneWasAdded = true } let indexOffset = CGFloat(index - updatedCurrentIndex) @@ -1458,7 +1168,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat return } pane.isAnimatingOut = false - if let (_, _, _, _, _, _, _, data, _, _, _) = strongSelf.currentParams { + if let (_, _, _, _, _, _, _, _, data, _, _, _) = strongSelf.currentParams { if let availablePanes = data?.availablePanes, let currentPaneKey = strongSelf.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey), let paneIndex = availablePanes.firstIndex(of: key), abs(paneIndex - currentIndex) <= 1 { } else { if let pane = strongSelf.currentPanes.removeValue(forKey: key) { @@ -1484,21 +1194,23 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat } else { let isAnimatingOut = pane.isAnimatingOut pane.isAnimatingOut = true - paneTransition.updateFrame(node: pane.node, frame: adjustedFrame, completion: isAnimatingOut ? nil : { _ in + paneTransition.updateFrame(node: pane.node, frame: adjustedFrame, completion: isAnimatingOut ? nil : { _ in paneCompletion() }) } - pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition) + pane.update(size: paneFrame.size, topInset: effectiveTabsHeight + topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition) } } var tabsOffset: CGFloat = 0.0 - if let currentPane = self.currentPane { - tabsOffset = currentPane.node.tabBarOffset - } - tabsOffset = max(0.0, min(tabsHeight, tabsOffset)) - if isScrollingLockedAtTop || self.isMediaOnly { - tabsOffset = 0.0 + if !"".isEmpty { + if let currentPane = self.currentPane { + tabsOffset = currentPane.node.tabBarOffset + } + tabsOffset = max(0.0, min(tabsHeight, tabsOffset)) + if isScrollingLockedAtTop || self.isMediaOnly { + tabsOffset = 0.0 + } } var tabsAlpha: CGFloat @@ -1510,13 +1222,6 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat } tabsAlpha *= tabsAlpha - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - tabsOffset), size: CGSize(width: size.width, height: UIScreenPixel))) - transition.updateFrame(node: self.coveringBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - tabsOffset), size: CGSize(width: size.width, height: tabsHeight + UIScreenPixel))) - transition.updateFrame(node: self.additionalBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - tabsOffset), size: CGSize(width: size.width, height: tabsHeight + UIScreenPixel))) - self.coveringBackgroundNode.update(size: self.coveringBackgroundNode.bounds.size, transition: transition) - - transition.updateFrame(node: self.tabsSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: tabsHeight - tabsOffset), size: CGSize(width: size.width, height: UIScreenPixel))) - var canManageTabs = false if let peer = data?.peer { if peer.id == self.context.account.peerId { @@ -1528,130 +1233,145 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat } } - let items: [TabSelectorComponent.Item] = availablePanes.map { key in - let content: TabSelectorComponent.Item.Content - var canReorder = false - switch key { - case .stories: - content = .text(presentationData.strings.PeerInfo_PaneStories) - canReorder = true - case .storyArchive: - content = .text(presentationData.strings.PeerInfo_PaneArchivedStories) - case .botPreview: - content = .text(presentationData.strings.PeerInfo_PaneBotPreviews) - case .media: - content = .text(presentationData.strings.PeerInfo_PaneMedia) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .files: - content = .text(presentationData.strings.PeerInfo_PaneFiles) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .links: - content = .text(presentationData.strings.PeerInfo_PaneLinks) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .voice: - content = .text(presentationData.strings.PeerInfo_PaneVoiceAndVideo) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .gifs: - content = .text(presentationData.strings.PeerInfo_PaneGifs) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .music: - content = .text(presentationData.strings.PeerInfo_PaneAudio) - canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel - case .groupsInCommon: - content = .text(presentationData.strings.PeerInfo_PaneGroups) - case .members: - content = .text(presentationData.strings.PeerInfo_PaneMembers) - case .similarChannels: - content = .text(presentationData.strings.PeerInfo_PaneRecommended) - case .similarBots: - content = .text(presentationData.strings.PeerInfo_PaneRecommendedBots) - case .savedMessagesChats: - content = .text(presentationData.strings.DialogList_TabTitle) - case .savedMessages: - content = .text(presentationData.strings.PeerInfo_SavedMessagesTabTitle) - case .gifts: - var icons: [ProfileGiftsContext.State.StarGift] = [] - if let gifts = data?.profileGiftsContext?.currentState?.gifts.prefix(3) { - icons = Array(gifts) - } - content = .component(AnyComponent( - GiftsTabItemComponent(context: self.context, icons: icons, title: presentationData.strings.PeerInfo_PaneGifts, theme: presentationData.theme) - )) - canReorder = true - } - return TabSelectorComponent.Item(id: key, content: content, isReorderable: false, contextAction: key != availablePanes.first && canManageTabs && canReorder ? { [weak self] node, gesture in - self?.openTabContextMenu(key: key, sourceNode: node, gesture: gesture) - } : nil) - } + let tabsSideInset: CGFloat = sideInset + 16.0 - let tabsContainerSize = CGSize(width: size.width - sideInset * 2.0, height: tabsHeight) + let tabsContainerSize = CGSize(width: size.width - tabsSideInset * 2.0, height: tabsHeight) let tabsContainerEffectiveSize = self.tabsContainer.update( transition: ComponentTransition(transition), - component: AnyComponent(TabSelectorComponent( - colors: TabSelectorComponent.Colors( - foreground: presentationData.theme.list.itemSecondaryTextColor, - selection: presentationData.theme.list.itemAccentColor - ), + component: AnyComponent(HorizontalTabsComponent( + context: self.context, theme: presentationData.theme, - customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(14.0), - spacing: 6.0, - fillWidth: true, - lineSelection: true - ), - items: items, - selectedId: self.currentPaneKey, - setSelectedId: { [weak self] id in - guard let strongSelf = self, let key = id.base as? PeerInfoPaneKey else { - return - } - if strongSelf.currentPaneKey == key { - if let requestExpandTabs = strongSelf.requestExpandTabs, requestExpandTabs() { - } else { - let _ = strongSelf.currentPane?.node.scrollToTop() - } - return - } - if strongSelf.currentPanes[key] != nil { - strongSelf.currentPaneKey = key - - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) - - strongSelf.currentPaneUpdated?(true) - - strongSelf.currentPaneStatusPromise.set(strongSelf.currentPane?.node.status ?? .single(nil)) - strongSelf.nextPaneStatusPromise.set(.single(nil)) - strongSelf.paneTransitionPromise.set(nil) - } - } else if strongSelf.pendingSwitchToPaneKey != key { - strongSelf.pendingSwitchToPaneKey = key - strongSelf.expandOnSwitch = true - - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + tabs: availablePanes.map { paneKey -> HorizontalTabsComponent.Tab in + var canReorder = false + let content: HorizontalTabsComponent.Tab.Content + switch paneKey { + case .stories: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneStories, entities: [], enableAnimations: false)) + canReorder = true + case .storyArchive: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneArchivedStories, entities: [], enableAnimations: false)) + case .botPreview: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneBotPreviews, entities: [], enableAnimations: false)) + case .media: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneMedia, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .files: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneFiles, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .links: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneLinks, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .voice: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneVoiceAndVideo, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .gifs: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneGifs, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .music: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneAudio, entities: [], enableAnimations: false)) + canReorder = self.peerId.namespace == Namespaces.Peer.CloudChannel + case .groupsInCommon: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneGroups, entities: [], enableAnimations: false)) + case .members: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneMembers, entities: [], enableAnimations: false)) + case .similarChannels: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneRecommended, entities: [], enableAnimations: false)) + case .similarBots: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_PaneRecommendedBots, entities: [], enableAnimations: false)) + case .savedMessagesChats: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.DialogList_TabTitle, entities: [], enableAnimations: false)) + case .savedMessages: + content = .title(HorizontalTabsComponent.Tab.Title(text: presentationData.strings.PeerInfo_SavedMessagesTabTitle, entities: [], enableAnimations: false)) + case .gifts: + var icons: [ProfileGiftsContext.State.StarGift] = [] + if let gifts = data?.profileGiftsContext?.currentState?.gifts.prefix(3) { + icons = Array(gifts) } + content = .custom(AnyComponent( + GiftsTabItemComponent(context: self.context, icons: icons, title: presentationData.strings.PeerInfo_PaneGifts, theme: presentationData.theme) + )) + canReorder = true } + + return HorizontalTabsComponent.Tab( + id: AnyHashable(paneKey), + content: content, + badge: nil, + action: { [weak self] in + guard let self else { + return + } + if self.currentPaneKey == paneKey { + if let requestExpandTabs = self.requestExpandTabs, requestExpandTabs() { + } else { + let _ = self.currentPane?.node.scrollToTop() + } + return + } + if self.currentPanes[paneKey] != nil { + self.currentPaneKey = paneKey + + if let (size, sideInset, topInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = self.currentParams { + self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + + self.currentPaneUpdated?(true) + + self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil)) + self.nextPaneStatusPromise.set(.single(nil)) + self.paneTransitionPromise.set(nil) + } + } else if self.pendingSwitchToPaneKey != paneKey { + self.pendingSwitchToPaneKey = paneKey + self.expandOnSwitch = true + + if let (size, sideInset, topInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, disableTabSwitching, navigationHeight) = self.currentParams { + self.update(size: size, sideInset: sideInset, topInset: topInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) + } + } + }, + contextAction: paneKey != availablePanes.first && canManageTabs && canReorder ? { [weak self] sourceView, gesture in + guard let self else { + return + } + self.openTabContextMenu(key: paneKey, sourceView: sourceView, gesture: gesture) + } : nil, + deleteAction: nil + ) }, - transitionFraction: -self.transitionFraction + selectedTab: self.currentPaneKey.flatMap { HorizontalTabsComponent.Tab.Id($0) }, + isEditing: false, + layout: .fit, + liftWhileSwitching: deviceMetrics.type != .tablet )), environment: {}, containerSize: tabsContainerSize ) - let tabContainerFrameOriginX = items.count == 1 ? sideInset : floorToScreenPixels((size.width - tabsContainerEffectiveSize.width) / 2.0) - let tabContainerFrame = CGRect(origin: CGPoint(x: tabContainerFrameOriginX, y: 10.0 - tabsOffset), size: tabsContainerSize) - if let tabsContainerView = self.tabsContainer.view { + + let tabContainerFrameOriginX = floorToScreenPixels((size.width - tabsContainerEffectiveSize.width) / 2.0) + let tabContainerFrame = CGRect(origin: CGPoint(x: tabContainerFrameOriginX, y: 10.0), size: tabsContainerEffectiveSize) + + transition.updateFrame(view: self.tabsBackgroundContainer, frame: tabContainerFrame) + self.tabsBackgroundContainer.update(size: tabContainerFrame.size, isDark: presentationData.theme.overallDarkAppearance, transition: ComponentTransition(transition)) + + transition.updateFrame(view: self.tabsBackgroundView, frame: CGRect(origin: CGPoint(), size: tabContainerFrame.size)) + self.tabsBackgroundView.update(size: tabContainerFrame.size, cornerRadius: tabContainerFrame.height * 0.5, isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: ComponentTransition(transition)) + + ComponentTransition(transition).setAlpha(view: self.tabsBackgroundContainer, alpha: tabsAlpha) + + if let tabsContainerView = self.tabsContainer.view as? HorizontalTabsComponent.View { if tabsContainerView.superview == nil { - self.view.insertSubview(tabsContainerView, belowSubview: self.tabsSeparatorNode.view) + self.tabsBackgroundView.contentView.addSubview(tabsContainerView) + tabsContainerView.setOverlayContainerView(overlayContainerView: self.headerContainer) } - transition.updateFrame(view: tabsContainerView, frame: tabContainerFrame) - transition.updateAlpha(layer: tabsContainerView.layer, alpha: tabsAlpha) + transition.updateFrame(view: tabsContainerView, frame: CGRect(origin: CGPoint(), size: tabContainerFrame.size)) + + tabsContainerView.updateTabSwitchFraction(fraction: self.transitionFraction, isDragging: self.isDraggingTabs, transition: ComponentTransition(transition)) } for (_, pane) in self.pendingPanes { let paneTransition: ContainedViewLayoutTransition = .immediate paneTransition.updateFrame(node: pane.pane.node, frame: paneFrame) - pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: true, transition: paneTransition) + pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight + topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: true, transition: paneTransition) } var removeKeys: [PeerInfoPaneKey] = [] @@ -1717,3 +1437,22 @@ private final class TabsExtractedContentSource: ContextExtractedContentSource { return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) } } + +private final class TabsReferenceContentSource: ContextReferenceContentSource { + let keepInPlace: Bool = true + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center + + private let sourceView: ContextExtractedContentContainingView + + init(sourceView: ContextExtractedContentContainingView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo( + referenceView: self.sourceView.contentView, + contentAreaInScreenSpace: UIScreen.main.bounds, + actionsPosition: .bottom + ) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift new file mode 100644 index 00000000..abde525b --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift @@ -0,0 +1,1730 @@ +import Foundation +import UIKit +import Display +import AccountContext +import TelegramPresentationData +import TelegramCore +import Postbox +import PeerInfoUI +import TextFormat +import PhoneNumberFormat +import SwiftSignalKit +import TelegramStringFormatting +import AsyncDisplayKit +import LocationResources +import AttachmentUI +import WebUI +import AvatarNode +import PeerNameColorItem +import BoostLevelIconComponent + +private let enabledPublicBioEntities: EnabledEntityTypes = [.allUrl, .mention, .hashtag] +private let enabledPrivateBioEntities: EnabledEntityTypes = [.internalUrl, .mention, .hashtag] + +enum InfoSection: Int, CaseIterable { + case groupLocation + case calls + case personalChannel + case peerInfo + case balances + case permissions + case peerInfoTrailing + case peerSettings + case peerMembers + case channelMonoforum + case botAffiliateProgram +} + +func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], chatLocation: ChatLocation, isOpenedFromChat: Bool, isMyProfile: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] { + guard let data = data else { + return [] + } + + var currentPeerInfoSection: InfoSection = .peerInfo + + var items: [InfoSection: [PeerInfoScreenItem]] = [:] + for section in InfoSection.allCases { + items[section] = [] + } + + let bioContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in + interaction.openBioContextMenu(node, gesture) + } + let noteContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in + interaction.openNoteContextMenu(node, gesture) + } + let bioLinkAction: (TextLinkItemActionType, TextLinkItem, ASDisplayNode, CGRect?, Promise?) -> Void = { action, item, _, _, _ in + interaction.performBioLinkAction(action, item) + } + let workingHoursContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in + interaction.openWorkingHoursContextMenu(node, gesture) + } + let businessLocationContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in + interaction.openBusinessLocationContextMenu(node, gesture) + } + let birthdayContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in + interaction.openBirthdayContextMenu(node, gesture) + } + + if let user = data.peer as? TelegramUser { + let ItemCallList = 1000 + let ItemPersonalChannelHeader = 2000 + let ItemPersonalChannel = 2001 + let ItemPhoneNumber = 3000 + let ItemUsername = 3001 + let ItemBirthdate = 3002 + let ItemAbout = 3003 + let ItemNote = 3004 + let ItemAppFooter = 3005 + let ItemAffiliate = 4000 + let ItemAffiliateInfo = 4001 + let ItemBusinessHours = 5000 + let ItemLocation = 5001 + let ItemSendMessage = 6000 + let ItemReport = 6001 + let ItemAddToContacts = 6002 + let ItemBlock = 6003 + let ItemEncryptionKey = 6004 + let ItemBalanceHeader = 7000 + let ItemBalanceTon = 7001 + let ItemBalanceStars = 7002 + let ItemBotPermissionsHeader = 8000 + let ItemBotPermissionsEmojiStatus = 8001 + let ItemBotPermissionsLocation = 8002 + let ItemBotPermissionsBiometry = 8003 + let ItemBotSettings = 9000 + let ItemBotReport = 9001 + let ItemBotAddToChat = 9002 + let ItemBotAddToChatInfo = 9003 + let ItemVerification = 9004 + + if !callMessages.isEmpty { + items[.calls]!.append(PeerInfoScreenCallListItem(id: ItemCallList, messages: callMessages)) + } + + if let personalChannel = data.personalChannel { + let peerId = personalChannel.peer.peerId + var label: String? + if let subscriberCount = personalChannel.subscriberCount { + label = presentationData.strings.Conversation_StatusSubscribers(Int32(subscriberCount)) + } + items[.personalChannel]?.append(PeerInfoScreenHeaderItem(id: ItemPersonalChannelHeader, text: presentationData.strings.Profile_PersonalChannelSectionTitle, label: label)) + items[.personalChannel]?.append(PeerInfoScreenPersonalChannelItem(id: ItemPersonalChannel, context: context, data: personalChannel, controller: { [weak interaction] in + guard let interaction else { + return nil + } + return interaction.getController() + }, action: { [weak interaction] in + guard let interaction else { + return + } + interaction.openChat(peerId) + })) + } + + if let phone = user.phone { + let formattedPhone = formatPhoneNumber(context: context, number: phone) + let label: String + if formattedPhone.hasPrefix("+888 ") { + label = presentationData.strings.UserInfo_AnonymousNumberLabel + } else { + label = presentationData.strings.ContactInfo_PhoneLabelMobile + } + items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemPhoneNumber, label: label, text: formattedPhone, textColor: .accent, action: { node, progress in + interaction.openPhone(phone, node, nil, progress) + }, longTapAction: nil, contextAction: { node, gesture, _ in + interaction.openPhone(phone, node, gesture, nil) + }, requestLayout: { animated in + interaction.requestLayout(animated) + })) + } + if let mainUsername = user.addressName { + var additionalUsernames: String? + let usernames = user.usernames.filter { $0.isActive && $0.username != mainUsername } + if !usernames.isEmpty { + additionalUsernames = presentationData.strings.Profile_AdditionalUsernames(String(usernames.map { "@\($0.username)" }.joined(separator: ", "))).string + } + + items[currentPeerInfoSection]!.append( + PeerInfoScreenLabeledValueItem( + id: ItemUsername, + label: presentationData.strings.Profile_Username, + text: "@\(mainUsername)", + additionalText: additionalUsernames, + textColor: .accent, + icon: .qrCode, + action: { _, progress in + interaction.openUsername(mainUsername, true, progress) + }, linkItemAction: { type, item, _, _, progress in + if case .tap = type { + if case let .mention(username) = item { + interaction.openUsername(String(username[username.index(username.startIndex, offsetBy: 1)...]), false, progress) + } + } + }, iconAction: { + interaction.openQrCode() + }, contextAction: { node, gesture, _ in + interaction.openUsernameContextMenu(node, gesture) + }, requestLayout: { animated in + interaction.requestLayout(animated) + } + ) + ) + } + + if let cachedData = data.cachedData as? CachedUserData { + if let birthday = cachedData.birthday { + var hasBirthdayToday = false + let today = Calendar.current.dateComponents(Set([.day, .month]), from: Date()) + if today.day == Int(birthday.day) && today.month == Int(birthday.month) { + hasBirthdayToday = true + } + + var birthdayAction: ((ASDisplayNode, Promise?) -> Void)? + if isMyProfile { + birthdayAction = { node, _ in + birthdayContextAction(node, nil, nil) + } + } else if hasBirthdayToday && cachedData.disallowedGifts != TelegramDisallowedGifts.All { + birthdayAction = { _, _ in + interaction.openPremiumGift() + } + } + + items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemBirthdate, context: context, label: hasBirthdayToday ? presentationData.strings.UserInfo_BirthdayToday : presentationData.strings.UserInfo_Birthday, text: stringForCompactBirthday(birthday, strings: presentationData.strings, showAge: true), textColor: .primary, leftIcon: hasBirthdayToday ? .birthday : nil, icon: hasBirthdayToday ? .premiumGift : nil, action: birthdayAction, longTapAction: nil, iconAction: { + interaction.openPremiumGift() + }, contextAction: birthdayContextAction, requestLayout: { _ in + })) + } + + var hasAbout = false + if let about = cachedData.about, !about.isEmpty { + hasAbout = true + } + var hasNote = false + if let note = cachedData.note, !note.text.isEmpty { + hasNote = true + } + + var hasWebApp = false + if let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) { + hasWebApp = true + } + + if user.isFake { + items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: "", text: user.botInfo != nil ? presentationData.strings.UserInfo_FakeBotWarning : presentationData.strings.UserInfo_FakeUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: { animated in + interaction.requestLayout(animated) + })) + } else if user.isScam { + items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: { animated in + interaction.requestLayout(animated) + })) + } else if hasAbout || hasNote || hasWebApp { + var actionButton: PeerInfoScreenLabeledValueItem.Button? + if hasWebApp { + actionButton = PeerInfoScreenLabeledValueItem.Button(title: presentationData.strings.PeerInfo_OpenAppButton, action: { + guard let parentController = interaction.getController() else { + return + } + + if let navigationController = parentController.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer { + for controller in minimizedContainer.controllers { + if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == user.id && mainController.source == .generic { + navigationController.maximizeViewController(controller, animated: true) + return + } + } + } + + context.sharedContext.openWebApp( + context: context, + parentController: parentController, + updatedPresentationData: nil, + botPeer: .user(user), + chatPeer: nil, + threadId: nil, + buttonText: "", + url: "", + simple: true, + source: .generic, + skipTermsOfService: true, + payload: nil, + verifyAgeCompletion: nil + ) + }) + } + + if hasAbout || hasWebApp { + var label: String = "" + if let about = cachedData.about, !about.isEmpty { + label = user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo + } + items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: label, text: cachedData.about ?? "", textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.isPremium ? enabledPublicBioEntities : enabledPrivateBioEntities), action: isMyProfile ? { node, _ in + bioContextAction(node, nil, nil) + } : nil, linkItemAction: bioLinkAction, button: actionButton, contextAction: bioContextAction, requestLayout: { animated in + interaction.requestLayout(animated) + })) + } + + if let note = cachedData.note, !note.text.isEmpty { + var entities = note.entities + if context.isPremium { + entities = generateTextEntities(note.text, enabledTypes: [.mention, .hashtag, .allUrl], currentEntities: entities) + } + items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemNote, label: presentationData.strings.PeerInfo_Notes, rightLabel: presentationData.strings.PeerInfo_NotesInfo, text: note.text, entities: entities, handleSpoilers: true, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: []), action: nil, linkItemAction: bioLinkAction, button: nil, contextAction: noteContextAction, requestLayout: { animated in + interaction.requestLayout(animated) + })) + } + + if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { + items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemAppFooter, text: presentationData.strings.PeerInfo_AppFooterAdmin, linkAction: { action in + if case let .tap(url) = action { + context.sharedContext.applicationBindings.openUrl(url) + } + })) + + currentPeerInfoSection = .peerInfoTrailing + } else if actionButton != nil { + items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemAppFooter, text: presentationData.strings.PeerInfo_AppFooter, linkAction: { action in + if case let .tap(url) = action { + context.sharedContext.applicationBindings.openUrl(url) + } + })) + + currentPeerInfoSection = .peerInfoTrailing + } + + if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { + } else { + if let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil { + var canJoinRefProgram = false + if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] { + if let value = value as? Double { + canJoinRefProgram = value != 0.0 + } else if let value = value as? Bool { + canJoinRefProgram = value + } + } + + if canJoinRefProgram { + if items[.botAffiliateProgram] == nil { + items[.botAffiliateProgram] = [] + } + let programTitleValue: String + programTitleValue = "\(formatPermille(starRefProgram.commissionPermille))%" + items[.botAffiliateProgram]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliate, label: .labelBadge(programTitleValue), additionalBadgeLabel: nil, text: presentationData.strings.PeerInfo_ItemAffiliateProgram_Title, icon: PresentationResourcesSettings.affiliateProgram, action: { + interaction.editingOpenAffiliateProgram() + })) + items[.botAffiliateProgram]!.append(PeerInfoScreenCommentItem(id: ItemAffiliateInfo, text: presentationData.strings.PeerInfo_ItemAffiliateProgram_Footer(EnginePeer.user(user).compactDisplayTitle, formatPermille(starRefProgram.commissionPermille)).string)) + } + } + } + } + + if let businessHours = cachedData.businessHours { + items[currentPeerInfoSection]!.append(PeerInfoScreenBusinessHoursItem(id: ItemBusinessHours, label: presentationData.strings.PeerInfo_BusinessHours_Label, businessHours: businessHours, requestLayout: { animated in + interaction.requestLayout(animated) + }, longTapAction: nil, contextAction: workingHoursContextAction)) + } + + if let businessLocation = cachedData.businessLocation { + if let coordinates = businessLocation.coordinates { + let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: coordinates.latitude, longitude: coordinates.longitude, width: 90, height: 90)) + items[currentPeerInfoSection]!.append(PeerInfoScreenAddressItem( + id: ItemLocation, + label: presentationData.strings.PeerInfo_Location_Label, + text: businessLocation.address, + imageSignal: imageSignal, + action: { + interaction.openLocation() + }, + contextAction: businessLocationContextAction + )) + } else { + items[currentPeerInfoSection]!.append(PeerInfoScreenAddressItem( + id: ItemLocation, + label: presentationData.strings.PeerInfo_Location_Label, + text: businessLocation.address, + imageSignal: nil, + action: nil, + contextAction: businessLocationContextAction + )) + } + } + } + + if !isMyProfile { + if let reactionSourceMessageId = reactionSourceMessageId, !data.isContact { + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemSendMessage, text: presentationData.strings.UserInfo_SendMessage, action: { + interaction.openChat(nil) + })) + + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemReport, text: presentationData.strings.ReportPeer_BanAndReport, color: .destructive, action: { + interaction.openReport(.reaction(reactionSourceMessageId)) + })) + } else if let _ = nearbyPeerDistance { + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemSendMessage, text: presentationData.strings.UserInfo_SendMessage, action: { + interaction.openChat(nil) + })) + + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemReport, text: presentationData.strings.ReportPeer_Report, color: .destructive, action: { + interaction.openReport(.user) + })) + } else { + if !data.isContact { + if user.botInfo == nil { + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemAddToContacts, text: presentationData.strings.PeerInfo_AddToContacts, action: { + interaction.openAddContact() + })) + } + } + + var isBlocked = false + if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked { + isBlocked = true + } + + if isBlocked { + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemBlock, text: user.botInfo != nil ? presentationData.strings.Bot_Unblock : presentationData.strings.Conversation_Unblock, action: { + interaction.updateBlocked(false) + })) + } else { + if user.flags.contains(.isSupport) || data.isContact { + } else { + if user.botInfo == nil { + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemBlock, text: presentationData.strings.Conversation_BlockUser, color: .destructive, action: { + interaction.updateBlocked(true) + })) + } + } + } + + if let encryptionKeyFingerprint = data.encryptionKeyFingerprint { + items[currentPeerInfoSection]!.append(PeerInfoScreenDisclosureEncryptionKeyItem(id: ItemEncryptionKey, text: presentationData.strings.Profile_EncryptionKey, fingerprint: encryptionKeyFingerprint, action: { + interaction.openEncryptionKey() + })) + } + + let revenueBalance = data.revenueStatsState?.balances.currentBalance.amount.value ?? 0 + let overallRevenueBalance = data.revenueStatsState?.balances.overallRevenue.amount.value ?? 0 + + let starsBalance = data.starsRevenueStatsState?.balances.currentBalance.amount ?? StarsAmount.zero + let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue.amount ?? StarsAmount.zero + + if overallRevenueBalance > 0 || overallStarsBalance > StarsAmount.zero { + items[.balances]!.append(PeerInfoScreenHeaderItem(id: ItemBalanceHeader, text: presentationData.strings.PeerInfo_BotBalance_Title)) + if overallRevenueBalance > 0 { + let string = "*\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))" + let attributedString = NSMutableAttributedString(string: string, font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), textColor: presentationData.theme.list.itemSecondaryTextColor) + if let range = attributedString.string.range(of: "*") { + attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: false)), range: NSRange(range, in: attributedString.string)) + attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string)) + } + items[.balances]!.append(PeerInfoScreenDisclosureItem(id: ItemBalanceTon, label: .attributedText(attributedString), text: presentationData.strings.PeerInfo_BotBalance_Ton, icon: PresentationResourcesSettings.ton, action: { + interaction.editingOpenRevenue() + })) + } + + if overallStarsBalance > StarsAmount.zero { + let formattedLabel = formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat) + let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) + let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) + let labelColor = presentationData.theme.list.itemSecondaryTextColor + let attributedString = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString + attributedString.insert(NSAttributedString(string: "*", font: labelFont, textColor: labelColor), at: 0) + + if let range = attributedString.string.range(of: "*") { + attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string)) + attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string)) + } + items[.balances]!.append(PeerInfoScreenDisclosureItem(id: ItemBalanceStars, label: .attributedText(attributedString), text: presentationData.strings.PeerInfo_BotBalance_Stars, icon: PresentationResourcesSettings.stars, action: { + interaction.editingOpenStars() + })) + } + } + + if let _ = user.botInfo { + var canManageEmojiStatus = false + if let cachedData = data.cachedData as? CachedUserData, cachedData.flags.contains(.botCanManageEmojiStatus) { + canManageEmojiStatus = true + } + if canManageEmojiStatus || data.webAppPermissions?.emojiStatus?.isRequested == true { + items[.permissions]!.append(PeerInfoScreenSwitchItem(id: ItemBotPermissionsEmojiStatus, text: presentationData.strings.PeerInfo_Permissions_EmojiStatus, value: canManageEmojiStatus, icon: UIImage(bundleImageName: "Chat/Info/Status"), isLocked: false, toggled: { value in + let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: user.id, enabled: value) + |> deliverOnMainQueue).startStandalone() + + let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in + return WebAppPermissionsState(location: current?.location, emojiStatus: WebAppPermissionsState.EmojiStatus(isRequested: true)) + }.startStandalone() + })) + } + if data.webAppPermissions?.location?.isRequested == true || data.webAppPermissions?.location?.isAllowed == true { + items[.permissions]!.append(PeerInfoScreenSwitchItem(id: ItemBotPermissionsLocation, text: presentationData.strings.PeerInfo_Permissions_Geolocation, value: data.webAppPermissions?.location?.isAllowed ?? false, icon: UIImage(bundleImageName: "Chat/Info/Location"), isLocked: false, toggled: { value in + let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in + return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: value), emojiStatus: current?.emojiStatus) + }.startStandalone() + })) + } + if !"".isEmpty { + items[.permissions]!.append(PeerInfoScreenSwitchItem(id: ItemBotPermissionsBiometry, text: presentationData.strings.PeerInfo_Permissions_Biometry, value: true, icon: UIImage(bundleImageName: "Settings/Menu/TouchId"), isLocked: false, toggled: { value in + + })) + } + + if !items[.permissions]!.isEmpty { + items[.permissions]!.insert(PeerInfoScreenHeaderItem(id: ItemBotPermissionsHeader, text: presentationData.strings.PeerInfo_Permissions_Title), at: 0) + } + } + + if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { + items[currentPeerInfoSection]!.append(PeerInfoScreenDisclosureItem(id: ItemBotSettings, label: .none, text: presentationData.strings.Bot_Settings, icon: UIImage(bundleImageName: "Chat/Info/SettingsIcon"), action: { + interaction.openEditing() + })) + } + + if let botInfo = user.botInfo, !botInfo.flags.contains(.canEdit) { + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemBotReport, text: presentationData.strings.ReportPeer_Report, action: { + interaction.openReport(.default) + })) + } + + if let verification = (data.cachedData as? CachedUserData)?.verification { + let description: String + let descriptionString = verification.description + let entities = generateTextEntities(descriptionString, enabledTypes: [.allUrl]) + if let entity = entities.first { + let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) + let url = (descriptionString as NSString).substring(with: range) + description = descriptionString.replacingOccurrences(of: url, with: "[\(url)](\(url))") + } else { + description = descriptionString + } + let attributedPrefix = NSMutableAttributedString(string: " ") + attributedPrefix.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: verification.iconFileId, file: nil), range: NSMakeRange(0, 1)) + + items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemVerification, text: description, attributedPrefix: attributedPrefix, useAccentLinkColor: false, linkAction: { action in + if case let .tap(url) = action, let navigationController = interaction.getController()?.navigationController as? NavigationController { + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) + } + })) + } else if let botInfo = user.botInfo, botInfo.flags.contains(.worksWithGroups) { + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemBotAddToChat, text: presentationData.strings.Bot_AddToChat, color: .accent, action: { + interaction.openAddBotToGroup() + })) + items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemBotAddToChatInfo, text: presentationData.strings.Bot_AddToChatInfo)) + } + } + } + } else if let channel = data.peer as? TelegramChannel { + let ItemUsername = 1 + let ItemUsernameInfo = 2 + let ItemAbout = 3 + let ItemLocationHeader = 4 + let ItemLocation = 5 + let ItemAdmins = 6 + let ItemMembers = 7 + let ItemMemberRequests = 8 + let ItemBalance = 9 + let ItemEdit = 10 + let ItemPeerPersonalChannel = 11 + + if let _ = data.threadData { + let mainUsername: String + if let addressName = channel.addressName { + mainUsername = addressName + } else { + mainUsername = "c/\(channel.id.id._internalGetInt64Value())" + } + + var threadId: Int64 = 0 + if case let .replyThread(message) = chatLocation { + threadId = message.threadId + } + + let linkText = "https://t.me/\(mainUsername)/\(threadId)" + + items[currentPeerInfoSection]!.append( + PeerInfoScreenLabeledValueItem( + id: ItemUsername, + label: presentationData.strings.Channel_LinkItem, + text: linkText, + textColor: .accent, + icon: .qrCode, + action: { _, progress in + interaction.openUsername(linkText, true, progress) + }, longTapAction: { sourceNode in + interaction.openPeerInfoContextMenu(.link(customLink: linkText), sourceNode, nil) + }, linkItemAction: { type, item, _, _, progress in + if case .tap = type { + if case let .mention(username) = item { + interaction.openUsername(String(username.suffix(from: username.index(username.startIndex, offsetBy: 1))), false, progress) + } + } + }, iconAction: { + interaction.openQrCode() + }, requestLayout: { animated in + interaction.requestLayout(animated) + } + ) + ) + if let _ = channel.addressName { + + } else { + items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemUsernameInfo, text: presentationData.strings.PeerInfo_PrivateShareLinkInfo)) + } + } else { + if let location = (data.cachedData as? CachedChannelData)?.peerGeoLocation { + items[.groupLocation]!.append(PeerInfoScreenHeaderItem(id: ItemLocationHeader, text: presentationData.strings.GroupInfo_Location.uppercased())) + + let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) + items[.groupLocation]!.append(PeerInfoScreenAddressItem( + id: ItemLocation, + label: "", + text: location.address.replacingOccurrences(of: ", ", with: "\n"), + imageSignal: imageSignal, + action: { + interaction.openLocation() + } + )) + } + + if let mainUsername = channel.addressName { + var additionalUsernames: String? + let usernames = channel.usernames.filter { $0.isActive && $0.username != mainUsername } + if !usernames.isEmpty { + additionalUsernames = presentationData.strings.Profile_AdditionalUsernames(String(usernames.map { "@\($0.username)" }.joined(separator: ", "))).string + } + + items[currentPeerInfoSection]!.append( + PeerInfoScreenLabeledValueItem( + id: ItemUsername, + label: presentationData.strings.Channel_LinkItem, + text: "https://t.me/\(mainUsername)", + additionalText: additionalUsernames, + textColor: .accent, + icon: .qrCode, + action: { _, progress in + interaction.openUsername(mainUsername, true, progress) + }, longTapAction: { sourceNode in + interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode, nil) + }, linkItemAction: { type, item, sourceNode, sourceRect, progress in + if case .tap = type { + if case let .mention(username) = item { + interaction.openUsername(String(username.suffix(from: username.index(username.startIndex, offsetBy: 1))), false, progress) + } + } else if case .longTap = type { + if case let .mention(username) = item { + interaction.openPeerInfoContextMenu(.link(customLink: username), sourceNode, sourceRect) + } + } + }, iconAction: { + interaction.openQrCode() + }, requestLayout: { animated in + interaction.requestLayout(animated) + } + ) + ) + } + if let cachedData = data.cachedData as? CachedChannelData { + let aboutText: String? + if channel.isFake { + if case .broadcast = channel.info { + aboutText = presentationData.strings.ChannelInfo_FakeChannelWarning + } else { + aboutText = presentationData.strings.GroupInfo_FakeGroupWarning + } + } else if channel.isScam { + if case .broadcast = channel.info { + aboutText = presentationData.strings.ChannelInfo_ScamChannelWarning + } else { + aboutText = presentationData.strings.GroupInfo_ScamGroupWarning + } + } else if let about = cachedData.about, !about.isEmpty { + aboutText = about + } else { + aboutText = nil + } + + if let aboutText = aboutText { + var enabledEntities = enabledPublicBioEntities + if case .group = channel.info { + enabledEntities = enabledPrivateBioEntities + } + items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledEntities), action: isMyProfile ? { node, _ in + bioContextAction(node, nil, nil) + } : nil, linkItemAction: bioLinkAction, contextAction: bioContextAction, requestLayout: { animated in + interaction.requestLayout(animated) + })) + } + + if let verification = (data.cachedData as? CachedChannelData)?.verification { + let description: String + let descriptionString = verification.description + let entities = generateTextEntities(descriptionString, enabledTypes: [.allUrl]) + if let entity = entities.first { + let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) + let url = (descriptionString as NSString).substring(with: range) + description = descriptionString.replacingOccurrences(of: url, with: "[\(url)](\(url))") + } else { + description = descriptionString + } + + let attributedPrefix = NSMutableAttributedString(string: " ") + attributedPrefix.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: verification.iconFileId, file: nil), range: NSMakeRange(0, 1)) + + items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: 800, text: description, attributedPrefix: attributedPrefix, useAccentLinkColor: false, linkAction: { action in + if case let .tap(url) = action, let navigationController = interaction.getController()?.navigationController as? NavigationController { + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) + } + })) + } + + if case .broadcast = channel.info { + var canEditMembers = false + if channel.hasPermission(.banMembers) { + canEditMembers = true + } + if canEditMembers { + if channel.adminRights != nil || channel.flags.contains(.isCreator) { + let adminCount = cachedData.participantsSummary.adminCount ?? 0 + let memberCount = cachedData.participantsSummary.memberCount ?? 0 + + items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text("\(adminCount == 0 ? "" : "\(presentationStringsFormattedNumber(adminCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { + interaction.openParticipantsSection(.admins) + })) + items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text("\(memberCount == 0 ? "" : "\(presentationStringsFormattedNumber(memberCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.Channel_Info_Subscribers, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: { + interaction.openParticipantsSection(.members) + })) + + if let count = data.requests?.count, count > 0 { + items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { + interaction.openParticipantsSection(.memberRequests) + })) + } + } + } + } + + if channel.adminRights != nil || channel.flags.contains(.isCreator) { + let section: InfoSection + if case .group = channel.info { + section = .peerSettings + } else { + section = .peerMembers + } + if cachedData.flags.contains(.canViewRevenue) || cachedData.flags.contains(.canViewStarsRevenue) { + let revenueBalance = data.revenueStatsState?.balances.currentBalance.amount.value ?? 0 + let starsBalance = data.starsRevenueStatsState?.balances.currentBalance.amount ?? StarsAmount.zero + + let overallRevenueBalance = data.revenueStatsState?.balances.overallRevenue.amount.value ?? 0 + let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue.amount ?? StarsAmount.zero + + if overallRevenueBalance > 0 || overallStarsBalance > StarsAmount.zero { + let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) + let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) + let labelColor = presentationData.theme.list.itemSecondaryTextColor + + let attributedString = NSMutableAttributedString() + if overallRevenueBalance > 0 { + attributedString.append(NSAttributedString(string: "#\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))", font: labelFont, textColor: labelColor)) + } + if overallStarsBalance > StarsAmount.zero { + if !attributedString.string.isEmpty { + attributedString.append(NSAttributedString(string: " ", font: labelFont, textColor: labelColor)) + } + attributedString.append(NSAttributedString(string: "*", font: labelFont, textColor: labelColor)) + + let formattedLabel = formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat) + let starsAttributedString = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString + attributedString.append(starsAttributedString) + } + if let range = attributedString.string.range(of: "#") { + attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: false)), range: NSRange(range, in: attributedString.string)) + attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string)) + } + if let range = attributedString.string.range(of: "*") { + attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 1, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string)) + attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string)) + } + + items[section]!.append(PeerInfoScreenDisclosureItem(id: ItemBalance, label: .attributedText(attributedString), text: presentationData.strings.PeerInfo_Bot_Balance, icon: PresentationResourcesSettings.balance, action: { + interaction.openStats(.monetization) + })) + } + } + + let settingsTitle: String + switch channel.info { + case .broadcast: + settingsTitle = presentationData.strings.Channel_Info_Settings + case .group: + settingsTitle = presentationData.strings.Group_Info_Settings + } + items[section]!.append(PeerInfoScreenDisclosureItem(id: ItemEdit, label: .none, text: settingsTitle, icon: UIImage(bundleImageName: "Chat/Info/SettingsIcon"), action: { + interaction.openEditing() + })) + } + + if channel.hasPermission(.manageDirect), let personalChannel = data.personalChannel { + let peerId = personalChannel.peer.peerId + items[.channelMonoforum]?.append(PeerInfoScreenPersonalChannelItem(id: ItemPeerPersonalChannel, context: context, data: personalChannel, controller: { [weak interaction] in + guard let interaction else { + return nil + } + return interaction.getController() + }, action: { [weak interaction] in + guard let interaction else { + return + } + interaction.openChat(peerId) + })) + } + } + } + } else if let group = data.peer as? TelegramGroup { + if let cachedData = data.cachedData as? CachedGroupData { + let aboutText: String? + if group.isFake { + aboutText = presentationData.strings.GroupInfo_FakeGroupWarning + } else if group.isScam { + aboutText = presentationData.strings.GroupInfo_ScamGroupWarning + } else if let about = cachedData.about, !about.isEmpty { + aboutText = about + } else { + aboutText = nil + } + + if let aboutText = aboutText { + items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPrivateBioEntities), action: isMyProfile ? { node, _ in + bioContextAction(node, nil, nil) + } : nil, linkItemAction: bioLinkAction, contextAction: bioContextAction, requestLayout: { animated in + interaction.requestLayout(animated) + })) + } + } + } + + if let peer = data.peer, let members = data.members, case let .shortList(_, memberList) = members { + var canAddMembers = false + if let group = data.peer as? TelegramGroup { + switch group.role { + case .admin, .creator: + canAddMembers = true + case .member: + break + } + if !group.hasBannedPermission(.banAddMembers) { + canAddMembers = true + } + } else if let channel = data.peer as? TelegramChannel { + switch channel.info { + case .broadcast: + break + case .group: + if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) { + canAddMembers = true + } + } + } + + if canAddMembers { + items[.peerMembers]!.append(PeerInfoScreenActionItem(id: 0, text: presentationData.strings.GroupInfo_AddParticipant, color: .accent, icon: UIImage(bundleImageName: "Contact List/AddMemberIcon"), alignment: .peerList, action: { + interaction.openAddMember() + })) + } + + for member in memberList { + let isAccountPeer = member.id == context.account.peerId + items[.peerMembers]!.append(PeerInfoScreenMemberItem(id: member.id, context: .account(context), enclosingPeer: peer, member: member, isAccount: false, action: isAccountPeer ? nil : { action in + switch action { + case .open: + interaction.openPeerInfo(member.peer, true) + case .promote: + interaction.performMemberAction(member, .promote) + case .restrict: + interaction.performMemberAction(member, .restrict) + case .remove: + interaction.performMemberAction(member, .remove) + } + }, openStories: { sourceView in + interaction.performMemberAction(member, .openStories(sourceView: sourceView)) + })) + } + } + + var result: [(AnyHashable, [PeerInfoScreenItem])] = [] + for section in InfoSection.allCases { + if let sectionItems = items[section], !sectionItems.isEmpty { + result.append((section, sectionItems)) + } + } + return result +} + +func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, state: PeerInfoState, chatLocation: ChatLocation, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction) -> [(AnyHashable, [PeerInfoScreenItem])] { + enum Section: Int, CaseIterable { + case notifications + case groupLocation + case peerPublicSettings + case peerNote + case peerDataSettings + case peerVerifySettings + case peerSettings + case linkedMonoforum + case peerAdditionalSettings + case peerActions + } + + var items: [Section: [PeerInfoScreenItem]] = [:] + for section in Section.allCases { + items[section] = [] + } + + if let data = data { + if let user = data.peer as? TelegramUser { + let ItemNote: AnyHashable = AnyHashable("note_edit") + let ItemNoteInfo = 1 + + let ItemSuggestBirthdate = 2 + let ItemSuggestPhoto = 3 + let ItemCustomPhoto = 4 + let ItemReset = 5 + let ItemInfo = 6 + let ItemDelete = 7 + let ItemUsername = 8 + let ItemAffiliateProgram = 9 + + let ItemVerify = 10 + + let ItemIntro = 11 + let ItemCommands = 12 + let ItemBotSettings = 13 + let ItemBotInfo = 14 + + if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: presentationData.strings.PeerInfo_Bot_Username, icon: PresentationResourcesSettings.bot, action: { + interaction.editingOpenPublicLinkSetup() + })) + + var canSetupRefProgram = false + if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_program_allowed"] { + if let value = value as? Double { + canSetupRefProgram = value != 0.0 + } else if let value = value as? Bool { + canSetupRefProgram = value + } + } + + if canSetupRefProgram { + let programTitleValue: PeerInfoScreenDisclosureItem.Label + if let cachedData = data.cachedData as? CachedUserData, let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil { + programTitleValue = .labelBadge("\(formatPermille(starRefProgram.commissionPermille))%") + } else { + programTitleValue = .text(presentationData.strings.PeerInfo_ItemAffiliateProgram_ValueOff) + } + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliateProgram, label: programTitleValue, additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.PeerInfo_ItemAffiliateProgram_Title, icon: PresentationResourcesSettings.affiliateProgram, action: { + interaction.editingOpenAffiliateProgram() + })) + } + + if let cachedUserData = data.cachedData as? CachedUserData, let _ = cachedUserData.botInfo?.verifierSettings { + items[.peerVerifySettings]!.append(PeerInfoScreenActionItem(id: ItemVerify, text: presentationData.strings.PeerInfo_VerifyAccounts, icon: UIImage(bundleImageName: "Peer Info/BotVerify"), action: { + interaction.editingOpenVerifyAccounts() + })) + } + + items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemIntro, text: presentationData.strings.PeerInfo_Bot_EditIntro, icon: UIImage(bundleImageName: "Peer Info/BotIntro"), action: { + interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-intro", behavior: .interactive))) + })) + items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemCommands, text: presentationData.strings.PeerInfo_Bot_EditCommands, icon: UIImage(bundleImageName: "Peer Info/BotCommands"), action: { + interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-commands", behavior: .interactive))) + })) + items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemBotSettings, text: presentationData.strings.PeerInfo_Bot_ChangeSettings, icon: UIImage(bundleImageName: "Peer Info/BotSettings"), action: { + interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: user.addressName ?? "", behavior: .interactive))) + })) + items[.peerSettings]!.append(PeerInfoScreenCommentItem(id: ItemBotInfo, text: presentationData.strings.PeerInfo_Bot_BotFatherInfo, linkAction: { _ in + interaction.openPeerMention("botfather", .default) + })) + } else if !user.flags.contains(.isSupport) { + let compactName = EnginePeer(user).compactDisplayTitle + + if let cachedData = data.cachedData as? CachedUserData { + items[.peerNote]!.append(PeerInfoScreenNoteListItem( + id: ItemNote, + initialValue: chatInputStateStringWithAppliedEntities(cachedData.note?.text ?? "", entities: cachedData.note?.entities ?? []), + valueUpdated: { value in + interaction.updateNote(value) + }, + requestLayout: { animated in + interaction.requestLayout(animated) + } + )) + + items[.peerNote]!.append(PeerInfoScreenCommentItem(id: ItemNoteInfo, text: presentationData.strings.PeerInfo_AddNotesInfo)) + + if let _ = cachedData.sendPaidMessageStars { + + } else { + if cachedData.birthday == nil { + items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggestBirthdate, text: presentationData.strings.UserInfo_SuggestBirthdate, color: .accent, icon: UIImage(bundleImageName: "Contact List/AddBirthdayIcon"), action: { + interaction.suggestBirthdate() + })) + } + + items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggestPhoto, text: presentationData.strings.UserInfo_SuggestPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Peer Info/SuggestAvatar"), action: { + interaction.suggestPhoto() + })) + } + } + + let setText: String + if user.photo.first?.isPersonal == true || state.updatingAvatar != nil { + setText = presentationData.strings.UserInfo_ChangeCustomPhoto(compactName).string + } else { + setText = presentationData.strings.UserInfo_SetCustomPhoto(compactName).string + } + + items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemCustomPhoto, text: setText, color: .accent, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: { + interaction.setCustomPhoto() + })) + + if user.photo.first?.isPersonal == true || state.updatingAvatar != nil { + var representation: TelegramMediaImageRepresentation? + var originalIsVideo: Bool? + if let cachedData = data.cachedData as? CachedUserData, case let .known(photo) = cachedData.photo { + representation = photo?.representationForDisplayAtSize(PixelDimensions(width: 28, height: 28)) + originalIsVideo = !(photo?.videoRepresentations.isEmpty ?? true) + } + + let removeText: String + if let originalIsVideo { + removeText = originalIsVideo ? presentationData.strings.UserInfo_ResetCustomVideo : presentationData.strings.UserInfo_ResetCustomPhoto + } else { + removeText = user.photo.first?.hasVideo == true ? presentationData.strings.UserInfo_RemoveCustomVideo : presentationData.strings.UserInfo_RemoveCustomPhoto + } + + let imageSignal: Signal + if let representation, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(user), authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: 28.0, height: 28.0)) { + imageSignal = signal + |> map { data -> UIImage? in + return data?.0 + } + } else { + imageSignal = peerAvatarCompleteImage(account: context.account, peer: EnginePeer(user), forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0)) + } + + items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemReset, text: removeText, color: .accent, icon: nil, iconSignal: imageSignal, action: { + interaction.resetCustomPhoto() + })) + } + items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemInfo, text: presentationData.strings.UserInfo_CustomPhotoInfo(compactName).string)) + } + + if data.isContact { + items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemDelete, text: presentationData.strings.UserInfo_DeleteContact, color: .destructive, action: { + interaction.requestDeleteContact() + })) + } + } else if let channel = data.peer as? TelegramChannel { + switch channel.info { + case .broadcast: + let ItemUsername = 1 + let ItemPeerColor = 2 + let ItemInviteLinks = 3 + let ItemDiscussionGroup = 4 + let ItemDeleteChannel = 5 + let ItemReactions = 6 + let ItemAdmins = 7 + let ItemMembers = 8 + let ItemMemberRequests = 9 + let ItemStats = 10 + let ItemBanned = 11 + let ItemRecentActions = 12 + let ItemAffiliatePrograms = 13 + let ItemPostSuggestionsSettings = 14 + let ItemPeerAutoTranslate = 15 + + let isCreator = channel.flags.contains(.isCreator) + + if isCreator { + let linkText: String + if let _ = channel.addressName { + linkText = presentationData.strings.Channel_Setup_TypePublic + } else { + linkText = presentationData.strings.Channel_Setup_TypePrivate + } + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(linkText), text: presentationData.strings.Channel_TypeSetup_Title, icon: UIImage(bundleImageName: "Chat/Info/GroupChannelIcon"), action: { + interaction.editingOpenPublicLinkSetup() + })) + } + + if (isCreator && (channel.addressName?.isEmpty ?? true)) || (!channel.flags.contains(.isCreator) && channel.adminRights?.rights.contains(.canInviteUsers) == true) { + let invitesText: String + if let count = data.invitations?.count, count > 0 { + invitesText = "\(count)" + } else { + invitesText = "" + } + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { + interaction.editingOpenInviteLinksSetup() + })) + } + + if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { + let discussionGroupTitle: String + if let _ = data.cachedData as? CachedChannelData { + if let peer = data.linkedDiscussionPeer { + if let addressName = peer.addressName, !addressName.isEmpty { + discussionGroupTitle = "@\(addressName)" + } else { + discussionGroupTitle = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + } + } else { + discussionGroupTitle = presentationData.strings.Channel_DiscussionGroupAdd + } + } else { + discussionGroupTitle = "..." + } + + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: { + interaction.editingOpenDiscussionGroupSetup() + })) + } + + if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { + let label: String + if let cachedData = data.cachedData as? CachedChannelData, case let .known(reactionSettings) = cachedData.reactionSettings { + switch reactionSettings.allowedReactions { + case .all: + label = presentationData.strings.PeerInfo_LabelAllReactions + case .empty: + if let starsAllowed = reactionSettings.starsAllowed, starsAllowed { + label = "1" + } else { + label = presentationData.strings.PeerInfo_ReactionsDisabled + } + case let .limited(reactions): + var countValue = reactions.count + if let starsAllowed = reactionSettings.starsAllowed, starsAllowed { + countValue += 1 + } + label = "\(countValue)" + } + } else { + label = "" + } + let additionalBadgeLabel: String? = nil + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), additionalBadgeLabel: additionalBadgeLabel, text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { + interaction.editingOpenReactionsSetup() + })) + } + + if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { + var colors: [PeerNameColors.Colors] = [] + if let nameColor = channel.nameColor.flatMap({ context.peerNameColors.get($0, dark: presentationData.theme.overallDarkAppearance) }) { + colors.append(nameColor) + } + if let profileColor = channel.profileColor.flatMap({ context.peerNameColors.getProfile($0, dark: presentationData.theme.overallDarkAppearance, subject: .palette) }) { + colors.append(profileColor) + } + let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors) + + var boostIcon: UIImage? + if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel < 1 { + boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Info_BoostLevelPlusBadge("1").string) + } else { + /*let labelText = NSAttributedString(string: presentationData.strings.Settings_New, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) + let labelBounds = labelText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil) + let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height)) + let badgeSize = CGSize(width: labelSize.width + 8.0, height: labelSize.height + 2.0 + 1.0) + boostIcon = generateImage(badgeSize, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let rect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - UIScreenPixel * 2.0)) + + context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 5.0).cgPath) + context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor) + context.fillPath() + + UIGraphicsPushContext(context) + labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel)) + UIGraphicsPopContext() + })*/ + } + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: presentationData.strings.Channel_Info_AppearanceItem, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: { + interaction.editingOpenNameColorSetup() + })) + + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + var isLocked = true + if let boostLevel = boostStatus?.level, boostLevel >= BoostSubject.autoTranslate.requiredLevel(group: false, context: context, configuration: premiumConfiguration) { + isLocked = false + } + items[.peerSettings]!.append(PeerInfoScreenSwitchItem(id: ItemPeerAutoTranslate, text: presentationData.strings.Channel_Info_AutoTranslate, value: channel.flags.contains(.autoTranslateEnabled), icon: UIImage(bundleImageName: "Settings/Menu/AutoTranslate"), isLocked: isLocked, toggled: { value in + if isLocked { + interaction.displayAutoTranslateLocked() + } else { + interaction.editingToggleAutoTranslate(value) + } + })) + } + + if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { + let labelString: NSAttributedString + if channel.linkedMonoforumId != nil { + if let monoforumPeer = data.linkedMonoforumPeer as? TelegramChannel { + if let sendPaidMessageStars = monoforumPeer.sendPaidMessageStars { + let formattedLabel = formatStarsAmountText(sendPaidMessageStars, dateTimeFormat: presentationData.dateTimeFormat) + let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) + let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) + let labelColor = presentationData.theme.list.itemSecondaryTextColor + let attributedString = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString + attributedString.insert(NSAttributedString(string: "*", font: labelFont, textColor: labelColor), at: 0) + + if let range = attributedString.string.range(of: "*") { + attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string)) + attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string)) + } + labelString = attributedString + } else { + let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) + let labelColor = presentationData.theme.list.itemSecondaryTextColor + + labelString = NSAttributedString(string: presentationData.strings.PeerInfo_AllowChannelMessages_Free, font: labelFont, textColor: labelColor) + } + } else { + let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) + let labelColor = presentationData.theme.list.itemSecondaryTextColor + + labelString = NSAttributedString(string: " ", font: labelFont, textColor: labelColor) + } + } else { + let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) + let labelColor = presentationData.theme.list.itemSecondaryTextColor + + labelString = NSAttributedString(string: presentationData.strings.PeerInfo_AllowChannelMessages_Off, font: labelFont, textColor: labelColor) + } + + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .attributedText(labelString), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.PeerInfo_AllowChannelMessages, icon: PresentationResourcesSettings.channelMessages, action: { + interaction.editingOpenPostSuggestionsSetup() + })) + + if let personalChannel = data.personalChannel { + let peerId = personalChannel.peer.peerId + items[.linkedMonoforum]?.append(PeerInfoScreenPersonalChannelItem(id: 1, context: context, data: personalChannel, controller: { [weak interaction] in + guard let interaction else { + return nil + } + return interaction.getController() + }, action: { [weak interaction] in + guard let interaction else { + return + } + interaction.openChat(peerId) + })) + } + } + + var canEditMembers = false + if channel.hasPermission(.banMembers) && (channel.adminRights != nil || channel.flags.contains(.isCreator)) { + canEditMembers = true + } + if canEditMembers { + let adminCount: Int32 + let memberCount: Int32 + if let cachedData = data.cachedData as? CachedChannelData { + adminCount = cachedData.participantsSummary.adminCount ?? 0 + memberCount = cachedData.participantsSummary.memberCount ?? 0 + } else { + adminCount = 0 + memberCount = 0 + } + + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text("\(adminCount == 0 ? "" : "\(presentationStringsFormattedNumber(adminCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { + interaction.openParticipantsSection(.admins) + })) + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text("\(memberCount == 0 ? "" : "\(presentationStringsFormattedNumber(memberCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.Channel_Info_Subscribers, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: { + interaction.openParticipantsSection(.members) + })) + + if let count = data.requests?.count, count > 0 { + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { + interaction.openParticipantsSection(.memberRequests) + })) + } + } + + if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) { + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStats, label: .none, text: presentationData.strings.Channel_Info_Stats, icon: UIImage(bundleImageName: "Chat/Info/StatsIcon"), action: { + interaction.openStats(.stats) + })) + } + + if canEditMembers { + let bannedCount: Int32 + if let cachedData = data.cachedData as? CachedChannelData { + bannedCount = cachedData.participantsSummary.kickedCount ?? 0 + } else { + bannedCount = 0 + } + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemBanned, label: .text("\(bannedCount == 0 ? "" : "\(presentationStringsFormattedNumber(bannedCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: { + interaction.openParticipantsSection(.banned) + })) + + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRecentActions, label: .none, text: presentationData.strings.Group_Info_AdminLog, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), action: { + interaction.openRecentActions() + })) + } + + if channel.hasPermission(.changeInfo) { + var canJoinRefProgram = false + if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] { + if let value = value as? Double { + canJoinRefProgram = value != 0.0 + } else if let value = value as? Bool { + canJoinRefProgram = value + } + } + + if canJoinRefProgram { + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliatePrograms, label: .text(""), additionalBadgeLabel: nil, text: presentationData.strings.PeerInfo_ItemAffiliatePrograms_Title, icon: PresentationResourcesSettings.affiliateProgram, action: { + interaction.editingOpenAffiliateProgram() + })) + } + } + + if isCreator { //if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canDeleteHistory) { + items[.peerActions]!.append(PeerInfoScreenActionItem(id: ItemDeleteChannel, text: presentationData.strings.ChannelInfo_DeleteChannel, color: .destructive, icon: nil, alignment: .natural, action: { + interaction.openDeletePeer() + })) + } + case .group: + let ItemUsername = 101 + let ItemInviteLinks = 102 + let ItemLinkedChannel = 103 + let ItemPreHistory = 104 + let ItemMembers = 106 + let ItemPermissions = 107 + let ItemAdmins = 108 + let ItemMemberRequests = 109 + let ItemRemovedUsers = 110 + let ItemRecentActions = 111 + let ItemLocationHeader = 112 + let ItemLocation = 113 + let ItemLocationSetup = 114 + let ItemDeleteGroup = 115 + let ItemReactions = 116 + let ItemTopics = 117 + let ItemTopicsText = 118 + let ItemAppearance = 119 + + let isCreator = channel.flags.contains(.isCreator) + let isPublic = channel.addressName != nil + + if let cachedData = data.cachedData as? CachedChannelData { + if isCreator, let location = cachedData.peerGeoLocation { + items[.groupLocation]!.append(PeerInfoScreenHeaderItem(id: ItemLocationHeader, text: presentationData.strings.GroupInfo_Location.uppercased())) + + let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) + items[.groupLocation]!.append(PeerInfoScreenAddressItem( + id: ItemLocation, + label: "", + text: location.address.replacingOccurrences(of: ", ", with: "\n"), + imageSignal: imageSignal, + action: { + interaction.openLocation() + } + )) + if cachedData.flags.contains(.canChangePeerGeoLocation) { + items[.groupLocation]!.append(PeerInfoScreenActionItem(id: ItemLocationSetup, text: presentationData.strings.Group_Location_ChangeLocation, action: { + interaction.editingOpenSetupLocation() + })) + } + } + + if isCreator || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) { + if cachedData.peerGeoLocation != nil { + if isCreator { + let linkText: String + if let username = channel.addressName { + linkText = "@\(username)" + } else { + linkText = presentationData.strings.GroupInfo_PublicLinkAdd + } + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(linkText), text: presentationData.strings.GroupInfo_PublicLink, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { + interaction.editingOpenPublicLinkSetup() + })) + } + } else { + if cachedData.flags.contains(.canChangeUsername) { + items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(isPublic ? presentationData.strings.Group_Setup_TypePublic : presentationData.strings.Group_Setup_TypePrivate), text: presentationData.strings.GroupInfo_GroupType, icon: UIImage(bundleImageName: "Chat/Info/GroupTypeIcon"), action: { + interaction.editingOpenPublicLinkSetup() + })) + } + } + } + + if (isCreator && (channel.addressName?.isEmpty ?? true) && cachedData.peerGeoLocation == nil) || (!isCreator && channel.adminRights?.rights.contains(.canInviteUsers) == true) { + let invitesText: String + if let count = data.invitations?.count, count > 0 { + invitesText = "\(count)" + } else { + invitesText = "" + } + + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { + interaction.editingOpenInviteLinksSetup() + })) + } + + if (isCreator || (channel.adminRights != nil && channel.hasPermission(.pinMessages))) && cachedData.peerGeoLocation == nil { + if let linkedDiscussionPeer = data.linkedDiscussionPeer { + let peerTitle: String + if let addressName = linkedDiscussionPeer.addressName, !addressName.isEmpty { + peerTitle = "@\(addressName)" + } else { + peerTitle = EnginePeer(linkedDiscussionPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + } + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemLinkedChannel, label: .text(peerTitle), text: presentationData.strings.Group_LinkedChannel, icon: UIImage(bundleImageName: "Chat/Info/GroupLinkedChannelIcon"), action: { + interaction.editingOpenDiscussionGroupSetup() + })) + } + + if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { + let label: String + if let cachedData = data.cachedData as? CachedChannelData, case let .known(reactionSettings) = cachedData.reactionSettings { + switch reactionSettings.allowedReactions { + case .all: + label = presentationData.strings.PeerInfo_LabelAllReactions + case .empty: + label = presentationData.strings.PeerInfo_ReactionsDisabled + case let .limited(reactions): + label = "\(reactions.count)" + } + } else { + label = "" + } + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { + interaction.editingOpenReactionsSetup() + })) + } + } else { + if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { + let label: String + if let cachedData = data.cachedData as? CachedChannelData, case let .known(reactionSettings) = cachedData.reactionSettings { + switch reactionSettings.allowedReactions { + case .all: + label = presentationData.strings.PeerInfo_LabelAllReactions + case .empty: + label = presentationData.strings.PeerInfo_ReactionsDisabled + case let .limited(reactions): + label = "\(reactions.count)" + } + } else { + label = "" + } + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { + interaction.editingOpenReactionsSetup() + })) + } + } + + if isCreator || channel.adminRights?.rights.contains(.canChangeInfo) == true { + var colors: [PeerNameColors.Colors] = [] + if let nameColor = channel.nameColor.flatMap({ context.peerNameColors.get($0, dark: presentationData.theme.overallDarkAppearance) }) { + colors.append(nameColor) + } + if let profileColor = channel.profileColor.flatMap({ context.peerNameColors.getProfile($0, dark: presentationData.theme.overallDarkAppearance, subject: .palette) }) { + colors.append(profileColor) + } + let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors) + + var boostIcon: UIImage? + if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel < 1 { + boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Info_BoostLevelPlusBadge("1").string) + } else { + boostIcon = nil + /*let labelText = NSAttributedString(string: presentationData.strings.Settings_New, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) + let labelBounds = labelText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil) + let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height)) + let badgeSize = CGSize(width: labelSize.width + 8.0, height: labelSize.height + 2.0 + 1.0) + boostIcon = generateImage(badgeSize, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let rect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - UIScreenPixel * 2.0)) + + context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 5.0).cgPath) + context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor) + context.fillPath() + + UIGraphicsPushContext(context) + labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel)) + UIGraphicsPopContext() + })*/ + } + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAppearance, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: presentationData.strings.Channel_Info_AppearanceItem, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: { + interaction.editingOpenNameColorSetup() + })) + } + + if (isCreator || (channel.adminRights != nil && channel.hasPermission(.banMembers))) && cachedData.peerGeoLocation == nil, !isPublic, case .known(nil) = cachedData.linkedDiscussionPeerId, !channel.isForumOrMonoForum { + items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(cachedData.flags.contains(.preHistoryEnabled) ? presentationData.strings.GroupInfo_GroupHistoryVisible : presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistoryShort, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: { + interaction.editingOpenPreHistorySetup() + })) + } + + if isCreator, let appConfiguration = data.appConfiguration { + var minParticipants = 200 + if let data = appConfiguration.data, let value = data["forum_upgrade_participants_min"] as? Double { + minParticipants = Int(value) + } + + var canSetupTopics = false + var topicsLimitedReason: TopicsLimitedReason? + if channel.flags.contains(.isForum) { + canSetupTopics = true + } else if case let .known(value) = cachedData.linkedDiscussionPeerId, value != nil { + canSetupTopics = true + topicsLimitedReason = .discussion + } else if let memberCount = cachedData.participantsSummary.memberCount { + canSetupTopics = true + if Int(memberCount) < minParticipants { + topicsLimitedReason = .participants(minParticipants) + } + } + + if canSetupTopics { + let label = channel.flags.contains(.isForum) ? presentationData.strings.PeerInfo_OptionTopics_Enabled : presentationData.strings.PeerInfo_OptionTopics_Disabled + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemTopics, label: .text(label), text: presentationData.strings.PeerInfo_OptionTopics, icon: UIImage(bundleImageName: "Settings/Menu/Topics"), action: { + if let topicsLimitedReason = topicsLimitedReason { + interaction.displayTopicsLimited(topicsLimitedReason) + } else { + interaction.openForumSettings() + } + })) + + items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemTopicsText, text: presentationData.strings.PeerInfo_OptionTopicsText)) + } + } + + var canViewAdminsAndBanned = false + if let _ = channel.adminRights { + canViewAdminsAndBanned = true + } else if channel.flags.contains(.isCreator) { + canViewAdminsAndBanned = true + } + + if canViewAdminsAndBanned { + var activePermissionCount: Int? + if let defaultBannedRights = channel.defaultBannedRights { + var count = 0 + for (right, _) in allGroupPermissionList(peer: .channel(channel), expandMedia: true) { + if right == .banSendMedia { + if banSendMediaSubList().allSatisfy({ !defaultBannedRights.flags.contains($0.0) }) { + count += 1 + } + } else { + if !defaultBannedRights.flags.contains(right) { + count += 1 + } + } + } + activePermissionCount = count + } + + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text(cachedData.participantsSummary.memberCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.Group_Info_Members, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: { + interaction.openParticipantsSection(.members) + })) + if !channel.flags.contains(.isGigagroup) { + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPermissions, label: .text(activePermissionCount.flatMap({ "\($0)/\(allGroupPermissionList(peer: .channel(channel), expandMedia: true).count)" }) ?? ""), text: presentationData.strings.GroupInfo_Permissions, icon: UIImage(bundleImageName: "Settings/Menu/SetPasscode"), action: { + interaction.openPermissions() + })) + } + + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text(cachedData.participantsSummary.adminCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { + interaction.openParticipantsSection(.admins) + })) + + if let count = data.requests?.count, count > 0 { + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { + interaction.openParticipantsSection(.memberRequests) + })) + } + + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRemovedUsers, label: .text(cachedData.participantsSummary.kickedCount.flatMap { $0 > 0 ? "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" : "" } ?? ""), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: { + interaction.openParticipantsSection(.banned) + })) + + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRecentActions, label: .none, text: presentationData.strings.Group_Info_AdminLog, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), action: { + interaction.openRecentActions() + })) + } + + if isCreator { + items[.peerActions]!.append(PeerInfoScreenActionItem(id: ItemDeleteGroup, text: presentationData.strings.Group_DeleteGroup, color: .destructive, icon: nil, alignment: .natural, action: { + interaction.openDeletePeer() + })) + } + } + } + } else if let group = data.peer as? TelegramGroup { + let ItemUsername = 101 + let ItemInviteLinks = 102 + let ItemPreHistory = 103 + let ItemPermissions = 104 + let ItemAdmins = 105 + let ItemMemberRequests = 106 + let ItemReactions = 107 + let ItemTopics = 108 + let ItemTopicsText = 109 + + var canViewAdminsAndBanned = false + + if case .creator = group.role { + if let cachedData = data.cachedData as? CachedGroupData { + if cachedData.flags.contains(.canChangeUsername) { + items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(presentationData.strings.Group_Setup_TypePrivate), text: presentationData.strings.GroupInfo_GroupType, icon: UIImage(bundleImageName: "Chat/Info/GroupTypeIcon"), action: { + interaction.editingOpenPublicLinkSetup() + })) + } + } + + if (group.addressName?.isEmpty ?? true) { + let invitesText: String + if let count = data.invitations?.count, count > 0 { + invitesText = "\(count)" + } else { + invitesText = "" + } + + items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { + interaction.editingOpenInviteLinksSetup() + })) + } + + items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistoryShort, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: { + interaction.editingOpenPreHistorySetup() + })) + + var canSetupTopics = false + if case .creator = group.role { + canSetupTopics = true + } + var topicsLimitedReason: TopicsLimitedReason? + if let appConfiguration = data.appConfiguration { + var minParticipants = 200 + if let data = appConfiguration.data, let value = data["forum_upgrade_participants_min"] as? Double { + minParticipants = Int(value) + } + if Int(group.participantCount) < minParticipants { + topicsLimitedReason = .participants(minParticipants) + } + } + + if canSetupTopics { + items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemTopics, label: .text(presentationData.strings.PeerInfo_OptionTopics_Disabled), text: presentationData.strings.PeerInfo_OptionTopics, icon: UIImage(bundleImageName: "Settings/Menu/Topics"), action: { + if let topicsLimitedReason = topicsLimitedReason { + interaction.displayTopicsLimited(topicsLimitedReason) + } else { + interaction.openForumSettings() + } + })) + + items[.peerPublicSettings]!.append(PeerInfoScreenCommentItem(id: ItemTopicsText, text: presentationData.strings.PeerInfo_OptionTopicsText)) + } + + let label: String + if let cachedData = data.cachedData as? CachedGroupData, case let .known(reactionSettings) = cachedData.reactionSettings { + switch reactionSettings.allowedReactions { + case .all: + label = presentationData.strings.PeerInfo_LabelAllReactions + case .empty: + label = presentationData.strings.PeerInfo_ReactionsDisabled + case let .limited(reactions): + label = "\(reactions.count)" + } + } else { + label = "" + } + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { + interaction.editingOpenReactionsSetup() + })) + + canViewAdminsAndBanned = true + } else if case let .admin(rights, _) = group.role { + let label: String + if let cachedData = data.cachedData as? CachedGroupData, case let .known(reactionSettings) = cachedData.reactionSettings { + switch reactionSettings.allowedReactions { + case .all: + label = presentationData.strings.PeerInfo_LabelAllReactions + case .empty: + label = presentationData.strings.PeerInfo_ReactionsDisabled + case let .limited(reactions): + label = "\(reactions.count)" + } + } else { + label = "" + } + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { + interaction.editingOpenReactionsSetup() + })) + + if rights.rights.contains(.canInviteUsers) { + let invitesText: String + if let count = data.invitations?.count, count > 0 { + invitesText = "\(count)" + } else { + invitesText = "" + } + + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { + interaction.editingOpenInviteLinksSetup() + })) + } + + canViewAdminsAndBanned = true + } + + if canViewAdminsAndBanned { + var activePermissionCount: Int? + if let defaultBannedRights = group.defaultBannedRights { + var count = 0 + for (right, _) in allGroupPermissionList(peer: .legacyGroup(group), expandMedia: true) { + if right == .banSendMedia { + if banSendMediaSubList().allSatisfy({ !defaultBannedRights.flags.contains($0.0) }) { + count += 1 + } + } else { + if !defaultBannedRights.flags.contains(right) { + count += 1 + } + } + } + activePermissionCount = count + } + + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPermissions, label: .text(activePermissionCount.flatMap({ "\($0)/\(allGroupPermissionList(peer: .legacyGroup(group), expandMedia: true).count)" }) ?? ""), text: presentationData.strings.GroupInfo_Permissions, icon: UIImage(bundleImageName: "Settings/Menu/SetPasscode"), action: { + interaction.openPermissions() + })) + + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { + interaction.openParticipantsSection(.admins) + })) + + if let count = data.requests?.count, count > 0 { + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { + interaction.openParticipantsSection(.memberRequests) + })) + } + } + } + } + + var result: [(AnyHashable, [PeerInfoScreenItem])] = [] + for section in Section.allCases { + if let sectionItems = items[section], !sectionItems.isEmpty { + result.append((section, sectionItems)) + } + } + return result +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index ad318df6..379ed713 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -104,7 +104,6 @@ import AttachmentUI import BoostLevelIconComponent import PeerInfoChatPaneNode import PeerInfoChatListPaneNode -import GroupStickerPackSetupController import PeerNameColorItem import PeerSelectionScreen import UIKitRuntimeUtils @@ -124,386 +123,27 @@ public enum PeerInfoAvatarEditingMode { case fallback } -protocol PeerInfoScreenItem: AnyObject { - var id: AnyHashable { get } - func node() -> PeerInfoScreenItemNode -} - -class PeerInfoScreenItemNode: ASDisplayNode, AccessibilityFocusableNode { - var bringToFrontForHighlight: (() -> Void)? - - func update(context: AccountContext, width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, hasCorners: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { - preconditionFailure() - } - - override open func accessibilityElementDidBecomeFocused() { -// (self.supernode as? ListView)?.ensureItemNodeVisible(self, animated: false, overflow: 22.0) - } -} - -private final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode { - private let backgroundNode: ASDisplayNode - private let topSeparatorNode: ASDisplayNode - private let bottomSeparatorNode: ASDisplayNode - private let itemContainerNode: ASDisplayNode - - private var currentItems: [PeerInfoScreenItem] = [] - fileprivate var itemNodes: [AnyHashable: PeerInfoScreenItemNode] = [:] - - override init() { - self.backgroundNode = ASDisplayNode() - self.backgroundNode.isLayerBacked = true - - self.topSeparatorNode = ASDisplayNode() - self.topSeparatorNode.isLayerBacked = true - - self.bottomSeparatorNode = ASDisplayNode() - self.bottomSeparatorNode.isLayerBacked = true - - self.itemContainerNode = ASDisplayNode() - self.itemContainerNode.clipsToBounds = true - - super.init() - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.itemContainerNode) - self.addSubnode(self.topSeparatorNode) - self.addSubnode(self.bottomSeparatorNode) - } - - func update(context: AccountContext, width: CGFloat, safeInsets: UIEdgeInsets, hasCorners: Bool, presentationData: PresentationData, items: [PeerInfoScreenItem], transition: ContainedViewLayoutTransition) -> CGFloat { - self.backgroundNode.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor - self.topSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - - self.topSeparatorNode.isHidden = hasCorners - self.bottomSeparatorNode.isHidden = hasCorners - - var contentHeight: CGFloat = 0.0 - var contentWithBackgroundHeight: CGFloat = 0.0 - var contentWithBackgroundOffset: CGFloat = 0.0 - - for i in 0 ..< items.count { - let item = items[i] - - let itemNode: PeerInfoScreenItemNode - var wasAdded = false - if let current = self.itemNodes[item.id] { - itemNode = current - } else { - wasAdded = true - itemNode = item.node() - self.itemNodes[item.id] = itemNode - self.itemContainerNode.addSubnode(itemNode) - itemNode.bringToFrontForHighlight = { [weak self, weak itemNode] in - guard let strongSelf = self, let itemNode = itemNode else { - return - } - strongSelf.view.bringSubviewToFront(itemNode.view) - } - } - - let itemTransition: ContainedViewLayoutTransition = wasAdded ? .immediate : transition - - let topItem: PeerInfoScreenItem? - if i == 0 { - topItem = nil - } else if items[i - 1] is PeerInfoScreenHeaderItem { - topItem = nil - } else { - topItem = items[i - 1] - } - - let bottomItem: PeerInfoScreenItem? - if i == items.count - 1 { - bottomItem = nil - } else if items[i + 1] is PeerInfoScreenCommentItem { - bottomItem = nil - } else { - bottomItem = items[i + 1] - } - - let itemHeight = itemNode.update(context: context, width: width, safeInsets: safeInsets, presentationData: presentationData, item: item, topItem: topItem, bottomItem: bottomItem, hasCorners: hasCorners, transition: itemTransition) - let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: itemHeight)) - itemTransition.updateFrame(node: itemNode, frame: itemFrame) - if wasAdded { - itemNode.alpha = 0.0 - let alphaTransition: ContainedViewLayoutTransition = transition.isAnimated ? .animated(duration: 0.35, curve: .linear) : .immediate - alphaTransition.updateAlpha(node: itemNode, alpha: 1.0) - } - - if item is PeerInfoScreenCommentItem { - } else { - contentWithBackgroundHeight += itemHeight - } - contentHeight += itemHeight - - if item is PeerInfoScreenHeaderItem { - contentWithBackgroundOffset = contentHeight - } - } - - var removeIds: [AnyHashable] = [] - for (id, _) in self.itemNodes { - if !items.contains(where: { $0.id == id }) { - removeIds.append(id) - } - } - for id in removeIds { - if let itemNode = self.itemNodes.removeValue(forKey: id) { - itemNode.view.superview?.sendSubviewToBack(itemNode.view) - transition.updateAlpha(node: itemNode, alpha: 0.0, completion: { [weak itemNode] _ in - itemNode?.removeFromSupernode() - }) - } - } - - transition.updateFrame(node: self.itemContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight))) - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentWithBackgroundOffset), size: CGSize(width: width, height: max(0.0, contentWithBackgroundHeight - contentWithBackgroundOffset)))) - transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentWithBackgroundOffset - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))) - transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentWithBackgroundHeight), size: CGSize(width: width, height: UIScreenPixel))) - - if contentHeight.isZero { - transition.updateAlpha(node: self.topSeparatorNode, alpha: 0.0) - transition.updateAlpha(node: self.bottomSeparatorNode, alpha: 0.0) - } else { - transition.updateAlpha(node: self.topSeparatorNode, alpha: 1.0) - transition.updateAlpha(node: self.bottomSeparatorNode, alpha: 1.0) - } - - return contentHeight - } - - func animateErrorIfNeeded() { - for (_, itemNode) in self.itemNodes { - if let itemNode = itemNode as? PeerInfoScreenMultilineInputItemNode { - itemNode.animateErrorIfNeeded() - } - } - } -} - -final class PeerInfoSelectionPanelNode: ASDisplayNode { - private let context: AccountContext - private let peerId: PeerId - - private let deleteMessages: () -> Void - private let shareMessages: () -> Void - private let forwardMessages: () -> Void - private let reportMessages: () -> Void - private let displayCopyProtectionTip: (UIView, Bool) -> Void - - let selectionPanel: ChatMessageSelectionInputPanelNode - let separatorNode: ASDisplayNode - let backgroundNode: NavigationBackgroundNode - - var viewForOverlayContent: UIView? { - return self.selectionPanel.viewForOverlayContent - } - - init(context: AccountContext, presentationData: PresentationData, peerId: PeerId, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void, reportMessages: @escaping () -> Void, displayCopyProtectionTip: @escaping (UIView, Bool) -> Void) { - self.context = context - self.peerId = peerId - self.deleteMessages = deleteMessages - self.shareMessages = shareMessages - self.forwardMessages = forwardMessages - self.reportMessages = reportMessages - self.displayCopyProtectionTip = displayCopyProtectionTip - - let presentationData = presentationData - - self.separatorNode = ASDisplayNode() - self.backgroundNode = NavigationBackgroundNode(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor) - - self.selectionPanel = ChatMessageSelectionInputPanelNode(theme: presentationData.theme, strings: presentationData.strings, peerMedia: true) - self.selectionPanel.context = context - - let interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _, _ in - }, setupEditMessage: { _, _ in - }, beginMessageSelection: { _, _ in - }, cancelMessageSelection: { _ in - }, deleteSelectedMessages: { - deleteMessages() - }, reportSelectedMessages: { - reportMessages() - }, reportMessages: { _, _ in - }, blockMessageAuthor: { _, _ in - }, deleteMessages: { _, _, f in - f(.default) - }, forwardSelectedMessages: { - forwardMessages() - }, forwardCurrentForwardMessages: { - }, forwardMessages: { _ in - }, updateForwardOptionsState: { _ in - }, presentForwardOptions: { _ in - }, presentReplyOptions: { _ in - }, presentLinkOptions: { _ in - }, presentSuggestPostOptions: { - }, shareSelectedMessages: { - shareMessages() - }, updateTextInputStateAndMode: { _ in - }, updateInputModeAndDismissedButtonKeyboardMessageId: { _ in - }, openStickers: { - }, editMessage: { - }, beginMessageSearch: { _, _ in - }, dismissMessageSearch: { - }, updateMessageSearch: { _ in - }, openSearchResults: { - }, navigateMessageSearch: { _ in - }, openCalendarSearch: { - }, toggleMembersSearch: { _ in - }, navigateToMessage: { _, _, _, _ in - }, navigateToChat: { _ in - }, navigateToProfile: { _ in - }, openPeerInfo: { - }, togglePeerNotifications: { - }, sendContextResult: { _, _, _, _ in - return false - }, sendBotCommand: { _, _ in - }, sendShortcut: { _ in - }, openEditShortcuts: { - }, sendBotStart: { _ in - }, botSwitchChatWithPayload: { _, _ in - }, beginMediaRecording: { _ in - }, finishMediaRecording: { _ in - }, stopMediaRecording: { - }, lockMediaRecording: { - }, resumeMediaRecording: { - }, deleteRecordedMedia: { - }, sendRecordedMedia: { _, _ in - }, displayRestrictedInfo: { _, _ in - }, displayVideoUnmuteTip: { _ in - }, switchMediaRecordingMode: { - }, setupMessageAutoremoveTimeout: { - }, sendSticker: { _, _, _, _, _, _ in - return false - }, unblockPeer: { - }, pinMessage: { _, _ in - }, unpinMessage: { _, _, _ in - }, unpinAllMessages: { - }, openPinnedList: { _ in - }, shareAccountContact: { - }, reportPeer: { - }, presentPeerContact: { - }, dismissReportPeer: { - }, deleteChat: { - }, beginCall: { _ in - }, toggleMessageStickerStarred: { _ in - }, presentController: { _, _ in - }, presentControllerInCurrent: { _, _ in - }, getNavigationController: { - return nil - }, presentGlobalOverlayController: { _, _ in - }, navigateFeed: { - }, openGrouping: { - }, toggleSilentPost: { - }, requestUnvoteInMessage: { _ in - }, requestStopPollInMessage: { _ in - }, updateInputLanguage: { _ in - }, unarchiveChat: { - }, openLinkEditing: { - }, displaySlowmodeTooltip: { _, _ in - }, displaySendMessageOptions: { _, _ in - }, openScheduledMessages: { - }, openPeersNearby: { - }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: { - }, scrollToTop: { - }, viewReplies: { _, _ in - }, activatePinnedListPreview: { _, _ in - }, joinGroupCall: { _ in - }, presentInviteMembers: { - }, presentGigagroupHelp: { - }, openMonoforum: { - }, editMessageMedia: { _, _ in - }, updateShowCommands: { _ in - }, updateShowSendAsPeers: { _ in - }, openInviteRequests: { - }, openSendAsPeer: { _, _ in - }, presentChatRequestAdminInfo: { - }, displayCopyProtectionTip: { view, save in - displayCopyProtectionTip(view, save) - }, openWebView: { _, _, _, _ in - }, updateShowWebView: { _ in - }, insertText: { _ in - }, backwardsDeleteText: { - }, restartTopic: { - }, toggleTranslation: { _ in - }, changeTranslationLanguage: { _ in - }, addDoNotTranslateLanguage: { _ in - }, hideTranslationPanel: { - }, openPremiumGift: { - }, openSuggestPost: { _, _ in - }, openPremiumRequiredForMessaging: { - }, openStarsPurchase: { _ in - }, openMessagePayment: { - }, openBoostToUnrestrict: { - }, updateRecordingTrimRange: { _, _, _, _ in - }, dismissAllTooltips: { - }, editTodoMessage: { _, _, _ in - }, dismissUrlPreview: { - }, dismissForwardMessages: { - }, dismissSuggestPost: { - }, displayUndo: { _ in - }, sendEmoji: { _, _, _ in - }, updateHistoryFilter: { _ in - }, updateChatLocationThread: { _, _ in - }, toggleChatSidebarMode: { - }, updateDisplayHistoryFilterAsList: { _ in - }, requestLayout: { _ in - }, chatController: { - return nil - }, statuses: nil) - - self.selectionPanel.interfaceInteraction = interfaceInteraction - - super.init() - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.separatorNode) - self.addSubnode(self.selectionPanel) - } - - func update(layout: ContainerViewLayout, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat { - self.backgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor - - let interfaceState = ChatPresentationInterfaceState(chatWallpaper: .color(0), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: .regular, bubbleCorners: PresentationChatBubbleCorners(mainRadius: 16.0, auxiliaryRadius: 8.0, mergeBubbleCorners: true), accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: self.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) - let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height, maxOverlayHeight: layout.size.height, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics, isMediaInputExpanded: false) - - transition.updateFrame(node: self.selectionPanel, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeight))) - - let panelHeightWithInset = panelHeight + layout.intrinsicInsets.bottom - - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeightWithInset))) - self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition) - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - - return panelHeightWithInset - } -} - -private enum PeerInfoBotCommand { +enum PeerInfoBotCommand { case settings case help case privacy } -private enum PeerInfoParticipantsSection { +enum PeerInfoParticipantsSection { case members case admins case banned case memberRequests } -private enum PeerInfoMemberAction { +enum PeerInfoMemberAction { case promote case restrict case remove case openStories(sourceView: UIView) } -private enum PeerInfoContextSubject { +enum PeerInfoContextSubject { case bio case phone(String) case link(customLink: String?) @@ -512,7 +152,7 @@ private enum PeerInfoContextSubject { case birthday } -private enum PeerInfoSettingsSection { +enum PeerInfoSettingsSection { case avatar case edit case proxy @@ -548,2514 +188,82 @@ private enum PeerInfoSettingsSection { case premiumManagement case stars case ton + case ghostgram } -private enum PeerInfoReportType { +enum PeerInfoReportType { case `default` case user case reaction(MessageId) } -private enum TopicsLimitedReason { +enum TopicsLimitedReason { case participants(Int) case discussion } -private final class PeerInfoInteraction { - let openChat: (EnginePeer.Id?) -> Void - let openUsername: (String, Bool, Promise?) -> Void - let openPhone: (String, ASDisplayNode, ContextGesture?, Promise?) -> Void - let editingOpenNotificationSettings: () -> Void - let editingOpenSoundSettings: () -> Void - let editingToggleShowMessageText: (Bool) -> Void - let requestDeleteContact: () -> Void - let suggestBirthdate: () -> Void - let suggestPhoto: () -> Void - let setCustomPhoto: () -> Void - let resetCustomPhoto: () -> Void - let openAddContact: () -> Void - let updateBlocked: (Bool) -> Void - let openReport: (PeerInfoReportType) -> Void - let openShareBot: () -> Void - let openAddBotToGroup: () -> Void - let performBotCommand: (PeerInfoBotCommand) -> Void - let editingOpenPublicLinkSetup: () -> Void - let editingOpenNameColorSetup: () -> Void - let editingOpenInviteLinksSetup: () -> Void - let editingOpenDiscussionGroupSetup: () -> Void - let editingOpenPostSuggestionsSetup: () -> Void - let editingOpenRevenue: () -> Void - let editingOpenStars: () -> Void - let openParticipantsSection: (PeerInfoParticipantsSection) -> Void - let openRecentActions: () -> Void - let openChannelMessages: () -> Void - let openStats: (ChannelStatsSection) -> Void - let editingOpenPreHistorySetup: () -> Void - let editingOpenAutoremoveMesages: () -> Void - let openPermissions: () -> Void - let editingOpenStickerPackSetup: () -> Void - let openLocation: () -> Void - let editingOpenSetupLocation: () -> Void - let openPeerInfo: (Peer, Bool) -> Void - let performMemberAction: (PeerInfoMember, PeerInfoMemberAction) -> Void - let openPeerInfoContextMenu: (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void - let performBioLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void - let requestLayout: (Bool) -> Void - let openEncryptionKey: () -> Void - let openSettings: (PeerInfoSettingsSection) -> Void - let openPaymentMethod: () -> Void - let switchToAccount: (AccountRecordId) -> Void - let logoutAccount: (AccountRecordId) -> Void - let accountContextMenu: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void - let updateBio: (String) -> Void - let updateNote: (NSAttributedString) -> Void - let openDeletePeer: () -> Void - let openFaq: (String?) -> Void - let openAddMember: () -> Void - let openQrCode: () -> Void - let editingOpenReactionsSetup: () -> Void - let dismissInput: () -> Void - let openForumSettings: () -> Void - let displayTopicsLimited: (TopicsLimitedReason) -> Void - let openPeerMention: (String, ChatControllerInteractionNavigateToPeer) -> Void - let openBotApp: (AttachMenuBot) -> Void - let openEditing: () -> Void - let updateBirthdate: (TelegramBirthday??) -> Void - let updateIsEditingBirthdate: (Bool) -> Void - let openBioPrivacy: () -> Void - let openBirthdatePrivacy: () -> Void - let openPremiumGift: () -> Void - let editingOpenPersonalChannel: () -> Void - let openUsernameContextMenu: (ASDisplayNode, ContextGesture?) -> Void - let openBioContextMenu: (ASDisplayNode, ContextGesture?) -> Void - let openNoteContextMenu: (ASDisplayNode, ContextGesture?) -> Void - let openWorkingHoursContextMenu: (ASDisplayNode, ContextGesture?) -> Void - let openBusinessLocationContextMenu: (ASDisplayNode, ContextGesture?) -> Void - let openBirthdayContextMenu: (ASDisplayNode, ContextGesture?) -> Void - let editingOpenAffiliateProgram: () -> Void - let editingOpenVerifyAccounts: () -> Void - let editingToggleAutoTranslate: (Bool) -> Void - let displayAutoTranslateLocked: () -> Void - let getController: () -> ViewController? - - init( - openUsername: @escaping (String, Bool, Promise?) -> Void, - openPhone: @escaping (String, ASDisplayNode, ContextGesture?, Promise?) -> Void, - editingOpenNotificationSettings: @escaping () -> Void, - editingOpenSoundSettings: @escaping () -> Void, - editingToggleShowMessageText: @escaping (Bool) -> Void, - requestDeleteContact: @escaping () -> Void, - suggestBirthdate: @escaping () -> Void, - suggestPhoto: @escaping () -> Void, - setCustomPhoto: @escaping () -> Void, - resetCustomPhoto: @escaping () -> Void, - openChat: @escaping (EnginePeer.Id?) -> Void, - openAddContact: @escaping () -> Void, - updateBlocked: @escaping (Bool) -> Void, - openReport: @escaping (PeerInfoReportType) -> Void, - openShareBot: @escaping () -> Void, - openAddBotToGroup: @escaping () -> Void, - performBotCommand: @escaping (PeerInfoBotCommand) -> Void, - editingOpenPublicLinkSetup: @escaping () -> Void, - editingOpenNameColorSetup: @escaping () -> Void, - editingOpenInviteLinksSetup: @escaping () -> Void, - editingOpenDiscussionGroupSetup: @escaping () -> Void, - editingOpenPostSuggestionsSetup: @escaping () -> Void, - editingOpenRevenue: @escaping () -> Void, - editingOpenStars: @escaping () -> Void, - openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void, - openRecentActions: @escaping () -> Void, - openChannelMessages: @escaping () -> Void, - openStats: @escaping (ChannelStatsSection) -> Void, - editingOpenPreHistorySetup: @escaping () -> Void, - editingOpenAutoremoveMesages: @escaping () -> Void, - openPermissions: @escaping () -> Void, - editingOpenStickerPackSetup: @escaping () -> Void, - openLocation: @escaping () -> Void, - editingOpenSetupLocation: @escaping () -> Void, - openPeerInfo: @escaping (Peer, Bool) -> Void, - performMemberAction: @escaping (PeerInfoMember, PeerInfoMemberAction) -> Void, - openPeerInfoContextMenu: @escaping (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void, - performBioLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, - requestLayout: @escaping (Bool) -> Void, - openEncryptionKey: @escaping () -> Void, - openSettings: @escaping (PeerInfoSettingsSection) -> Void, - openPaymentMethod: @escaping () -> Void, - switchToAccount: @escaping (AccountRecordId) -> Void, - logoutAccount: @escaping (AccountRecordId) -> Void, - accountContextMenu: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void, - updateBio: @escaping (String) -> Void, - updateNote: @escaping (NSAttributedString) -> Void, - openDeletePeer: @escaping () -> Void, - openFaq: @escaping (String?) -> Void, - openAddMember: @escaping () -> Void, - openQrCode: @escaping () -> Void, - editingOpenReactionsSetup: @escaping () -> Void, - dismissInput: @escaping () -> Void, - openForumSettings: @escaping () -> Void, - displayTopicsLimited: @escaping (TopicsLimitedReason) -> Void, - openPeerMention: @escaping (String, ChatControllerInteractionNavigateToPeer) -> Void, - openBotApp: @escaping (AttachMenuBot) -> Void, - openEditing: @escaping () -> Void, - updateBirthdate: @escaping (TelegramBirthday??) -> Void, - updateIsEditingBirthdate: @escaping (Bool) -> Void, - openBioPrivacy: @escaping () -> Void, - openBirthdatePrivacy: @escaping () -> Void, - openPremiumGift: @escaping () -> Void, - editingOpenPersonalChannel: @escaping () -> Void, - openUsernameContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, - openBioContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, - openNoteContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, - openWorkingHoursContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, - openBusinessLocationContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, - openBirthdayContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, - editingOpenAffiliateProgram: @escaping () -> Void, - editingOpenVerifyAccounts: @escaping () -> Void, - editingToggleAutoTranslate: @escaping (Bool) -> Void, - displayAutoTranslateLocked: @escaping () -> Void, - getController: @escaping () -> ViewController? - ) { - self.openUsername = openUsername - self.openPhone = openPhone - self.editingOpenNotificationSettings = editingOpenNotificationSettings - self.editingOpenSoundSettings = editingOpenSoundSettings - self.editingToggleShowMessageText = editingToggleShowMessageText - self.requestDeleteContact = requestDeleteContact - self.suggestBirthdate = suggestBirthdate - self.suggestPhoto = suggestPhoto - self.setCustomPhoto = setCustomPhoto - self.resetCustomPhoto = resetCustomPhoto - self.openChat = openChat - self.openAddContact = openAddContact - self.updateBlocked = updateBlocked - self.openReport = openReport - self.openShareBot = openShareBot - self.openAddBotToGroup = openAddBotToGroup - self.performBotCommand = performBotCommand - self.editingOpenPublicLinkSetup = editingOpenPublicLinkSetup - self.editingOpenNameColorSetup = editingOpenNameColorSetup - self.editingOpenInviteLinksSetup = editingOpenInviteLinksSetup - self.editingOpenDiscussionGroupSetup = editingOpenDiscussionGroupSetup - self.editingOpenPostSuggestionsSetup = editingOpenPostSuggestionsSetup - self.editingOpenRevenue = editingOpenRevenue - self.editingOpenStars = editingOpenStars - self.openParticipantsSection = openParticipantsSection - self.openRecentActions = openRecentActions - self.openChannelMessages = openChannelMessages - self.openStats = openStats - self.editingOpenPreHistorySetup = editingOpenPreHistorySetup - self.editingOpenAutoremoveMesages = editingOpenAutoremoveMesages - self.openPermissions = openPermissions - self.editingOpenStickerPackSetup = editingOpenStickerPackSetup - self.openLocation = openLocation - self.editingOpenSetupLocation = editingOpenSetupLocation - self.openPeerInfo = openPeerInfo - self.performMemberAction = performMemberAction - self.openPeerInfoContextMenu = openPeerInfoContextMenu - self.performBioLinkAction = performBioLinkAction - self.requestLayout = requestLayout - self.openEncryptionKey = openEncryptionKey - self.openSettings = openSettings - self.openPaymentMethod = openPaymentMethod - self.switchToAccount = switchToAccount - self.logoutAccount = logoutAccount - self.accountContextMenu = accountContextMenu - self.updateBio = updateBio - self.updateNote = updateNote - self.openDeletePeer = openDeletePeer - self.openFaq = openFaq - self.openAddMember = openAddMember - self.openQrCode = openQrCode - self.editingOpenReactionsSetup = editingOpenReactionsSetup - self.dismissInput = dismissInput - self.openForumSettings = openForumSettings - self.displayTopicsLimited = displayTopicsLimited - self.openPeerMention = openPeerMention - self.openBotApp = openBotApp - self.openEditing = openEditing - self.updateBirthdate = updateBirthdate - self.updateIsEditingBirthdate = updateIsEditingBirthdate - self.openBioPrivacy = openBioPrivacy - self.openBirthdatePrivacy = openBirthdatePrivacy - self.openPremiumGift = openPremiumGift - self.editingOpenPersonalChannel = editingOpenPersonalChannel - self.openUsernameContextMenu = openUsernameContextMenu - self.openBioContextMenu = openBioContextMenu - self.openNoteContextMenu = openNoteContextMenu - self.openWorkingHoursContextMenu = openWorkingHoursContextMenu - self.openBusinessLocationContextMenu = openBusinessLocationContextMenu - self.openBirthdayContextMenu = openBirthdayContextMenu - self.editingOpenAffiliateProgram = editingOpenAffiliateProgram - self.editingOpenVerifyAccounts = editingOpenVerifyAccounts - self.editingToggleAutoTranslate = editingToggleAutoTranslate - self.displayAutoTranslateLocked = displayAutoTranslateLocked - self.getController = getController - } -} - -private let enabledPublicBioEntities: EnabledEntityTypes = [.allUrl, .mention, .hashtag] -private let enabledPrivateBioEntities: EnabledEntityTypes = [.internalUrl, .mention, .hashtag] - -private enum SettingsSection: Int, CaseIterable { - case edit - case phone - case accounts - case myProfile - case proxy - case apps - case shortcuts - case advanced - case payment - case extra - case support -} - -private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, isExpanded: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] { - guard let data = data else { - return [] - } - - var items: [SettingsSection: [PeerInfoScreenItem]] = [:] - for section in SettingsSection.allCases { - items[section] = [] - } - - let setPhotoTitle: String - if let peer = data.peer, !peer.profileImageRepresentations.isEmpty { - setPhotoTitle = presentationData.strings.Settings_ChangeProfilePhoto - } else { - setPhotoTitle = presentationData.strings.Settings_SetProfilePhotoOrVideo - } - - var setStatusTitle: String = "" - let displaySetStatus: Bool - var hasEmojiStatus = false - if let peer = data.peer as? TelegramUser, peer.isPremium { - if peer.emojiStatus != nil { - hasEmojiStatus = true - setStatusTitle = presentationData.strings.PeerInfo_ChangeEmojiStatus - } else { - setStatusTitle = presentationData.strings.PeerInfo_SetEmojiStatus - } - displaySetStatus = true - } else { - displaySetStatus = false - } - - if displaySetStatus { - items[.edit]!.append(PeerInfoScreenActionItem(id: 0, text: setStatusTitle, icon: UIImage(bundleImageName: hasEmojiStatus ? "Settings/EditEmojiStatus" : "Settings/SetEmojiStatus"), action: { - interaction.openSettings(.emojiStatus) - })) - - items[.edit]!.append(PeerInfoScreenActionItem(id: 1, text: presentationData.strings.PeerInfo_ChangeProfileColor, icon: UIImage(bundleImageName: "Premium/BoostPerk/CoverColor"), action: { - interaction.openSettings(.profileColor) - })) - } - - items[.edit]!.append(PeerInfoScreenActionItem(id: 2, text: setPhotoTitle, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: { - interaction.openSettings(.avatar) - })) - - if let peer = data.peer, (peer.addressName ?? "").isEmpty { - items[.edit]!.append(PeerInfoScreenActionItem(id: 3, text: presentationData.strings.Settings_SetUsername, icon: UIImage(bundleImageName: "Settings/SetUsername"), action: { - interaction.openSettings(.username) - })) - } - - if let settings = data.globalSettings { - if settings.premiumGracePeriod { - items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: "Your access to Telegram Premium will expire soon!", text: .markdown("Unfortunately, your latest payment didn't come through. To keep your access to exclusive features, please renew the subscription."), isWarning: true, linkAction: nil)) - items[.phone]!.append(PeerInfoScreenActionItem(id: 1, text: "Restore Subscription", action: { - interaction.openSettings(.premiumManagement) - })) - } else if settings.suggestPhoneNumberConfirmation, let peer = data.peer as? TelegramUser { - let phoneNumber = formatPhoneNumber(context: context, number: peer.phone ?? "") - items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: presentationData.strings.Settings_CheckPhoneNumberTitle(phoneNumber).string, text: .markdown(presentationData.strings.Settings_CheckPhoneNumberText), linkAction: { link in - if case .tap = link { - interaction.openFaq(presentationData.strings.Settings_CheckPhoneNumberFAQAnchor) - } - })) - items[.phone]!.append(PeerInfoScreenActionItem(id: 1, text: presentationData.strings.Settings_KeepPhoneNumber(phoneNumber).string, action: { - let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.validatePhoneNumber.id).startStandalone() - })) - items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_ChangePhoneNumber, action: { - interaction.openSettings(.phoneNumber) - })) - } else if settings.suggestPasswordConfirmation { - items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: presentationData.strings.Settings_CheckPasswordTitle, text: .markdown(presentationData.strings.Settings_CheckPasswordText), linkAction: { _ in - })) - items[.phone]!.append(PeerInfoScreenActionItem(id: 1, text: presentationData.strings.Settings_KeepPassword, action: { - let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.validatePassword.id).startStandalone() - })) - items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_TryEnterPassword, action: { - interaction.openSettings(.rememberPassword) - })) - } else if settings.suggestPasswordSetup { - items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: presentationData.strings.Settings_SuggestSetupPasswordTitle, text: .markdown(presentationData.strings.Settings_SuggestSetupPasswordText), linkAction: { _ in - })) - items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_SuggestSetupPasswordAction, action: { - interaction.openSettings(.passwordSetup) - })) - } - - if !settings.accountsAndPeers.isEmpty { - for (peerAccountContext, peer, badgeCount) in settings.accountsAndPeers { - let mappedContext = ItemListPeerItem.Context.custom(ItemListPeerItem.Context.Custom( - accountPeerId: peerAccountContext.account.peerId, - postbox: peerAccountContext.account.postbox, - network: peerAccountContext.account.network, - animationCache: context.animationCache, - animationRenderer: context.animationRenderer, - isPremiumDisabled: false, - resolveInlineStickers: { fileIds in - return context.engine.stickers.resolveInlineStickers(fileIds: fileIds) - } - )) - let member: PeerInfoMember = .account(peer: RenderedPeer(peer: peer._asPeer())) - items[.accounts]!.append(PeerInfoScreenMemberItem(id: member.id, context: mappedContext, enclosingPeer: nil, member: member, badge: badgeCount > 0 ? "\(compactNumericCountString(Int(badgeCount), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))" : nil, isAccount: true, action: { action in - switch action { - case .open: - interaction.switchToAccount(peerAccountContext.account.id) - case .remove: - interaction.logoutAccount(peerAccountContext.account.id) - default: - break - } - }, contextAction: { node, gesture in - interaction.accountContextMenu(peerAccountContext.account.id, node, gesture) - })) - } - - items[.accounts]!.append(PeerInfoScreenActionItem(id: 100, text: presentationData.strings.Settings_AddAccount, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), action: { - interaction.openSettings(.addAccount) - })) - } - - items[.myProfile]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_MyProfile, icon: PresentationResourcesSettings.myProfile, action: { - interaction.openSettings(.profile) - })) - - if !settings.proxySettings.servers.isEmpty { - let proxyType: String - if settings.proxySettings.enabled, let activeServer = settings.proxySettings.activeServer { - switch activeServer.connection { - case .mtp: - proxyType = presentationData.strings.SocksProxySetup_ProxyTelegram - case .socks5: - proxyType = presentationData.strings.SocksProxySetup_ProxySocks5 - } - } else { - proxyType = presentationData.strings.Settings_ProxyDisabled - } - items[.proxy]!.append(PeerInfoScreenDisclosureItem(id: 0, label: .text(proxyType), text: presentationData.strings.Settings_Proxy, icon: PresentationResourcesSettings.proxy, action: { - interaction.openSettings(.proxy) - })) - } - } - - var appIndex = 1000 - if let settings = data.globalSettings { - for bot in settings.bots { - let iconSignal: Signal - if let peer = PeerReference(bot.peer._asPeer()), let icon = bot.icons[.iOSSettingsStatic] { - let fileReference: FileMediaReference = .attachBot(peer: peer, media: icon) - iconSignal = instantPageImageFile(account: context.account, userLocation: .other, fileReference: fileReference, fetched: true) - |> map { generator -> UIImage? in - let size = CGSize(width: 29.0, height: 29.0) - let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: .zero)) - return context?.generateImage() - } - let _ = freeMediaFileInteractiveFetched(account: context.account, userLocation: .other, fileReference: fileReference).startStandalone() - } else { - iconSignal = .single(UIImage()) - } - let label: PeerInfoScreenDisclosureItem.Label = bot.flags.contains(.notActivated) || bot.flags.contains(.showInSettingsDisclaimer) ? .titleBadge(presentationData.strings.Settings_New, presentationData.theme.list.itemAccentColor) : .none - items[.apps]!.append(PeerInfoScreenDisclosureItem(id: bot.peer.id.id._internalGetInt64Value(), label: label, text: bot.shortName, icon: nil, iconSignal: iconSignal, action: { - interaction.openBotApp(bot) - })) - appIndex += 1 - } - } - - items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_SavedMessages, icon: PresentationResourcesSettings.savedMessages, action: { - interaction.openSettings(.savedMessages) - })) - items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 2, text: presentationData.strings.CallSettings_RecentCalls, icon: PresentationResourcesSettings.recentCalls, action: { - interaction.openSettings(.recentCalls) - })) - - let devicesLabel: String - if let settings = data.globalSettings, let otherSessionsCount = settings.otherSessionsCount { - if settings.enableQRLogin { - devicesLabel = otherSessionsCount == 0 ? presentationData.strings.Settings_AddDevice : "\(otherSessionsCount + 1)" - } else { - devicesLabel = otherSessionsCount == 0 ? "" : "\(otherSessionsCount + 1)" - } - } else { - devicesLabel = "" - } - - items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 3, label: .text(devicesLabel), text: presentationData.strings.Settings_Devices, icon: PresentationResourcesSettings.devices, action: { - interaction.openSettings(.devices) - })) - items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 4, text: presentationData.strings.Settings_ChatFolders, icon: PresentationResourcesSettings.chatFolders, action: { - interaction.openSettings(.chatFolders) - })) - - let notificationsWarning: Bool - if let settings = data.globalSettings { - notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: settings.notificationAuthorizationStatus, suppressed: settings.notificationWarningSuppressed) - } else { - notificationsWarning = false - } - items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 0, label: notificationsWarning ? .badge("!", presentationData.theme.list.itemDestructiveColor) : .none, text: presentationData.strings.Settings_NotificationsAndSounds, icon: PresentationResourcesSettings.notifications, action: { - interaction.openSettings(.notificationsAndSounds) - })) - items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_PrivacySettings, icon: PresentationResourcesSettings.security, action: { - interaction.openSettings(.privacyAndSecurity) - })) - items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 2, text: presentationData.strings.Settings_ChatSettings, icon: PresentationResourcesSettings.dataAndStorage, action: { - interaction.openSettings(.dataAndStorage) - })) - items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 3, text: presentationData.strings.Settings_Appearance, icon: PresentationResourcesSettings.appearance, action: { - interaction.openSettings(.appearance) - })) - - items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 6, label: .text(data.isPowerSavingEnabled == true ? presentationData.strings.Settings_PowerSavingOn : presentationData.strings.Settings_PowerSavingOff), text: presentationData.strings.Settings_PowerSaving, icon: PresentationResourcesSettings.powerSaving, action: { - interaction.openSettings(.powerSaving) - })) - - let languageName = presentationData.strings.primaryComponent.localizedName - items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 4, label: .text(languageName.isEmpty ? presentationData.strings.Localization_LanguageName : languageName), text: presentationData.strings.Settings_AppLanguage, icon: PresentationResourcesSettings.language, action: { - interaction.openSettings(.language) - })) - - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) - let isPremiumDisabled = premiumConfiguration.isPremiumDisabled - if !isPremiumDisabled || context.isPremium { - items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: presentationData.strings.Settings_Premium, icon: PresentationResourcesSettings.premium, action: { - interaction.openSettings(.premium) - })) - } - if let starsState = data.starsState { - if !isPremiumDisabled || abs(starsState.balance.value) > 0 { - let balanceText: NSAttributedString - if abs(starsState.balance.value) > 0 { - let formattedLabel = formatStarsAmountText(starsState.balance, dateTimeFormat: presentationData.dateTimeFormat) - let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) - let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) - let labelColor = presentationData.theme.list.itemSecondaryTextColor - balanceText = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) - } else { - balanceText = NSAttributedString() - } - items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 102, label: .attributedText(balanceText), text: presentationData.strings.Settings_Stars, icon: PresentationResourcesSettings.stars, action: { - interaction.openSettings(.stars) - })) - } - } - if let tonState = data.tonState { - if abs(tonState.balance.value) > 0 { - let balanceText: NSAttributedString - if abs(tonState.balance.value) > 0 { - let formattedLabel = formatTonAmountText(tonState.balance.value, dateTimeFormat: presentationData.dateTimeFormat) - let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) - let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) - let labelColor = presentationData.theme.list.itemSecondaryTextColor - balanceText = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) - } else { - balanceText = NSAttributedString() - } - items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 103, label: .attributedText(balanceText), text: presentationData.strings.Settings_MyTon, icon: PresentationResourcesSettings.ton, action: { - interaction.openSettings(.ton) - })) - } - } - if !isPremiumDisabled || context.isPremium { - items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 104, label: .text(""), additionalBadgeLabel: nil, text: presentationData.strings.Settings_Business, icon: PresentationResourcesSettings.business, action: { - interaction.openSettings(.businessSetup) - })) - } - if let starsState = data.starsState { - if !isPremiumDisabled || starsState.balance > StarsAmount.zero { - items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 105, label: .text(""), text: presentationData.strings.Settings_SendGift, icon: PresentationResourcesSettings.premiumGift, action: { - interaction.openSettings(.premiumGift) - })) - } - } - - if let settings = data.globalSettings { - if settings.hasPassport { - items[.extra]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_Passport, icon: PresentationResourcesSettings.passport, action: { - interaction.openSettings(.passport) - })) - } - if settings.hasWatchApp { - items[.extra]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_AppleWatch, icon: PresentationResourcesSettings.watch, action: { - interaction.openSettings(.watch) - })) - } - } - - items[.support]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_Support, icon: PresentationResourcesSettings.support, action: { - interaction.openSettings(.support) - })) - items[.support]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_FAQ, icon: PresentationResourcesSettings.faq, action: { - interaction.openSettings(.faq) - })) - items[.support]!.append(PeerInfoScreenDisclosureItem(id: 2, text: presentationData.strings.Settings_Tips, icon: PresentationResourcesSettings.tips, action: { - interaction.openSettings(.tips) - })) - - var result: [(AnyHashable, [PeerInfoScreenItem])] = [] - for section in SettingsSection.allCases { - if let sectionItems = items[section], !sectionItems.isEmpty { - result.append((section, sectionItems)) - } - } - return result -} - -private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, isMyProfile: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] { - guard let data = data else { - return [] - } - - enum Section: Int, CaseIterable { - case help - case bio - case birthday - case info - case account - case logout - } - - var items: [Section: [PeerInfoScreenItem]] = [:] - for section in Section.allCases { - items[section] = [] - } - - let ItemNameHelp = 0 - let ItemBio: AnyHashable = AnyHashable("bio_edit") - let ItemBioHelp = 2 - let ItemPhoneNumber = 3 - let ItemUsername = 4 - let ItemAddAccount = 5 - let ItemAddAccountHelp = 6 - let ItemLogout = 7 - let ItemPeerColor = 8 - let ItemBirthday = 9 - let ItemBirthdayPicker = 10 - let ItemBirthdayRemove = 11 - let ItemBirthdayHelp = 12 - let ItemPeerPersonalChannel = 13 - - items[.help]!.append(PeerInfoScreenCommentItem(id: ItemNameHelp, text: presentationData.strings.EditProfile_NameAndPhotoOrVideoHelp)) - - if let cachedData = data.cachedData as? CachedUserData { - items[.bio]!.append(PeerInfoScreenMultilineInputItem(id: ItemBio, text: state.updatingBio ?? (cachedData.about ?? ""), placeholder: presentationData.strings.UserInfo_About_Placeholder, textUpdated: { updatedText in - interaction.updateBio(updatedText) - }, action: { - interaction.dismissInput() - }, maxLength: Int(data.globalSettings?.userLimits.maxAboutLength ?? 70))) - items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_PrivacyHelp, linkAction: { _ in - interaction.openBioPrivacy() - })) - } - - - var birthday: TelegramBirthday? - if let updatingBirthDate = state.updatingBirthDate { - birthday = updatingBirthDate - } else { - birthday = (data.cachedData as? CachedUserData)?.birthday - } - - var birthDateString: String - if let birthday { - birthDateString = stringForCompactBirthday(birthday, strings: presentationData.strings) - } else { - birthDateString = presentationData.strings.Settings_Birthday_Add - } - - let isEditingBirthDate = state.isEditingBirthDate - items[.birthday]!.append(PeerInfoScreenDisclosureItem(id: ItemBirthday, label: .coloredText(birthDateString, isEditingBirthDate ? .accent : .generic), text: presentationData.strings.Settings_Birthday, icon: nil, hasArrow: false, action: { - interaction.updateIsEditingBirthdate(!isEditingBirthDate) - })) - if isEditingBirthDate, let birthday { - items[.birthday]!.append(PeerInfoScreenBirthdatePickerItem(id: ItemBirthdayPicker, value: birthday, valueUpdated: { value in - interaction.updateBirthdate(value) - })) - items[.birthday]!.append(PeerInfoScreenActionItem(id: ItemBirthdayRemove, text: presentationData.strings.Settings_Birthday_Remove, alignment: .natural, action: { - interaction.updateBirthdate(.some(nil)) - interaction.updateIsEditingBirthdate(false) - })) - } - - - var birthdayIsForContactsOnly = false - if let birthdayPrivacy = data.globalSettings?.privacySettings?.birthday, case .enableContacts = birthdayPrivacy { - birthdayIsForContactsOnly = true - } - items[.birthday]!.append(PeerInfoScreenCommentItem(id: ItemBirthdayHelp, text: birthdayIsForContactsOnly ? presentationData.strings.Settings_Birthday_ContactsHelp : presentationData.strings.Settings_Birthday_Help, linkAction: { _ in - interaction.openBirthdatePrivacy() - })) - - if let user = data.peer as? TelegramUser { - items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPhoneNumber, label: .text(user.phone.flatMap({ formatPhoneNumber(context: context, number: $0) }) ?? ""), text: presentationData.strings.Settings_PhoneNumber, action: { - interaction.openSettings(.phoneNumber) - })) - } - var username = "" - if let addressName = data.peer?.addressName, !addressName.isEmpty { - username = "@\(addressName)" - } - items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(username), text: presentationData.strings.Settings_Username, action: { - interaction.openSettings(.username) - })) - - if let peer = data.peer as? TelegramUser { - var colors: [PeerNameColors.Colors] = [] - if let nameColor = peer.nameColor { - let nameColors: PeerNameColors.Colors - switch nameColor { - case let .preset(nameColor): - nameColors = context.peerNameColors.get(nameColor, dark: presentationData.theme.overallDarkAppearance) - case let .collectible(collectibleColor): - nameColors = collectibleColor.peerNameColors(dark: presentationData.theme.overallDarkAppearance) - } - colors.append(nameColors) - } - if let profileColor = peer.effectiveProfileColor.flatMap({ context.peerNameColors.getProfile($0, dark: presentationData.theme.overallDarkAppearance, subject: .palette) }) { - colors.append(profileColor) - } - let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors) - - items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), text: presentationData.strings.Settings_YourColor, icon: nil, action: { - interaction.editingOpenNameColorSetup() - })) - - var displayPersonalChannel = false - if data.personalChannel != nil { - displayPersonalChannel = true - } else if let personalChannels = state.personalChannels, !personalChannels.isEmpty { - displayPersonalChannel = true - } - if displayPersonalChannel { - var personalChannelTitle: String? - if let personalChannel = data.personalChannel, let peer = personalChannel.peer.chatOrMonoforumMainPeer { - personalChannelTitle = peer.compactDisplayTitle - } - - items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerPersonalChannel, label: .text(personalChannelTitle ?? presentationData.strings.Settings_PersonalChannelEmptyValue), text: presentationData.strings.Settings_PersonalChannelItem, icon: nil, action: { - interaction.editingOpenPersonalChannel() - })) - } - } - - items[.account]!.append(PeerInfoScreenActionItem(id: ItemAddAccount, text: presentationData.strings.Settings_AddAnotherAccount, alignment: .center, action: { - interaction.openSettings(.addAccount) - })) - - var hasPremiumAccounts = false - if data.peer?.isPremium == true && !context.account.testingEnvironment { - hasPremiumAccounts = true - } - if let settings = data.globalSettings { - for (accountContext, peer, _) in settings.accountsAndPeers { - if !accountContext.account.testingEnvironment { - if peer.isPremium { - hasPremiumAccounts = true - break - } - } - } - } - - items[.account]!.append(PeerInfoScreenCommentItem(id: ItemAddAccountHelp, text: hasPremiumAccounts ? presentationData.strings.Settings_AddAnotherAccount_PremiumHelp : presentationData.strings.Settings_AddAnotherAccount_Help)) - - items[.logout]!.append(PeerInfoScreenActionItem(id: ItemLogout, text: presentationData.strings.Settings_Logout, color: .destructive, alignment: .center, action: { - interaction.openSettings(.logout) - })) - - var result: [(AnyHashable, [PeerInfoScreenItem])] = [] - for section in Section.allCases { - if let sectionItems = items[section], !sectionItems.isEmpty { - result.append((section, sectionItems)) - } - } - return result -} - -private enum InfoSection: Int, CaseIterable { - case groupLocation - case calls - case personalChannel - case peerInfo - case balances - case permissions - case peerInfoTrailing - case peerSettings - case peerMembers - case channelMonoforum - case botAffiliateProgram -} - -private func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], chatLocation: ChatLocation, isOpenedFromChat: Bool, isMyProfile: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] { - guard let data = data else { - return [] - } - - var currentPeerInfoSection: InfoSection = .peerInfo - - var items: [InfoSection: [PeerInfoScreenItem]] = [:] - for section in InfoSection.allCases { - items[section] = [] - } - - let bioContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in - interaction.openBioContextMenu(node, gesture) - } - let noteContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in - interaction.openNoteContextMenu(node, gesture) - } - let bioLinkAction: (TextLinkItemActionType, TextLinkItem, ASDisplayNode, CGRect?, Promise?) -> Void = { action, item, _, _, _ in - interaction.performBioLinkAction(action, item) - } - let workingHoursContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in - interaction.openWorkingHoursContextMenu(node, gesture) - } - let businessLocationContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in - interaction.openBusinessLocationContextMenu(node, gesture) - } - let birthdayContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in - interaction.openBirthdayContextMenu(node, gesture) - } - - if let user = data.peer as? TelegramUser { - let ItemCallList = 1000 - let ItemPersonalChannelHeader = 2000 - let ItemPersonalChannel = 2001 - let ItemPhoneNumber = 3000 - let ItemUsername = 3001 - let ItemBirthdate = 3002 - let ItemAbout = 3003 - let ItemNote = 3004 - let ItemAppFooter = 3005 - let ItemAffiliate = 4000 - let ItemAffiliateInfo = 4001 - let ItemBusinessHours = 5000 - let ItemLocation = 5001 - let ItemSendMessage = 6000 - let ItemReport = 6001 - let ItemAddToContacts = 6002 - let ItemBlock = 6003 - let ItemEncryptionKey = 6004 - let ItemBalanceHeader = 7000 - let ItemBalanceTon = 7001 - let ItemBalanceStars = 7002 - let ItemBotPermissionsHeader = 8000 - let ItemBotPermissionsEmojiStatus = 8001 - let ItemBotPermissionsLocation = 8002 - let ItemBotPermissionsBiometry = 8003 - let ItemBotSettings = 9000 - let ItemBotReport = 9001 - let ItemBotAddToChat = 9002 - let ItemBotAddToChatInfo = 9003 - let ItemVerification = 9004 - - if !callMessages.isEmpty { - items[.calls]!.append(PeerInfoScreenCallListItem(id: ItemCallList, messages: callMessages)) - } - - if let personalChannel = data.personalChannel { - let peerId = personalChannel.peer.peerId - var label: String? - if let subscriberCount = personalChannel.subscriberCount { - label = presentationData.strings.Conversation_StatusSubscribers(Int32(subscriberCount)) - } - items[.personalChannel]?.append(PeerInfoScreenHeaderItem(id: ItemPersonalChannelHeader, text: presentationData.strings.Profile_PersonalChannelSectionTitle, label: label)) - items[.personalChannel]?.append(PeerInfoScreenPersonalChannelItem(id: ItemPersonalChannel, context: context, data: personalChannel, controller: { [weak interaction] in - guard let interaction else { - return nil - } - return interaction.getController() - }, action: { [weak interaction] in - guard let interaction else { - return - } - interaction.openChat(peerId) - })) - } - - if let phone = user.phone { - let formattedPhone = formatPhoneNumber(context: context, number: phone) - let label: String - if formattedPhone.hasPrefix("+888 ") { - label = presentationData.strings.UserInfo_AnonymousNumberLabel - } else { - label = presentationData.strings.ContactInfo_PhoneLabelMobile - } - items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemPhoneNumber, label: label, text: formattedPhone, textColor: .accent, action: { node, progress in - interaction.openPhone(phone, node, nil, progress) - }, longTapAction: nil, contextAction: { node, gesture, _ in - interaction.openPhone(phone, node, gesture, nil) - }, requestLayout: { animated in - interaction.requestLayout(animated) - })) - } - if let mainUsername = user.addressName { - var additionalUsernames: String? - let usernames = user.usernames.filter { $0.isActive && $0.username != mainUsername } - if !usernames.isEmpty { - additionalUsernames = presentationData.strings.Profile_AdditionalUsernames(String(usernames.map { "@\($0.username)" }.joined(separator: ", "))).string - } - - items[currentPeerInfoSection]!.append( - PeerInfoScreenLabeledValueItem( - id: ItemUsername, - label: presentationData.strings.Profile_Username, - text: "@\(mainUsername)", - additionalText: additionalUsernames, - textColor: .accent, - icon: .qrCode, - action: { _, progress in - interaction.openUsername(mainUsername, true, progress) - }, linkItemAction: { type, item, _, _, progress in - if case .tap = type { - if case let .mention(username) = item { - interaction.openUsername(String(username[username.index(username.startIndex, offsetBy: 1)...]), false, progress) - } - } - }, iconAction: { - interaction.openQrCode() - }, contextAction: { node, gesture, _ in - interaction.openUsernameContextMenu(node, gesture) - }, requestLayout: { animated in - interaction.requestLayout(animated) - } - ) - ) - } - - if let cachedData = data.cachedData as? CachedUserData { - if let birthday = cachedData.birthday { - var hasBirthdayToday = false - let today = Calendar.current.dateComponents(Set([.day, .month]), from: Date()) - if today.day == Int(birthday.day) && today.month == Int(birthday.month) { - hasBirthdayToday = true - } - - var birthdayAction: ((ASDisplayNode, Promise?) -> Void)? - if isMyProfile { - birthdayAction = { node, _ in - birthdayContextAction(node, nil, nil) - } - } else if hasBirthdayToday && cachedData.disallowedGifts != TelegramDisallowedGifts.All { - birthdayAction = { _, _ in - interaction.openPremiumGift() - } - } - - items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemBirthdate, context: context, label: hasBirthdayToday ? presentationData.strings.UserInfo_BirthdayToday : presentationData.strings.UserInfo_Birthday, text: stringForCompactBirthday(birthday, strings: presentationData.strings, showAge: true), textColor: .primary, leftIcon: hasBirthdayToday ? .birthday : nil, icon: hasBirthdayToday ? .premiumGift : nil, action: birthdayAction, longTapAction: nil, iconAction: { - interaction.openPremiumGift() - }, contextAction: birthdayContextAction, requestLayout: { _ in - })) - } - - var hasAbout = false - if let about = cachedData.about, !about.isEmpty { - hasAbout = true - } - var hasNote = false - if let note = cachedData.note, !note.text.isEmpty { - hasNote = true - } - - var hasWebApp = false - if let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) { - hasWebApp = true - } - - if user.isFake { - items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: "", text: user.botInfo != nil ? presentationData.strings.UserInfo_FakeBotWarning : presentationData.strings.UserInfo_FakeUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: { animated in - interaction.requestLayout(animated) - })) - } else if user.isScam { - items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: { animated in - interaction.requestLayout(animated) - })) - } else if hasAbout || hasNote || hasWebApp { - var actionButton: PeerInfoScreenLabeledValueItem.Button? - if hasWebApp { - actionButton = PeerInfoScreenLabeledValueItem.Button(title: presentationData.strings.PeerInfo_OpenAppButton, action: { - guard let parentController = interaction.getController() else { - return - } - - if let navigationController = parentController.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer { - for controller in minimizedContainer.controllers { - if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == user.id && mainController.source == .generic { - navigationController.maximizeViewController(controller, animated: true) - return - } - } - } - - context.sharedContext.openWebApp( - context: context, - parentController: parentController, - updatedPresentationData: nil, - botPeer: .user(user), - chatPeer: nil, - threadId: nil, - buttonText: "", - url: "", - simple: true, - source: .generic, - skipTermsOfService: true, - payload: nil, - verifyAgeCompletion: nil - ) - }) - } - - if hasAbout || hasWebApp { - var label: String = "" - if let about = cachedData.about, !about.isEmpty { - label = user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo - } - items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: label, text: cachedData.about ?? "", textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.isPremium ? enabledPublicBioEntities : enabledPrivateBioEntities), action: isMyProfile ? { node, _ in - bioContextAction(node, nil, nil) - } : nil, linkItemAction: bioLinkAction, button: actionButton, contextAction: bioContextAction, requestLayout: { animated in - interaction.requestLayout(animated) - })) - } - - if let note = cachedData.note, !note.text.isEmpty { - var entities = note.entities - if context.isPremium { - entities = generateTextEntities(note.text, enabledTypes: [.mention, .hashtag, .allUrl], currentEntities: entities) - } - items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemNote, label: presentationData.strings.PeerInfo_Notes, rightLabel: presentationData.strings.PeerInfo_NotesInfo, text: note.text, entities: entities, handleSpoilers: true, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: []), action: nil, linkItemAction: bioLinkAction, button: nil, contextAction: noteContextAction, requestLayout: { animated in - interaction.requestLayout(animated) - })) - } - - if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { - items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemAppFooter, text: presentationData.strings.PeerInfo_AppFooterAdmin, linkAction: { action in - if case let .tap(url) = action { - context.sharedContext.applicationBindings.openUrl(url) - } - })) - - currentPeerInfoSection = .peerInfoTrailing - } else if actionButton != nil { - items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemAppFooter, text: presentationData.strings.PeerInfo_AppFooter, linkAction: { action in - if case let .tap(url) = action { - context.sharedContext.applicationBindings.openUrl(url) - } - })) - - currentPeerInfoSection = .peerInfoTrailing - } - - if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { - } else { - if let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil { - var canJoinRefProgram = false - if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] { - if let value = value as? Double { - canJoinRefProgram = value != 0.0 - } else if let value = value as? Bool { - canJoinRefProgram = value - } - } - - if canJoinRefProgram { - if items[.botAffiliateProgram] == nil { - items[.botAffiliateProgram] = [] - } - let programTitleValue: String - programTitleValue = "\(formatPermille(starRefProgram.commissionPermille))%" - items[.botAffiliateProgram]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliate, label: .labelBadge(programTitleValue), additionalBadgeLabel: nil, text: presentationData.strings.PeerInfo_ItemAffiliateProgram_Title, icon: PresentationResourcesSettings.affiliateProgram, action: { - interaction.editingOpenAffiliateProgram() - })) - items[.botAffiliateProgram]!.append(PeerInfoScreenCommentItem(id: ItemAffiliateInfo, text: presentationData.strings.PeerInfo_ItemAffiliateProgram_Footer(EnginePeer.user(user).compactDisplayTitle, formatPermille(starRefProgram.commissionPermille)).string)) - } - } - } - } - - if let businessHours = cachedData.businessHours { - items[currentPeerInfoSection]!.append(PeerInfoScreenBusinessHoursItem(id: ItemBusinessHours, label: presentationData.strings.PeerInfo_BusinessHours_Label, businessHours: businessHours, requestLayout: { animated in - interaction.requestLayout(animated) - }, longTapAction: nil, contextAction: workingHoursContextAction)) - } - - if let businessLocation = cachedData.businessLocation { - if let coordinates = businessLocation.coordinates { - let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: coordinates.latitude, longitude: coordinates.longitude, width: 90, height: 90)) - items[currentPeerInfoSection]!.append(PeerInfoScreenAddressItem( - id: ItemLocation, - label: presentationData.strings.PeerInfo_Location_Label, - text: businessLocation.address, - imageSignal: imageSignal, - action: { - interaction.openLocation() - }, - contextAction: businessLocationContextAction - )) - } else { - items[currentPeerInfoSection]!.append(PeerInfoScreenAddressItem( - id: ItemLocation, - label: presentationData.strings.PeerInfo_Location_Label, - text: businessLocation.address, - imageSignal: nil, - action: nil, - contextAction: businessLocationContextAction - )) - } - } - } - - if !isMyProfile { - if let reactionSourceMessageId = reactionSourceMessageId, !data.isContact { - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemSendMessage, text: presentationData.strings.UserInfo_SendMessage, action: { - interaction.openChat(nil) - })) - - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemReport, text: presentationData.strings.ReportPeer_BanAndReport, color: .destructive, action: { - interaction.openReport(.reaction(reactionSourceMessageId)) - })) - } else if let _ = nearbyPeerDistance { - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemSendMessage, text: presentationData.strings.UserInfo_SendMessage, action: { - interaction.openChat(nil) - })) - - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemReport, text: presentationData.strings.ReportPeer_Report, color: .destructive, action: { - interaction.openReport(.user) - })) - } else { - if !data.isContact { - if user.botInfo == nil { - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemAddToContacts, text: presentationData.strings.PeerInfo_AddToContacts, action: { - interaction.openAddContact() - })) - } - } - - var isBlocked = false - if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked { - isBlocked = true - } - - if isBlocked { - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemBlock, text: user.botInfo != nil ? presentationData.strings.Bot_Unblock : presentationData.strings.Conversation_Unblock, action: { - interaction.updateBlocked(false) - })) - } else { - if user.flags.contains(.isSupport) || data.isContact { - } else { - if user.botInfo == nil { - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemBlock, text: presentationData.strings.Conversation_BlockUser, color: .destructive, action: { - interaction.updateBlocked(true) - })) - } - } - } - - if let encryptionKeyFingerprint = data.encryptionKeyFingerprint { - items[currentPeerInfoSection]!.append(PeerInfoScreenDisclosureEncryptionKeyItem(id: ItemEncryptionKey, text: presentationData.strings.Profile_EncryptionKey, fingerprint: encryptionKeyFingerprint, action: { - interaction.openEncryptionKey() - })) - } - - let revenueBalance = data.revenueStatsState?.balances.currentBalance.amount.value ?? 0 - let overallRevenueBalance = data.revenueStatsState?.balances.overallRevenue.amount.value ?? 0 - - let starsBalance = data.starsRevenueStatsState?.balances.currentBalance.amount ?? StarsAmount.zero - let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue.amount ?? StarsAmount.zero - - if overallRevenueBalance > 0 || overallStarsBalance > StarsAmount.zero { - items[.balances]!.append(PeerInfoScreenHeaderItem(id: ItemBalanceHeader, text: presentationData.strings.PeerInfo_BotBalance_Title)) - if overallRevenueBalance > 0 { - let string = "*\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))" - let attributedString = NSMutableAttributedString(string: string, font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), textColor: presentationData.theme.list.itemSecondaryTextColor) - if let range = attributedString.string.range(of: "*") { - attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: false)), range: NSRange(range, in: attributedString.string)) - attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string)) - } - items[.balances]!.append(PeerInfoScreenDisclosureItem(id: ItemBalanceTon, label: .attributedText(attributedString), text: presentationData.strings.PeerInfo_BotBalance_Ton, icon: PresentationResourcesSettings.ton, action: { - interaction.editingOpenRevenue() - })) - } - - if overallStarsBalance > StarsAmount.zero { - let formattedLabel = formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat) - let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) - let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) - let labelColor = presentationData.theme.list.itemSecondaryTextColor - let attributedString = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString - attributedString.insert(NSAttributedString(string: "*", font: labelFont, textColor: labelColor), at: 0) - - if let range = attributedString.string.range(of: "*") { - attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string)) - attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string)) - } - items[.balances]!.append(PeerInfoScreenDisclosureItem(id: ItemBalanceStars, label: .attributedText(attributedString), text: presentationData.strings.PeerInfo_BotBalance_Stars, icon: PresentationResourcesSettings.stars, action: { - interaction.editingOpenStars() - })) - } - } - - if let _ = user.botInfo { - var canManageEmojiStatus = false - if let cachedData = data.cachedData as? CachedUserData, cachedData.flags.contains(.botCanManageEmojiStatus) { - canManageEmojiStatus = true - } - if canManageEmojiStatus || data.webAppPermissions?.emojiStatus?.isRequested == true { - items[.permissions]!.append(PeerInfoScreenSwitchItem(id: ItemBotPermissionsEmojiStatus, text: presentationData.strings.PeerInfo_Permissions_EmojiStatus, value: canManageEmojiStatus, icon: UIImage(bundleImageName: "Chat/Info/Status"), isLocked: false, toggled: { value in - let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: user.id, enabled: value) - |> deliverOnMainQueue).startStandalone() - - let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in - return WebAppPermissionsState(location: current?.location, emojiStatus: WebAppPermissionsState.EmojiStatus(isRequested: true)) - }.startStandalone() - })) - } - if data.webAppPermissions?.location?.isRequested == true || data.webAppPermissions?.location?.isAllowed == true { - items[.permissions]!.append(PeerInfoScreenSwitchItem(id: ItemBotPermissionsLocation, text: presentationData.strings.PeerInfo_Permissions_Geolocation, value: data.webAppPermissions?.location?.isAllowed ?? false, icon: UIImage(bundleImageName: "Chat/Info/Location"), isLocked: false, toggled: { value in - let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in - return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: value), emojiStatus: current?.emojiStatus) - }.startStandalone() - })) - } - if !"".isEmpty { - items[.permissions]!.append(PeerInfoScreenSwitchItem(id: ItemBotPermissionsBiometry, text: presentationData.strings.PeerInfo_Permissions_Biometry, value: true, icon: UIImage(bundleImageName: "Settings/Menu/TouchId"), isLocked: false, toggled: { value in - - })) - } - - if !items[.permissions]!.isEmpty { - items[.permissions]!.insert(PeerInfoScreenHeaderItem(id: ItemBotPermissionsHeader, text: presentationData.strings.PeerInfo_Permissions_Title), at: 0) - } - } - - if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { - items[currentPeerInfoSection]!.append(PeerInfoScreenDisclosureItem(id: ItemBotSettings, label: .none, text: presentationData.strings.Bot_Settings, icon: UIImage(bundleImageName: "Chat/Info/SettingsIcon"), action: { - interaction.openEditing() - })) - } - - if let botInfo = user.botInfo, !botInfo.flags.contains(.canEdit) { - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemBotReport, text: presentationData.strings.ReportPeer_Report, action: { - interaction.openReport(.default) - })) - } - - if let verification = (data.cachedData as? CachedUserData)?.verification { - let description: String - let descriptionString = verification.description - let entities = generateTextEntities(descriptionString, enabledTypes: [.allUrl]) - if let entity = entities.first { - let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) - let url = (descriptionString as NSString).substring(with: range) - description = descriptionString.replacingOccurrences(of: url, with: "[\(url)](\(url))") - } else { - description = descriptionString - } - let attributedPrefix = NSMutableAttributedString(string: " ") - attributedPrefix.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: verification.iconFileId, file: nil), range: NSMakeRange(0, 1)) - - items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemVerification, text: description, attributedPrefix: attributedPrefix, useAccentLinkColor: false, linkAction: { action in - if case let .tap(url) = action, let navigationController = interaction.getController()?.navigationController as? NavigationController { - context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) - } - })) - } else if let botInfo = user.botInfo, botInfo.flags.contains(.worksWithGroups) { - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemBotAddToChat, text: presentationData.strings.Bot_AddToChat, color: .accent, action: { - interaction.openAddBotToGroup() - })) - items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemBotAddToChatInfo, text: presentationData.strings.Bot_AddToChatInfo)) - } - } - } - } else if let channel = data.peer as? TelegramChannel { - let ItemUsername = 1 - let ItemUsernameInfo = 2 - let ItemAbout = 3 - let ItemLocationHeader = 4 - let ItemLocation = 5 - let ItemAdmins = 6 - let ItemMembers = 7 - let ItemMemberRequests = 8 - let ItemBalance = 9 - let ItemEdit = 10 - let ItemPeerPersonalChannel = 11 - - if let _ = data.threadData { - let mainUsername: String - if let addressName = channel.addressName { - mainUsername = addressName - } else { - mainUsername = "c/\(channel.id.id._internalGetInt64Value())" - } - - var threadId: Int64 = 0 - if case let .replyThread(message) = chatLocation { - threadId = message.threadId - } - - let linkText = "https://t.me/\(mainUsername)/\(threadId)" - - items[currentPeerInfoSection]!.append( - PeerInfoScreenLabeledValueItem( - id: ItemUsername, - label: presentationData.strings.Channel_LinkItem, - text: linkText, - textColor: .accent, - icon: .qrCode, - action: { _, progress in - interaction.openUsername(linkText, true, progress) - }, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.link(customLink: linkText), sourceNode, nil) - }, linkItemAction: { type, item, _, _, progress in - if case .tap = type { - if case let .mention(username) = item { - interaction.openUsername(String(username.suffix(from: username.index(username.startIndex, offsetBy: 1))), false, progress) - } - } - }, iconAction: { - interaction.openQrCode() - }, requestLayout: { animated in - interaction.requestLayout(animated) - } - ) - ) - if let _ = channel.addressName { - - } else { - items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemUsernameInfo, text: presentationData.strings.PeerInfo_PrivateShareLinkInfo)) - } - } else { - if let location = (data.cachedData as? CachedChannelData)?.peerGeoLocation { - items[.groupLocation]!.append(PeerInfoScreenHeaderItem(id: ItemLocationHeader, text: presentationData.strings.GroupInfo_Location.uppercased())) - - let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) - items[.groupLocation]!.append(PeerInfoScreenAddressItem( - id: ItemLocation, - label: "", - text: location.address.replacingOccurrences(of: ", ", with: "\n"), - imageSignal: imageSignal, - action: { - interaction.openLocation() - } - )) - } - - if let mainUsername = channel.addressName { - var additionalUsernames: String? - let usernames = channel.usernames.filter { $0.isActive && $0.username != mainUsername } - if !usernames.isEmpty { - additionalUsernames = presentationData.strings.Profile_AdditionalUsernames(String(usernames.map { "@\($0.username)" }.joined(separator: ", "))).string - } - - items[currentPeerInfoSection]!.append( - PeerInfoScreenLabeledValueItem( - id: ItemUsername, - label: presentationData.strings.Channel_LinkItem, - text: "https://t.me/\(mainUsername)", - additionalText: additionalUsernames, - textColor: .accent, - icon: .qrCode, - action: { _, progress in - interaction.openUsername(mainUsername, true, progress) - }, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode, nil) - }, linkItemAction: { type, item, sourceNode, sourceRect, progress in - if case .tap = type { - if case let .mention(username) = item { - interaction.openUsername(String(username.suffix(from: username.index(username.startIndex, offsetBy: 1))), false, progress) - } - } else if case .longTap = type { - if case let .mention(username) = item { - interaction.openPeerInfoContextMenu(.link(customLink: username), sourceNode, sourceRect) - } - } - }, iconAction: { - interaction.openQrCode() - }, requestLayout: { animated in - interaction.requestLayout(animated) - } - ) - ) - } - if let cachedData = data.cachedData as? CachedChannelData { - let aboutText: String? - if channel.isFake { - if case .broadcast = channel.info { - aboutText = presentationData.strings.ChannelInfo_FakeChannelWarning - } else { - aboutText = presentationData.strings.GroupInfo_FakeGroupWarning - } - } else if channel.isScam { - if case .broadcast = channel.info { - aboutText = presentationData.strings.ChannelInfo_ScamChannelWarning - } else { - aboutText = presentationData.strings.GroupInfo_ScamGroupWarning - } - } else if let about = cachedData.about, !about.isEmpty { - aboutText = about - } else { - aboutText = nil - } - - if let aboutText = aboutText { - var enabledEntities = enabledPublicBioEntities - if case .group = channel.info { - enabledEntities = enabledPrivateBioEntities - } - items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledEntities), action: isMyProfile ? { node, _ in - bioContextAction(node, nil, nil) - } : nil, linkItemAction: bioLinkAction, contextAction: bioContextAction, requestLayout: { animated in - interaction.requestLayout(animated) - })) - } - - if let verification = (data.cachedData as? CachedChannelData)?.verification { - let description: String - let descriptionString = verification.description - let entities = generateTextEntities(descriptionString, enabledTypes: [.allUrl]) - if let entity = entities.first { - let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) - let url = (descriptionString as NSString).substring(with: range) - description = descriptionString.replacingOccurrences(of: url, with: "[\(url)](\(url))") - } else { - description = descriptionString - } - - let attributedPrefix = NSMutableAttributedString(string: " ") - attributedPrefix.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: verification.iconFileId, file: nil), range: NSMakeRange(0, 1)) - - items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: 800, text: description, attributedPrefix: attributedPrefix, useAccentLinkColor: false, linkAction: { action in - if case let .tap(url) = action, let navigationController = interaction.getController()?.navigationController as? NavigationController { - context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) - } - })) - } - - if case .broadcast = channel.info { - var canEditMembers = false - if channel.hasPermission(.banMembers) { - canEditMembers = true - } - if canEditMembers { - if channel.adminRights != nil || channel.flags.contains(.isCreator) { - let adminCount = cachedData.participantsSummary.adminCount ?? 0 - let memberCount = cachedData.participantsSummary.memberCount ?? 0 - - items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text("\(adminCount == 0 ? "" : "\(presentationStringsFormattedNumber(adminCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { - interaction.openParticipantsSection(.admins) - })) - items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text("\(memberCount == 0 ? "" : "\(presentationStringsFormattedNumber(memberCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.Channel_Info_Subscribers, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: { - interaction.openParticipantsSection(.members) - })) - - if let count = data.requests?.count, count > 0 { - items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { - interaction.openParticipantsSection(.memberRequests) - })) - } - } - } - } - - if channel.adminRights != nil || channel.flags.contains(.isCreator) { - let section: InfoSection - if case .group = channel.info { - section = .peerSettings - } else { - section = .peerMembers - } - if cachedData.flags.contains(.canViewRevenue) || cachedData.flags.contains(.canViewStarsRevenue) { - let revenueBalance = data.revenueStatsState?.balances.currentBalance.amount.value ?? 0 - let starsBalance = data.starsRevenueStatsState?.balances.currentBalance.amount ?? StarsAmount.zero - - let overallRevenueBalance = data.revenueStatsState?.balances.overallRevenue.amount.value ?? 0 - let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue.amount ?? StarsAmount.zero - - if overallRevenueBalance > 0 || overallStarsBalance > StarsAmount.zero { - let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) - let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) - let labelColor = presentationData.theme.list.itemSecondaryTextColor - - let attributedString = NSMutableAttributedString() - if overallRevenueBalance > 0 { - attributedString.append(NSAttributedString(string: "#\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))", font: labelFont, textColor: labelColor)) - } - if overallStarsBalance > StarsAmount.zero { - if !attributedString.string.isEmpty { - attributedString.append(NSAttributedString(string: " ", font: labelFont, textColor: labelColor)) - } - attributedString.append(NSAttributedString(string: "*", font: labelFont, textColor: labelColor)) - - let formattedLabel = formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat) - let starsAttributedString = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString - attributedString.append(starsAttributedString) - } - if let range = attributedString.string.range(of: "#") { - attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: false)), range: NSRange(range, in: attributedString.string)) - attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string)) - } - if let range = attributedString.string.range(of: "*") { - attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 1, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string)) - attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string)) - } - - items[section]!.append(PeerInfoScreenDisclosureItem(id: ItemBalance, label: .attributedText(attributedString), text: presentationData.strings.PeerInfo_Bot_Balance, icon: PresentationResourcesSettings.balance, action: { - interaction.openStats(.monetization) - })) - } - } - - let settingsTitle: String - switch channel.info { - case .broadcast: - settingsTitle = presentationData.strings.Channel_Info_Settings - case .group: - settingsTitle = presentationData.strings.Group_Info_Settings - } - items[section]!.append(PeerInfoScreenDisclosureItem(id: ItemEdit, label: .none, text: settingsTitle, icon: UIImage(bundleImageName: "Chat/Info/SettingsIcon"), action: { - interaction.openEditing() - })) - } - - if channel.hasPermission(.manageDirect), let personalChannel = data.personalChannel { - let peerId = personalChannel.peer.peerId - items[.channelMonoforum]?.append(PeerInfoScreenPersonalChannelItem(id: ItemPeerPersonalChannel, context: context, data: personalChannel, controller: { [weak interaction] in - guard let interaction else { - return nil - } - return interaction.getController() - }, action: { [weak interaction] in - guard let interaction else { - return - } - interaction.openChat(peerId) - })) - } - } - } - } else if let group = data.peer as? TelegramGroup { - if let cachedData = data.cachedData as? CachedGroupData { - let aboutText: String? - if group.isFake { - aboutText = presentationData.strings.GroupInfo_FakeGroupWarning - } else if group.isScam { - aboutText = presentationData.strings.GroupInfo_ScamGroupWarning - } else if let about = cachedData.about, !about.isEmpty { - aboutText = about - } else { - aboutText = nil - } - - if let aboutText = aboutText { - items[currentPeerInfoSection]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPrivateBioEntities), action: isMyProfile ? { node, _ in - bioContextAction(node, nil, nil) - } : nil, linkItemAction: bioLinkAction, contextAction: bioContextAction, requestLayout: { animated in - interaction.requestLayout(animated) - })) - } - } - } - - if let peer = data.peer, let members = data.members, case let .shortList(_, memberList) = members { - var canAddMembers = false - if let group = data.peer as? TelegramGroup { - switch group.role { - case .admin, .creator: - canAddMembers = true - case .member: - break - } - if !group.hasBannedPermission(.banAddMembers) { - canAddMembers = true - } - } else if let channel = data.peer as? TelegramChannel { - switch channel.info { - case .broadcast: - break - case .group: - if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) { - canAddMembers = true - } - } - } - - if canAddMembers { - items[.peerMembers]!.append(PeerInfoScreenActionItem(id: 0, text: presentationData.strings.GroupInfo_AddParticipant, color: .accent, icon: UIImage(bundleImageName: "Contact List/AddMemberIcon"), alignment: .peerList, action: { - interaction.openAddMember() - })) - } - - for member in memberList { - let isAccountPeer = member.id == context.account.peerId - items[.peerMembers]!.append(PeerInfoScreenMemberItem(id: member.id, context: .account(context), enclosingPeer: peer, member: member, isAccount: false, action: isAccountPeer ? nil : { action in - switch action { - case .open: - interaction.openPeerInfo(member.peer, true) - case .promote: - interaction.performMemberAction(member, .promote) - case .restrict: - interaction.performMemberAction(member, .restrict) - case .remove: - interaction.performMemberAction(member, .remove) - } - }, openStories: { sourceView in - interaction.performMemberAction(member, .openStories(sourceView: sourceView)) - })) - } - } - - var result: [(AnyHashable, [PeerInfoScreenItem])] = [] - for section in InfoSection.allCases { - if let sectionItems = items[section], !sectionItems.isEmpty { - result.append((section, sectionItems)) - } - } - return result -} - -private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, state: PeerInfoState, chatLocation: ChatLocation, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction) -> [(AnyHashable, [PeerInfoScreenItem])] { - enum Section: Int, CaseIterable { - case notifications - case groupLocation - case peerPublicSettings - case peerNote - case peerDataSettings - case peerVerifySettings - case peerSettings - case linkedMonoforum - case peerAdditionalSettings - case peerActions - } - - var items: [Section: [PeerInfoScreenItem]] = [:] - for section in Section.allCases { - items[section] = [] - } - - if let data = data { - if let user = data.peer as? TelegramUser { - let ItemNote: AnyHashable = AnyHashable("note_edit") - let ItemNoteInfo = 1 - - let ItemSuggestBirthdate = 2 - let ItemSuggestPhoto = 3 - let ItemCustomPhoto = 4 - let ItemReset = 5 - let ItemInfo = 6 - let ItemDelete = 7 - let ItemUsername = 8 - let ItemAffiliateProgram = 9 - - let ItemVerify = 10 - - let ItemIntro = 11 - let ItemCommands = 12 - let ItemBotSettings = 13 - let ItemBotInfo = 14 - - if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { - items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: presentationData.strings.PeerInfo_Bot_Username, icon: PresentationResourcesSettings.bot, action: { - interaction.editingOpenPublicLinkSetup() - })) - - var canSetupRefProgram = false - if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_program_allowed"] { - if let value = value as? Double { - canSetupRefProgram = value != 0.0 - } else if let value = value as? Bool { - canSetupRefProgram = value - } - } - - if canSetupRefProgram { - let programTitleValue: PeerInfoScreenDisclosureItem.Label - if let cachedData = data.cachedData as? CachedUserData, let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil { - programTitleValue = .labelBadge("\(formatPermille(starRefProgram.commissionPermille))%") - } else { - programTitleValue = .text(presentationData.strings.PeerInfo_ItemAffiliateProgram_ValueOff) - } - items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliateProgram, label: programTitleValue, additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.PeerInfo_ItemAffiliateProgram_Title, icon: PresentationResourcesSettings.affiliateProgram, action: { - interaction.editingOpenAffiliateProgram() - })) - } - - if let cachedUserData = data.cachedData as? CachedUserData, let _ = cachedUserData.botInfo?.verifierSettings { - items[.peerVerifySettings]!.append(PeerInfoScreenActionItem(id: ItemVerify, text: presentationData.strings.PeerInfo_VerifyAccounts, icon: UIImage(bundleImageName: "Peer Info/BotVerify"), action: { - interaction.editingOpenVerifyAccounts() - })) - } - - items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemIntro, text: presentationData.strings.PeerInfo_Bot_EditIntro, icon: UIImage(bundleImageName: "Peer Info/BotIntro"), action: { - interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-intro", behavior: .interactive))) - })) - items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemCommands, text: presentationData.strings.PeerInfo_Bot_EditCommands, icon: UIImage(bundleImageName: "Peer Info/BotCommands"), action: { - interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-commands", behavior: .interactive))) - })) - items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemBotSettings, text: presentationData.strings.PeerInfo_Bot_ChangeSettings, icon: UIImage(bundleImageName: "Peer Info/BotSettings"), action: { - interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: user.addressName ?? "", behavior: .interactive))) - })) - items[.peerSettings]!.append(PeerInfoScreenCommentItem(id: ItemBotInfo, text: presentationData.strings.PeerInfo_Bot_BotFatherInfo, linkAction: { _ in - interaction.openPeerMention("botfather", .default) - })) - } else if !user.flags.contains(.isSupport) { - let compactName = EnginePeer(user).compactDisplayTitle - - if let cachedData = data.cachedData as? CachedUserData { - items[.peerNote]!.append(PeerInfoScreenNoteListItem( - id: ItemNote, - initialValue: chatInputStateStringWithAppliedEntities(cachedData.note?.text ?? "", entities: cachedData.note?.entities ?? []), - valueUpdated: { value in - interaction.updateNote(value) - }, - requestLayout: { animated in - interaction.requestLayout(animated) - } - )) - - items[.peerNote]!.append(PeerInfoScreenCommentItem(id: ItemNoteInfo, text: presentationData.strings.PeerInfo_AddNotesInfo)) - - if let _ = cachedData.sendPaidMessageStars { - - } else { - if cachedData.birthday == nil { - items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggestBirthdate, text: presentationData.strings.UserInfo_SuggestBirthdate, color: .accent, icon: UIImage(bundleImageName: "Contact List/AddBirthdayIcon"), action: { - interaction.suggestBirthdate() - })) - } - - items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggestPhoto, text: presentationData.strings.UserInfo_SuggestPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Peer Info/SuggestAvatar"), action: { - interaction.suggestPhoto() - })) - } - } - - let setText: String - if user.photo.first?.isPersonal == true || state.updatingAvatar != nil { - setText = presentationData.strings.UserInfo_ChangeCustomPhoto(compactName).string - } else { - setText = presentationData.strings.UserInfo_SetCustomPhoto(compactName).string - } - - items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemCustomPhoto, text: setText, color: .accent, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: { - interaction.setCustomPhoto() - })) - - if user.photo.first?.isPersonal == true || state.updatingAvatar != nil { - var representation: TelegramMediaImageRepresentation? - var originalIsVideo: Bool? - if let cachedData = data.cachedData as? CachedUserData, case let .known(photo) = cachedData.photo { - representation = photo?.representationForDisplayAtSize(PixelDimensions(width: 28, height: 28)) - originalIsVideo = !(photo?.videoRepresentations.isEmpty ?? true) - } - - let removeText: String - if let originalIsVideo { - removeText = originalIsVideo ? presentationData.strings.UserInfo_ResetCustomVideo : presentationData.strings.UserInfo_ResetCustomPhoto - } else { - removeText = user.photo.first?.hasVideo == true ? presentationData.strings.UserInfo_RemoveCustomVideo : presentationData.strings.UserInfo_RemoveCustomPhoto - } - - let imageSignal: Signal - if let representation, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(user), authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: 28.0, height: 28.0)) { - imageSignal = signal - |> map { data -> UIImage? in - return data?.0 - } - } else { - imageSignal = peerAvatarCompleteImage(account: context.account, peer: EnginePeer(user), forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0)) - } - - items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemReset, text: removeText, color: .accent, icon: nil, iconSignal: imageSignal, action: { - interaction.resetCustomPhoto() - })) - } - items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemInfo, text: presentationData.strings.UserInfo_CustomPhotoInfo(compactName).string)) - } - - if data.isContact { - items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemDelete, text: presentationData.strings.UserInfo_DeleteContact, color: .destructive, action: { - interaction.requestDeleteContact() - })) - } - } else if let channel = data.peer as? TelegramChannel { - switch channel.info { - case .broadcast: - let ItemUsername = 1 - let ItemPeerColor = 2 - let ItemInviteLinks = 3 - let ItemDiscussionGroup = 4 - let ItemDeleteChannel = 5 - let ItemReactions = 6 - let ItemAdmins = 7 - let ItemMembers = 8 - let ItemMemberRequests = 9 - let ItemStats = 10 - let ItemBanned = 11 - let ItemRecentActions = 12 - let ItemAffiliatePrograms = 13 - let ItemPostSuggestionsSettings = 14 - let ItemPeerAutoTranslate = 15 - - let isCreator = channel.flags.contains(.isCreator) - - if isCreator { - let linkText: String - if let _ = channel.addressName { - linkText = presentationData.strings.Channel_Setup_TypePublic - } else { - linkText = presentationData.strings.Channel_Setup_TypePrivate - } - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(linkText), text: presentationData.strings.Channel_TypeSetup_Title, icon: UIImage(bundleImageName: "Chat/Info/GroupChannelIcon"), action: { - interaction.editingOpenPublicLinkSetup() - })) - } - - if (isCreator && (channel.addressName?.isEmpty ?? true)) || (!channel.flags.contains(.isCreator) && channel.adminRights?.rights.contains(.canInviteUsers) == true) { - let invitesText: String - if let count = data.invitations?.count, count > 0 { - invitesText = "\(count)" - } else { - invitesText = "" - } - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { - interaction.editingOpenInviteLinksSetup() - })) - } - - if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { - let discussionGroupTitle: String - if let _ = data.cachedData as? CachedChannelData { - if let peer = data.linkedDiscussionPeer { - if let addressName = peer.addressName, !addressName.isEmpty { - discussionGroupTitle = "@\(addressName)" - } else { - discussionGroupTitle = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - } - } else { - discussionGroupTitle = presentationData.strings.Channel_DiscussionGroupAdd - } - } else { - discussionGroupTitle = "..." - } - - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: { - interaction.editingOpenDiscussionGroupSetup() - })) - } - - if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { - let label: String - if let cachedData = data.cachedData as? CachedChannelData, case let .known(reactionSettings) = cachedData.reactionSettings { - switch reactionSettings.allowedReactions { - case .all: - label = presentationData.strings.PeerInfo_LabelAllReactions - case .empty: - if let starsAllowed = reactionSettings.starsAllowed, starsAllowed { - label = "1" - } else { - label = presentationData.strings.PeerInfo_ReactionsDisabled - } - case let .limited(reactions): - var countValue = reactions.count - if let starsAllowed = reactionSettings.starsAllowed, starsAllowed { - countValue += 1 - } - label = "\(countValue)" - } - } else { - label = "" - } - let additionalBadgeLabel: String? = nil - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), additionalBadgeLabel: additionalBadgeLabel, text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { - interaction.editingOpenReactionsSetup() - })) - } - - if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { - var colors: [PeerNameColors.Colors] = [] - if let nameColor = channel.nameColor.flatMap({ context.peerNameColors.get($0, dark: presentationData.theme.overallDarkAppearance) }) { - colors.append(nameColor) - } - if let profileColor = channel.profileColor.flatMap({ context.peerNameColors.getProfile($0, dark: presentationData.theme.overallDarkAppearance, subject: .palette) }) { - colors.append(profileColor) - } - let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors) - - var boostIcon: UIImage? - if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel < 1 { - boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Info_BoostLevelPlusBadge("1").string) - } else { - /*let labelText = NSAttributedString(string: presentationData.strings.Settings_New, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) - let labelBounds = labelText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil) - let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height)) - let badgeSize = CGSize(width: labelSize.width + 8.0, height: labelSize.height + 2.0 + 1.0) - boostIcon = generateImage(badgeSize, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - let rect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - UIScreenPixel * 2.0)) - - context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 5.0).cgPath) - context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor) - context.fillPath() - - UIGraphicsPushContext(context) - labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel)) - UIGraphicsPopContext() - })*/ - } - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: presentationData.strings.Channel_Info_AppearanceItem, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: { - interaction.editingOpenNameColorSetup() - })) - - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) - var isLocked = true - if let boostLevel = boostStatus?.level, boostLevel >= BoostSubject.autoTranslate.requiredLevel(group: false, context: context, configuration: premiumConfiguration) { - isLocked = false - } - items[.peerSettings]!.append(PeerInfoScreenSwitchItem(id: ItemPeerAutoTranslate, text: presentationData.strings.Channel_Info_AutoTranslate, value: channel.flags.contains(.autoTranslateEnabled), icon: UIImage(bundleImageName: "Settings/Menu/AutoTranslate"), isLocked: isLocked, toggled: { value in - if isLocked { - interaction.displayAutoTranslateLocked() - } else { - interaction.editingToggleAutoTranslate(value) - } - })) - } - - if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { - let labelString: NSAttributedString - if channel.linkedMonoforumId != nil { - if let monoforumPeer = data.linkedMonoforumPeer as? TelegramChannel { - if let sendPaidMessageStars = monoforumPeer.sendPaidMessageStars { - let formattedLabel = formatStarsAmountText(sendPaidMessageStars, dateTimeFormat: presentationData.dateTimeFormat) - let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) - let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) - let labelColor = presentationData.theme.list.itemSecondaryTextColor - let attributedString = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString - attributedString.insert(NSAttributedString(string: "*", font: labelFont, textColor: labelColor), at: 0) - - if let range = attributedString.string.range(of: "*") { - attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string)) - attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string)) - } - labelString = attributedString - } else { - let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) - let labelColor = presentationData.theme.list.itemSecondaryTextColor - - labelString = NSAttributedString(string: presentationData.strings.PeerInfo_AllowChannelMessages_Free, font: labelFont, textColor: labelColor) - } - } else { - let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) - let labelColor = presentationData.theme.list.itemSecondaryTextColor - - labelString = NSAttributedString(string: " ", font: labelFont, textColor: labelColor) - } - } else { - let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) - let labelColor = presentationData.theme.list.itemSecondaryTextColor - - labelString = NSAttributedString(string: presentationData.strings.PeerInfo_AllowChannelMessages_Off, font: labelFont, textColor: labelColor) - } - - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .attributedText(labelString), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.PeerInfo_AllowChannelMessages, icon: PresentationResourcesSettings.channelMessages, action: { - interaction.editingOpenPostSuggestionsSetup() - })) - - if let personalChannel = data.personalChannel { - let peerId = personalChannel.peer.peerId - items[.linkedMonoforum]?.append(PeerInfoScreenPersonalChannelItem(id: 1, context: context, data: personalChannel, controller: { [weak interaction] in - guard let interaction else { - return nil - } - return interaction.getController() - }, action: { [weak interaction] in - guard let interaction else { - return - } - interaction.openChat(peerId) - })) - } - } - - var canEditMembers = false - if channel.hasPermission(.banMembers) && (channel.adminRights != nil || channel.flags.contains(.isCreator)) { - canEditMembers = true - } - if canEditMembers { - let adminCount: Int32 - let memberCount: Int32 - if let cachedData = data.cachedData as? CachedChannelData { - adminCount = cachedData.participantsSummary.adminCount ?? 0 - memberCount = cachedData.participantsSummary.memberCount ?? 0 - } else { - adminCount = 0 - memberCount = 0 - } - - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text("\(adminCount == 0 ? "" : "\(presentationStringsFormattedNumber(adminCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { - interaction.openParticipantsSection(.admins) - })) - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text("\(memberCount == 0 ? "" : "\(presentationStringsFormattedNumber(memberCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.Channel_Info_Subscribers, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: { - interaction.openParticipantsSection(.members) - })) - - if let count = data.requests?.count, count > 0 { - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { - interaction.openParticipantsSection(.memberRequests) - })) - } - } - - if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) { - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStats, label: .none, text: presentationData.strings.Channel_Info_Stats, icon: UIImage(bundleImageName: "Chat/Info/StatsIcon"), action: { - interaction.openStats(.stats) - })) - } - - if canEditMembers { - let bannedCount: Int32 - if let cachedData = data.cachedData as? CachedChannelData { - bannedCount = cachedData.participantsSummary.kickedCount ?? 0 - } else { - bannedCount = 0 - } - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemBanned, label: .text("\(bannedCount == 0 ? "" : "\(presentationStringsFormattedNumber(bannedCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: { - interaction.openParticipantsSection(.banned) - })) - - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRecentActions, label: .none, text: presentationData.strings.Group_Info_AdminLog, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), action: { - interaction.openRecentActions() - })) - } - - if channel.hasPermission(.changeInfo) { - var canJoinRefProgram = false - if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] { - if let value = value as? Double { - canJoinRefProgram = value != 0.0 - } else if let value = value as? Bool { - canJoinRefProgram = value - } - } - - if canJoinRefProgram { - items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliatePrograms, label: .text(""), additionalBadgeLabel: nil, text: presentationData.strings.PeerInfo_ItemAffiliatePrograms_Title, icon: PresentationResourcesSettings.affiliateProgram, action: { - interaction.editingOpenAffiliateProgram() - })) - } - } - - if isCreator { //if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canDeleteHistory) { - items[.peerActions]!.append(PeerInfoScreenActionItem(id: ItemDeleteChannel, text: presentationData.strings.ChannelInfo_DeleteChannel, color: .destructive, icon: nil, alignment: .natural, action: { - interaction.openDeletePeer() - })) - } - case .group: - let ItemUsername = 101 - let ItemInviteLinks = 102 - let ItemLinkedChannel = 103 - let ItemPreHistory = 104 - let ItemMembers = 106 - let ItemPermissions = 107 - let ItemAdmins = 108 - let ItemMemberRequests = 109 - let ItemRemovedUsers = 110 - let ItemRecentActions = 111 - let ItemLocationHeader = 112 - let ItemLocation = 113 - let ItemLocationSetup = 114 - let ItemDeleteGroup = 115 - let ItemReactions = 116 - let ItemTopics = 117 - let ItemTopicsText = 118 - let ItemAppearance = 119 - - let isCreator = channel.flags.contains(.isCreator) - let isPublic = channel.addressName != nil - - if let cachedData = data.cachedData as? CachedChannelData { - if isCreator, let location = cachedData.peerGeoLocation { - items[.groupLocation]!.append(PeerInfoScreenHeaderItem(id: ItemLocationHeader, text: presentationData.strings.GroupInfo_Location.uppercased())) - - let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) - items[.groupLocation]!.append(PeerInfoScreenAddressItem( - id: ItemLocation, - label: "", - text: location.address.replacingOccurrences(of: ", ", with: "\n"), - imageSignal: imageSignal, - action: { - interaction.openLocation() - } - )) - if cachedData.flags.contains(.canChangePeerGeoLocation) { - items[.groupLocation]!.append(PeerInfoScreenActionItem(id: ItemLocationSetup, text: presentationData.strings.Group_Location_ChangeLocation, action: { - interaction.editingOpenSetupLocation() - })) - } - } - - if isCreator || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) { - if cachedData.peerGeoLocation != nil { - if isCreator { - let linkText: String - if let username = channel.addressName { - linkText = "@\(username)" - } else { - linkText = presentationData.strings.GroupInfo_PublicLinkAdd - } - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(linkText), text: presentationData.strings.GroupInfo_PublicLink, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { - interaction.editingOpenPublicLinkSetup() - })) - } - } else { - if cachedData.flags.contains(.canChangeUsername) { - items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(isPublic ? presentationData.strings.Group_Setup_TypePublic : presentationData.strings.Group_Setup_TypePrivate), text: presentationData.strings.GroupInfo_GroupType, icon: UIImage(bundleImageName: "Chat/Info/GroupTypeIcon"), action: { - interaction.editingOpenPublicLinkSetup() - })) - } - } - } - - if (isCreator && (channel.addressName?.isEmpty ?? true) && cachedData.peerGeoLocation == nil) || (!isCreator && channel.adminRights?.rights.contains(.canInviteUsers) == true) { - let invitesText: String - if let count = data.invitations?.count, count > 0 { - invitesText = "\(count)" - } else { - invitesText = "" - } - - items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { - interaction.editingOpenInviteLinksSetup() - })) - } - - if (isCreator || (channel.adminRights != nil && channel.hasPermission(.pinMessages))) && cachedData.peerGeoLocation == nil { - if let linkedDiscussionPeer = data.linkedDiscussionPeer { - let peerTitle: String - if let addressName = linkedDiscussionPeer.addressName, !addressName.isEmpty { - peerTitle = "@\(addressName)" - } else { - peerTitle = EnginePeer(linkedDiscussionPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - } - items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemLinkedChannel, label: .text(peerTitle), text: presentationData.strings.Group_LinkedChannel, icon: UIImage(bundleImageName: "Chat/Info/GroupLinkedChannelIcon"), action: { - interaction.editingOpenDiscussionGroupSetup() - })) - } - - if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { - let label: String - if let cachedData = data.cachedData as? CachedChannelData, case let .known(reactionSettings) = cachedData.reactionSettings { - switch reactionSettings.allowedReactions { - case .all: - label = presentationData.strings.PeerInfo_LabelAllReactions - case .empty: - label = presentationData.strings.PeerInfo_ReactionsDisabled - case let .limited(reactions): - label = "\(reactions.count)" - } - } else { - label = "" - } - items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { - interaction.editingOpenReactionsSetup() - })) - } - } else { - if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { - let label: String - if let cachedData = data.cachedData as? CachedChannelData, case let .known(reactionSettings) = cachedData.reactionSettings { - switch reactionSettings.allowedReactions { - case .all: - label = presentationData.strings.PeerInfo_LabelAllReactions - case .empty: - label = presentationData.strings.PeerInfo_ReactionsDisabled - case let .limited(reactions): - label = "\(reactions.count)" - } - } else { - label = "" - } - items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { - interaction.editingOpenReactionsSetup() - })) - } - } - - if isCreator || channel.adminRights?.rights.contains(.canChangeInfo) == true { - var colors: [PeerNameColors.Colors] = [] - if let nameColor = channel.nameColor.flatMap({ context.peerNameColors.get($0, dark: presentationData.theme.overallDarkAppearance) }) { - colors.append(nameColor) - } - if let profileColor = channel.profileColor.flatMap({ context.peerNameColors.getProfile($0, dark: presentationData.theme.overallDarkAppearance, subject: .palette) }) { - colors.append(profileColor) - } - let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors) - - var boostIcon: UIImage? - if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel < 1 { - boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Info_BoostLevelPlusBadge("1").string) - } else { - boostIcon = nil - /*let labelText = NSAttributedString(string: presentationData.strings.Settings_New, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) - let labelBounds = labelText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil) - let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height)) - let badgeSize = CGSize(width: labelSize.width + 8.0, height: labelSize.height + 2.0 + 1.0) - boostIcon = generateImage(badgeSize, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - let rect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - UIScreenPixel * 2.0)) - - context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 5.0).cgPath) - context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor) - context.fillPath() - - UIGraphicsPushContext(context) - labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel)) - UIGraphicsPopContext() - })*/ - } - items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAppearance, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: presentationData.strings.Channel_Info_AppearanceItem, icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: { - interaction.editingOpenNameColorSetup() - })) - } - - if (isCreator || (channel.adminRights != nil && channel.hasPermission(.banMembers))) && cachedData.peerGeoLocation == nil, !isPublic, case .known(nil) = cachedData.linkedDiscussionPeerId, !channel.isForumOrMonoForum { - items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(cachedData.flags.contains(.preHistoryEnabled) ? presentationData.strings.GroupInfo_GroupHistoryVisible : presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistoryShort, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: { - interaction.editingOpenPreHistorySetup() - })) - } - - if isCreator, let appConfiguration = data.appConfiguration { - var minParticipants = 200 - if let data = appConfiguration.data, let value = data["forum_upgrade_participants_min"] as? Double { - minParticipants = Int(value) - } - - var canSetupTopics = false - var topicsLimitedReason: TopicsLimitedReason? - if channel.flags.contains(.isForum) { - canSetupTopics = true - } else if case let .known(value) = cachedData.linkedDiscussionPeerId, value != nil { - canSetupTopics = true - topicsLimitedReason = .discussion - } else if let memberCount = cachedData.participantsSummary.memberCount { - canSetupTopics = true - if Int(memberCount) < minParticipants { - topicsLimitedReason = .participants(minParticipants) - } - } - - if canSetupTopics { - let label = channel.flags.contains(.isForum) ? presentationData.strings.PeerInfo_OptionTopics_Enabled : presentationData.strings.PeerInfo_OptionTopics_Disabled - items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemTopics, label: .text(label), text: presentationData.strings.PeerInfo_OptionTopics, icon: UIImage(bundleImageName: "Settings/Menu/Topics"), action: { - if let topicsLimitedReason = topicsLimitedReason { - interaction.displayTopicsLimited(topicsLimitedReason) - } else { - interaction.openForumSettings() - } - })) - - items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemTopicsText, text: presentationData.strings.PeerInfo_OptionTopicsText)) - } - } - - var canViewAdminsAndBanned = false - if let _ = channel.adminRights { - canViewAdminsAndBanned = true - } else if channel.flags.contains(.isCreator) { - canViewAdminsAndBanned = true - } - - if canViewAdminsAndBanned { - var activePermissionCount: Int? - if let defaultBannedRights = channel.defaultBannedRights { - var count = 0 - for (right, _) in allGroupPermissionList(peer: .channel(channel), expandMedia: true) { - if right == .banSendMedia { - if banSendMediaSubList().allSatisfy({ !defaultBannedRights.flags.contains($0.0) }) { - count += 1 - } - } else { - if !defaultBannedRights.flags.contains(right) { - count += 1 - } - } - } - activePermissionCount = count - } - - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text(cachedData.participantsSummary.memberCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.Group_Info_Members, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: { - interaction.openParticipantsSection(.members) - })) - if !channel.flags.contains(.isGigagroup) { - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPermissions, label: .text(activePermissionCount.flatMap({ "\($0)/\(allGroupPermissionList(peer: .channel(channel), expandMedia: true).count)" }) ?? ""), text: presentationData.strings.GroupInfo_Permissions, icon: UIImage(bundleImageName: "Settings/Menu/SetPasscode"), action: { - interaction.openPermissions() - })) - } - - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text(cachedData.participantsSummary.adminCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { - interaction.openParticipantsSection(.admins) - })) - - if let count = data.requests?.count, count > 0 { - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { - interaction.openParticipantsSection(.memberRequests) - })) - } - - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRemovedUsers, label: .text(cachedData.participantsSummary.kickedCount.flatMap { $0 > 0 ? "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" : "" } ?? ""), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: { - interaction.openParticipantsSection(.banned) - })) - - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRecentActions, label: .none, text: presentationData.strings.Group_Info_AdminLog, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), action: { - interaction.openRecentActions() - })) - } - - if isCreator { - items[.peerActions]!.append(PeerInfoScreenActionItem(id: ItemDeleteGroup, text: presentationData.strings.Group_DeleteGroup, color: .destructive, icon: nil, alignment: .natural, action: { - interaction.openDeletePeer() - })) - } - } - } - } else if let group = data.peer as? TelegramGroup { - let ItemUsername = 101 - let ItemInviteLinks = 102 - let ItemPreHistory = 103 - let ItemPermissions = 104 - let ItemAdmins = 105 - let ItemMemberRequests = 106 - let ItemReactions = 107 - let ItemTopics = 108 - let ItemTopicsText = 109 - - var canViewAdminsAndBanned = false - - if case .creator = group.role { - if let cachedData = data.cachedData as? CachedGroupData { - if cachedData.flags.contains(.canChangeUsername) { - items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(presentationData.strings.Group_Setup_TypePrivate), text: presentationData.strings.GroupInfo_GroupType, icon: UIImage(bundleImageName: "Chat/Info/GroupTypeIcon"), action: { - interaction.editingOpenPublicLinkSetup() - })) - } - } - - if (group.addressName?.isEmpty ?? true) { - let invitesText: String - if let count = data.invitations?.count, count > 0 { - invitesText = "\(count)" - } else { - invitesText = "" - } - - items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { - interaction.editingOpenInviteLinksSetup() - })) - } - - items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistoryShort, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: { - interaction.editingOpenPreHistorySetup() - })) - - var canSetupTopics = false - if case .creator = group.role { - canSetupTopics = true - } - var topicsLimitedReason: TopicsLimitedReason? - if let appConfiguration = data.appConfiguration { - var minParticipants = 200 - if let data = appConfiguration.data, let value = data["forum_upgrade_participants_min"] as? Double { - minParticipants = Int(value) - } - if Int(group.participantCount) < minParticipants { - topicsLimitedReason = .participants(minParticipants) - } - } - - if canSetupTopics { - items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemTopics, label: .text(presentationData.strings.PeerInfo_OptionTopics_Disabled), text: presentationData.strings.PeerInfo_OptionTopics, icon: UIImage(bundleImageName: "Settings/Menu/Topics"), action: { - if let topicsLimitedReason = topicsLimitedReason { - interaction.displayTopicsLimited(topicsLimitedReason) - } else { - interaction.openForumSettings() - } - })) - - items[.peerPublicSettings]!.append(PeerInfoScreenCommentItem(id: ItemTopicsText, text: presentationData.strings.PeerInfo_OptionTopicsText)) - } - - let label: String - if let cachedData = data.cachedData as? CachedGroupData, case let .known(reactionSettings) = cachedData.reactionSettings { - switch reactionSettings.allowedReactions { - case .all: - label = presentationData.strings.PeerInfo_LabelAllReactions - case .empty: - label = presentationData.strings.PeerInfo_ReactionsDisabled - case let .limited(reactions): - label = "\(reactions.count)" - } - } else { - label = "" - } - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { - interaction.editingOpenReactionsSetup() - })) - - canViewAdminsAndBanned = true - } else if case let .admin(rights, _) = group.role { - let label: String - if let cachedData = data.cachedData as? CachedGroupData, case let .known(reactionSettings) = cachedData.reactionSettings { - switch reactionSettings.allowedReactions { - case .all: - label = presentationData.strings.PeerInfo_LabelAllReactions - case .empty: - label = presentationData.strings.PeerInfo_ReactionsDisabled - case let .limited(reactions): - label = "\(reactions.count)" - } - } else { - label = "" - } - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { - interaction.editingOpenReactionsSetup() - })) - - if rights.rights.contains(.canInviteUsers) { - let invitesText: String - if let count = data.invitations?.count, count > 0 { - invitesText = "\(count)" - } else { - invitesText = "" - } - - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { - interaction.editingOpenInviteLinksSetup() - })) - } - - canViewAdminsAndBanned = true - } - - if canViewAdminsAndBanned { - var activePermissionCount: Int? - if let defaultBannedRights = group.defaultBannedRights { - var count = 0 - for (right, _) in allGroupPermissionList(peer: .legacyGroup(group), expandMedia: true) { - if right == .banSendMedia { - if banSendMediaSubList().allSatisfy({ !defaultBannedRights.flags.contains($0.0) }) { - count += 1 - } - } else { - if !defaultBannedRights.flags.contains(right) { - count += 1 - } - } - } - activePermissionCount = count - } - - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPermissions, label: .text(activePermissionCount.flatMap({ "\($0)/\(allGroupPermissionList(peer: .legacyGroup(group), expandMedia: true).count)" }) ?? ""), text: presentationData.strings.GroupInfo_Permissions, icon: UIImage(bundleImageName: "Settings/Menu/SetPasscode"), action: { - interaction.openPermissions() - })) - - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { - interaction.openParticipantsSection(.admins) - })) - - if let count = data.requests?.count, count > 0 { - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { - interaction.openParticipantsSection(.memberRequests) - })) - } - } - } - } - - var result: [(AnyHashable, [PeerInfoScreenItem])] = [] - for section in Section.allCases { - if let sectionItems = items[section], !sectionItems.isEmpty { - result.append((section, sectionItems)) - } - } - return result -} - final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodeProtocol, ASScrollViewDelegate { - private weak var controller: PeerInfoScreenImpl? + weak var controller: PeerInfoScreenImpl? - private let context: AccountContext + let context: AccountContext let peerId: PeerId - private let isOpenedFromChat: Bool - private let videoCallsEnabled: Bool - private let callMessages: [Message] - private let chatLocation: ChatLocation - private let chatLocationContextHolder: Atomic - private let switchToStoryFolder: Int64? - private let switchToGiftsTarget: PeerInfoSwitchToGiftsTarget? - private let sharedMediaFromForumTopic: (EnginePeer.Id, Int64)? + let isOpenedFromChat: Bool + let videoCallsEnabled: Bool + let callMessages: [Message] + let chatLocation: ChatLocation + let chatLocationContextHolder: Atomic + let switchToStoryFolder: Int64? + let switchToGiftsTarget: PeerInfoSwitchToGiftsTarget? + let sharedMediaFromForumTopic: (EnginePeer.Id, Int64)? let isSettings: Bool let isMyProfile: Bool - private let isMediaOnly: Bool + let isMediaOnly: Bool let initialExpandPanes: Bool - private var presentationData: PresentationData + private(set) var presentationData: PresentationData - fileprivate let cachedDataPromise = Promise() + let cachedDataPromise = Promise() let scrollNode: ASScrollNode - private let edgeEffectView: EdgeEffectView + let edgeEffectView: EdgeEffectView let headerNode: PeerInfoHeaderNode - private var regularSections: [AnyHashable: PeerInfoScreenItemSectionContainerNode] = [:] - private var editingSections: [AnyHashable: PeerInfoScreenItemSectionContainerNode] = [:] - private let paneContainerNode: PeerInfoPaneContainerNode - private var ignoreScrolling: Bool = false - private lazy var hapticFeedback = { HapticFeedback() }() + var regularSections: [AnyHashable: PeerInfoScreenItemSectionContainerNode] = [:] + var editingSections: [AnyHashable: PeerInfoScreenItemSectionContainerNode] = [:] + let paneContainerNode: PeerInfoPaneContainerNode + var ignoreScrolling: Bool = false + lazy var hapticFeedback = { HapticFeedback() }() - private var customStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?) - private let customStatusPromise = Promise<(PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?)>((nil, nil, nil)) - private var customStatusDisposable: Disposable? + var customStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?) + let customStatusPromise = Promise<(PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?)>((nil, nil, nil)) + var customStatusDisposable: Disposable? - private var refreshMessageTagStatsDisposable: Disposable? + var refreshMessageTagStatsDisposable: Disposable? - private var searchDisplayController: SearchDisplayController? + var searchDisplayController: SearchDisplayController? private var _interaction: PeerInfoInteraction? - fileprivate var interaction: PeerInfoInteraction { + var interaction: PeerInfoInteraction { return self._interaction! } - private var _chatInterfaceInteraction: ChatControllerInteraction? - private var chatInterfaceInteraction: ChatControllerInteraction { + var _chatInterfaceInteraction: ChatControllerInteraction? + var chatInterfaceInteraction: ChatControllerInteraction { return self._chatInterfaceInteraction! } - private var hiddenMediaDisposable: Disposable? - private let hiddenAvatarRepresentationDisposable = MetaDisposable() + var hiddenMediaDisposable: Disposable? + let hiddenAvatarRepresentationDisposable = MetaDisposable() - private var autoTranslateDisposable: Disposable? + var autoTranslateDisposable: Disposable? - private var resolvePeerByNameDisposable: MetaDisposable? - private let navigationActionDisposable = MetaDisposable() - private let enqueueMediaMessageDisposable = MetaDisposable() + var resolvePeerByNameDisposable: MetaDisposable? + let navigationActionDisposable = MetaDisposable() + let enqueueMediaMessageDisposable = MetaDisposable() private(set) var validLayout: (ContainerViewLayout, CGFloat)? private(set) var data: PeerInfoScreenData? + var state = PeerInfoState( isEditing: false, selectedMessageIds: nil, @@ -3070,62 +278,64 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro updatingBirthDate: nil, personalChannels: nil ) - private var forceIsContactPromise = ValuePromise(false) - private let nearbyPeerDistance: Int32? - private let reactionSourceMessageId: MessageId? - private var dataDisposable: Disposable? + var forceIsContactPromise = ValuePromise(false) + let nearbyPeerDistance: Int32? + let reactionSourceMessageId: MessageId? + var dataDisposable: Disposable? - private let activeActionDisposable = MetaDisposable() - private let resolveUrlDisposable = MetaDisposable() - private let toggleShouldChannelMessagesSignaturesDisposable = MetaDisposable() - private let toggleMessageCopyProtectionDisposable = MetaDisposable() - private let selectAddMemberDisposable = MetaDisposable() - private let addMemberDisposable = MetaDisposable() - private let preloadHistoryDisposable = MetaDisposable() - private var shareStatusDisposable: MetaDisposable? - private let joinChannelDisposable = MetaDisposable() + let activeActionDisposable = MetaDisposable() + let resolveUrlDisposable = MetaDisposable() + let toggleShouldChannelMessagesSignaturesDisposable = MetaDisposable() + let toggleMessageCopyProtectionDisposable = MetaDisposable() + let selectAddMemberDisposable = MetaDisposable() + let addMemberDisposable = MetaDisposable() + let preloadHistoryDisposable = MetaDisposable() + var shareStatusDisposable: MetaDisposable? + let joinChannelDisposable = MetaDisposable() let updateAvatarDisposable = MetaDisposable() - private let currentAvatarMixin = Atomic(value: nil) + let currentAvatarMixin = Atomic(value: nil) - private var groupMembersSearchContext: GroupMembersSearchContext? + var groupMembersSearchContext: GroupMembersSearchContext? - private let displayAsPeersPromise = Promise<[FoundPeer]>([]) + let displayAsPeersPromise = Promise<[FoundPeer]>([]) - fileprivate let accountsAndPeers = Promise<[(AccountContext, EnginePeer, Int32)]>() - fileprivate let activeSessionsContextAndCount = Promise<(ActiveSessionsContext, Int, WebSessionsContext)?>() - private let notificationExceptions = Promise() - fileprivate let privacySettings = Promise() - private let archivedPacks = Promise<[ArchivedStickerPackItem]?>() - private let blockedPeers = Promise(nil) - private let hasTwoStepAuth = Promise(nil) - private let twoStepAccessConfiguration = Promise(nil) - private let twoStepAuthData = Promise(nil) - private let supportPeerDisposable = MetaDisposable() - private let tipsPeerDisposable = MetaDisposable() - private let cachedFaq = Promise(nil) + let accountsAndPeers = Promise<[(AccountContext, EnginePeer, Int32)]>() + let activeSessionsContextAndCount = Promise<(ActiveSessionsContext, Int, WebSessionsContext)?>() + let notificationExceptions = Promise() + let privacySettings = Promise() + let archivedPacks = Promise<[ArchivedStickerPackItem]?>() + let blockedPeers = Promise(nil) + let hasTwoStepAuth = Promise(nil) + let twoStepAccessConfiguration = Promise(nil) + let twoStepAuthData = Promise(nil) + let supportPeerDisposable = MetaDisposable() + let tipsPeerDisposable = MetaDisposable() + let cachedFaq = Promise(nil) - private weak var copyProtectionTooltipController: TooltipController? + weak var copyProtectionTooltipController: TooltipController? weak var emojiStatusSelectionController: ViewController? - private var forumTopicNotificationExceptions: [EngineMessageHistoryThread.NotificationException] = [] - private var forumTopicNotificationExceptionsDisposable: Disposable? + var forumTopicNotificationExceptions: [EngineMessageHistoryThread.NotificationException] = [] + var forumTopicNotificationExceptionsDisposable: Disposable? - private var translationState: ChatTranslationState? - private var translationStateDisposable: Disposable? + var translationState: ChatTranslationState? + var translationStateDisposable: Disposable? - private var boostStatus: ChannelBoostStatus? - private var boostStatusDisposable: Disposable? + var boostStatus: ChannelBoostStatus? + var boostStatusDisposable: Disposable? - private var expiringStoryList: PeerExpiringStoryListContext? - private var expiringStoryListState: PeerExpiringStoryListContext.State? - private var expiringStoryListDisposable: Disposable? - private var storyUploadProgressDisposable: Disposable? - private var postingAvailabilityDisposable: Disposable? + var expiringStoryList: PeerExpiringStoryListContext? + var expiringStoryListState: PeerExpiringStoryListContext.State? + var expiringStoryListDisposable: Disposable? + var storyUploadProgressDisposable: Disposable? + var postingAvailabilityDisposable: Disposable? - private let storiesReady = ValuePromise(true, ignoreRepeated: true) + let storiesReady = ValuePromise(true, ignoreRepeated: true) - private var personalChannelsDisposable: Disposable? + var personalChannelsDisposable: Disposable? + + var effectiveAreaExpansionFraction: CGFloat = 0.0 private let _ready = Promise() var ready: Promise { @@ -3273,9 +483,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro openPermissions: { [weak self] in self?.openPermissions() }, - editingOpenStickerPackSetup: { [weak self] in - self?.editingOpenStickerPackSetup() - }, openLocation: { [weak self] in self?.openLocation() }, @@ -4047,6 +1254,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.scrollNode.view.delegate = self.wrappedScrollViewDelegate self.addSubnode(self.scrollNode) self.scrollNode.addSubnode(self.paneContainerNode) + self.scrollNode.view.addSubview(self.headerNode.headerEdgeEffectContainer) + self.scrollNode.view.addSubview(self.paneContainerNode.headerContainer) self.view.addSubview(self.edgeEffectView) @@ -4847,6 +2056,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } + self.headerNode.updateUnderHeaderContentsAlpha = { [weak self] alpha, transition in + guard let self else { + return + } + if !self.state.isEditing { + for (_, section) in self.regularSections { + transition.updateAlpha(node: section, alpha: alpha) + } + } + } + let screenData: Signal if self.isSettings { self.notificationExceptions.set(.single(NotificationExceptionsList(peers: [:], settings: [:])) @@ -5608,467 +2828,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } - private func openStories(fromAvatar: Bool) { - guard let controller = self.controller else { - return - } - if let expiringStoryList = self.expiringStoryList, let expiringStoryListState = self.expiringStoryListState, !expiringStoryListState.items.isEmpty { - if fromAvatar { - StoryContainerScreen.openPeerStories(context: self.context, peerId: self.peerId, parentController: controller, avatarNode: self.headerNode.avatarListNode.avatarContainerNode.avatarNode) - return - } - - let _ = expiringStoryList - let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: self.peerId, singlePeer: true) - let _ = (storyContent.state - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak self] storyContentState in - guard let self else { - return - } - var transitionIn: StoryContainerScreen.TransitionIn? - - if fromAvatar { - let transitionView = self.headerNode.avatarListNode.avatarContainerNode.avatarNode.view - transitionIn = StoryContainerScreen.TransitionIn( - sourceView: transitionView, - sourceRect: transitionView.bounds, - sourceCornerRadius: transitionView.bounds.height * 0.5, - sourceIsAvatar: true - ) - self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = true - } else if let (expandedStorySetIndicatorTransitionView, subRect) = self.headerNode.avatarListNode.listContainerNode.expandedStorySetIndicatorTransitionView { - transitionIn = StoryContainerScreen.TransitionIn( - sourceView: expandedStorySetIndicatorTransitionView, - sourceRect: subRect, - sourceCornerRadius: expandedStorySetIndicatorTransitionView.bounds.height * 0.5, - sourceIsAvatar: false - ) - expandedStorySetIndicatorTransitionView.isHidden = true - } - - let storyContainerScreen = StoryContainerScreen( - context: self.context, - content: storyContent, - transitionIn: transitionIn, - transitionOut: { [weak self] peerId, _ in - guard let self else { - return nil - } - if !fromAvatar { - self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = false - - if let (expandedStorySetIndicatorTransitionView, subRect) = self.headerNode.avatarListNode.listContainerNode.expandedStorySetIndicatorTransitionView { - return StoryContainerScreen.TransitionOut( - destinationView: expandedStorySetIndicatorTransitionView, - transitionView: StoryContainerScreen.TransitionView( - makeView: { [weak expandedStorySetIndicatorTransitionView] in - let parentView = UIView() - if let copyView = expandedStorySetIndicatorTransitionView?.snapshotContentTree(unhide: true) { - copyView.layer.anchorPoint = CGPoint() - parentView.addSubview(copyView) - } - return parentView - }, - updateView: { copyView, state, transition in - guard let view = copyView.subviews.first else { - return - } - let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress) - transition.setPosition(view: view, position: CGPoint(x: 0.0, y: 0.0)) - transition.setScale(view: view, scale: size.width / state.destinationSize.width) - }, - insertCloneTransitionView: nil - ), - destinationRect: subRect, - destinationCornerRadius: expandedStorySetIndicatorTransitionView.bounds.height * 0.5, - destinationIsAvatar: false, - completed: { [weak self] in - guard let self else { - return - } - - if let (expandedStorySetIndicatorTransitionView, _) = self.headerNode.avatarListNode.listContainerNode.expandedStorySetIndicatorTransitionView { - expandedStorySetIndicatorTransitionView.isHidden = false - } - } - ) - } - - return nil - } - - let transitionView = self.headerNode.avatarListNode.avatarContainerNode.avatarNode.view - return StoryContainerScreen.TransitionOut( - destinationView: transitionView, - transitionView: StoryContainerScreen.TransitionView( - makeView: { [weak transitionView] in - let parentView = UIView() - if let copyView = transitionView?.snapshotContentTree(unhide: true) { - parentView.addSubview(copyView) - } - return parentView - }, - updateView: { copyView, state, transition in - guard let view = copyView.subviews.first else { - return - } - let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress) - transition.setPosition(view: view, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5)) - transition.setScale(view: view, scale: size.width / state.destinationSize.width) - }, - insertCloneTransitionView: nil - ), - destinationRect: transitionView.bounds, - destinationCornerRadius: transitionView.bounds.height * 0.5, - destinationIsAvatar: true, - completed: { [weak self] in - guard let self else { - return - } - self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = false - } - ) - } - ) - self.controller?.push(storyContainerScreen) - }) - - return - } - } - - private func openMessage(id: MessageId) -> Bool { - guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else { - return false - } - var foundGalleryMessage: Message? - if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { - if let galleryMessage = searchContentNode.messageForGallery(id) { - self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: [EngineMessage(galleryMessage)]) - foundGalleryMessage = galleryMessage - } - } - if foundGalleryMessage == nil, let galleryMessage = self.paneContainerNode.findLoadedMessage(id: id) { - foundGalleryMessage = galleryMessage - } - - guard let galleryMessage = foundGalleryMessage else { - return false - } - self.view.endEditing(true) - - return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: self.chatLocation, chatFilterTag: nil, chatLocationContextHolder: self.chatLocationContextHolder, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in - self?.view.endEditing(true) - }, present: { [weak self] c, a, _ in - self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true) - }, transitionNode: { [weak self] messageId, media, _ in - guard let strongSelf = self else { - return nil - } - return strongSelf.paneContainerNode.transitionNodeForGallery(messageId: messageId, media: media) - }, addToTransitionSurface: { [weak self] view in - guard let strongSelf = self else { - return - } - strongSelf.paneContainerNode.currentPane?.node.addToTransitionSurface(view: view) - }, openUrl: { [weak self] url in - self?.openUrl(url: url, concealed: false, external: false) - }, openPeer: { [weak self] peer, navigation in - self?.openPeer(peerId: peer.id, navigation: navigation) - }, callPeer: { peerId, isVideo in - }, openConferenceCall: { _ in - }, enqueueMessage: { _ in - }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed, forceExternal in - if let strongSelf = self { - strongSelf.openUrl(url: url, concealed: false, external: forceExternal) - } - }, openUrlIn: { [weak self] url in - if let strongSelf = self { - strongSelf.openUrlIn(url) - } - }, openPeerMention: { [weak self] mention in - if let strongSelf = self { - strongSelf.openPeerMention(mention) - } - }, openPeer: { [weak self] peer in - if let strongSelf = self { - strongSelf.openPeer(peerId: peer.id, navigation: .default) - } - }, openHashtag: { [weak self] peerName, hashtag in - if let strongSelf = self { - strongSelf.openHashtag(hashtag, peerName: peerName) - } - }, openBotCommand: { _ in - }, openAd: { _ in - }, addContact: { [weak self] phoneNumber in - if let strongSelf = self { - strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in - self?.controller?.present(controller, in: .window(.root), with: arguments) - }, pushController: { [weak self] controller in - if let strongSelf = self { - strongSelf.controller?.push(controller) - } - }, completed: {}) - } - }, storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in - guard let strongSelf = self else { - return - } - var storedState: MediaPlaybackStoredState? - if let timestamp = timestamp { - storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: AudioPlaybackRate(playbackRate)) - } - let _ = updateMediaPlaybackStoredStateInteractively(engine: strongSelf.context.engine, messageId: messageId, state: storedState).startStandalone() - }, editMedia: { [weak self] messageId, snapshots, transitionCompletion in - guard let strongSelf = self else { - return - } - - let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId)) - |> deliverOnMainQueue).startStandalone(next: { [weak self] message in - guard let strongSelf = self, let message = message else { - return - } - - var mediaReference: AnyMediaReference? - for media in message.media { - if let image = media as? TelegramMediaImage { - mediaReference = AnyMediaReference.standalone(media: image) - } else if let file = media as? TelegramMediaFile { - mediaReference = AnyMediaReference.standalone(media: file) - } - } - - if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { - legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: message.associatedThreadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { - transitionCompletion() - }, getCaptionPanelView: { - return nil - }, sendMessagesWithSignals: { [weak self] signals, _, _, _ in - if let strongSelf = self { - strongSelf.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: strongSelf.context, account: strongSelf.context.account, signals: signals!) - |> deliverOnMainQueue).startStrict(next: { [weak self] messages in - if let strongSelf = self { - let _ = enqueueMessages(account: strongSelf.context.account, peerId: strongSelf.peerId, messages: messages.map { $0.message }).startStandalone() - } - })) - } - }, present: { [weak self] c, a in - self?.controller?.present(c, in: .window(.root), with: a) - }) - } - }) - }, updateCanReadHistory: { _ in - }), centralItemUpdated: { [weak self] messageId in - let _ = self?.paneContainerNode.requestExpandTabs?() - self?.paneContainerNode.currentPane?.node.ensureMessageIsVisible(id: messageId) - })) - } - - private func openResolved(_ result: ResolvedUrl) { - guard let navigationController = self.controller?.navigationController as? NavigationController else { - return - } - self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(peerId: self.peerId, message: nil, updatedPresentationData: self.controller?.updatedPresentationData), navigationController: navigationController, forceExternal: false, forceUpdate: false, openPeer: { [weak self] peer, navigation in - guard let strongSelf = self else { - return - } - switch navigation { - case let .chat(inputState, subject, peekData): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: subject, updateTextInputState: inputState, activateInput: inputState != nil ? .text : nil, keepStack: .always, peekData: peekData)) - case .info: - if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { - strongSelf.controller?.push(infoController) - } - } - case let .withBotStartPayload(startPayload): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botStart: startPayload, keepStack: .always)) - case let .withAttachBot(attachBotStart): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), attachBotStart: attachBotStart)) - case let .withBotApp(botAppStart): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botAppStart: botAppStart)) - default: - break - } - }, - sendFile: nil, - sendSticker: nil, - sendEmoji: nil, - requestMessageActionUrlAuth: nil, - joinVoiceChat: { peerId, invite, call in - - }, present: { [weak self] c, a in - self?.controller?.present(c, in: .window(.root), with: a) - }, dismissInput: { [weak self] in - self?.view.endEditing(true) - }, contentContext: nil, progress: nil, completion: nil) - } - - private func openUrl(url: String, concealed: Bool, external: Bool, forceExternal: Bool = false, commit: @escaping () -> Void = {}) { - let _ = openUserGeneratedUrl(context: self.context, peerId: self.peerId, url: url, concealed: concealed, present: { [weak self] c in - self?.controller?.present(c, in: .window(.root)) - }, openResolved: { [weak self] tempResolved in - guard let strongSelf = self else { - return - } - - let result: ResolvedUrl = external ? .externalUrl(url) : tempResolved - - strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, forceExternal: forceExternal, forceUpdate: false, openPeer: { peer, navigation in - self?.openPeer(peerId: peer.id, navigation: navigation) - commit() - }, sendFile: nil, - sendSticker: nil, - sendEmoji: nil, - requestMessageActionUrlAuth: nil, - joinVoiceChat: { peerId, invite, call in - - }, - present: { c, a in - self?.controller?.present(c, in: .window(.root), with: a) - }, dismissInput: { - self?.view.endEditing(true) - }, contentContext: nil, progress: nil, completion: nil) - }) - } - - private func openUrlIn(_ url: String) { - let actionSheet = OpenInActionSheetController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, item: .url(url: url), openUrl: { [weak self] url in - if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { - strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.presentationData, navigationController: navigationController, dismissInput: { - }) - } - }) - self.controller?.present(actionSheet, in: .window(.root)) - } - - private func openPeer(peerId: PeerId, navigation: ChatControllerInteractionNavigateToPeer) { + func openPeer(peerId: PeerId, navigation: ChatControllerInteractionNavigateToPeer) { guard let navigationController = self.controller?.navigationController as? NavigationController else { return } PeerInfoScreenImpl.openPeer(context: self.context, peerId: peerId, navigation: navigation, navigationController: navigationController) } - - private func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default) { - let disposable: MetaDisposable - if let resolvePeerByNameDisposable = self.resolvePeerByNameDisposable { - disposable = resolvePeerByNameDisposable - } else { - disposable = MetaDisposable() - self.resolvePeerByNameDisposable = disposable - } - var resolveSignal = self.context.engine.peers.resolvePeerByName(name: name, referrer: nil, ageLimit: 10) - |> mapToSignal { result -> Signal in - guard case let .result(result) = result else { - return .complete() - } - return .single(result) - } - - var cancelImpl: (() -> Void)? - let presentationData = self.presentationData - let progressSignal = Signal { [weak self] subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - self?.controller?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - resolveSignal = resolveSignal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - cancelImpl = { [weak self] in - self?.resolvePeerByNameDisposable?.set(nil) - } - disposable.set((resolveSignal - |> take(1) - |> mapToSignal { peer -> Signal in - return .single(peer?._asPeer()) - } - |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self { - if let peer = peer { - var navigation = navigation - if case .default = navigation { - if let peer = peer as? TelegramUser, peer.botInfo != nil { - navigation = .chat(textInputState: nil, subject: nil, peekData: nil) - } - } - strongSelf.openResolved(.peer(peer, navigation)) - } else { - strongSelf.controller?.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - } - } - })) - } - - private func openHashtag(_ hashtag: String, peerName: String?) { - if self.resolvePeerByNameDisposable == nil { - self.resolvePeerByNameDisposable = MetaDisposable() - } - var resolveSignal: Signal - if let peerName = peerName { - resolveSignal = self.context.engine.peers.resolvePeerByName(name: peerName, referrer: nil) - |> mapToSignal { result -> Signal in - guard case let .result(result) = result else { - return .complete() - } - return .single(result) - } - |> mapToSignal { peer -> Signal in - return .single(peer?._asPeer()) - } - } else { - resolveSignal = self.context.account.postbox.loadedPeerWithId(self.peerId) - |> map(Optional.init) - } - var cancelImpl: (() -> Void)? - let presentationData = self.presentationData - let progressSignal = Signal { [weak self] subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - self?.controller?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - resolveSignal = resolveSignal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - cancelImpl = { [weak self] in - self?.resolvePeerByNameDisposable?.set(nil) - } - self.resolvePeerByNameDisposable?.set((resolveSignal - |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self, !hashtag.isEmpty { - let searchController = HashtagSearchController(context: strongSelf.context, peer: peer.flatMap(EnginePeer.init), query: hashtag) - strongSelf.controller?.push(searchController) - } - })) - } private func openBotApp(_ bot: AttachMenuBot) { guard let controller = self.controller else { @@ -6237,1271 +3002,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }) } - private func performButtonAction(key: PeerInfoHeaderButtonKey, gesture: ContextGesture?) { - guard let controller = self.controller else { - return - } - switch key { - case .message: - if let navigationController = controller.navigationController as? NavigationController, let peer = self.data?.peer { - if let channel = peer as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId { - Task { @MainActor [weak self] in - guard let self else { - return - } - - guard let peer = await self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId)).get() else { - return - } - - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default)) - } - } else { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in - if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { controller in - if controller is PeerInfoScreen { - return false - } - return true - } - navigationController.setViewControllers(viewControllers, animated: false) - } - })) - } - } - case .discussion: - if let cachedData = self.data?.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: linkedDiscussionPeerId)) - |> deliverOnMainQueue).startStandalone(next: { [weak self] linkedDiscussionPeer in - guard let self, let linkedDiscussionPeer else { - return - } - if let navigationController = controller.navigationController as? NavigationController { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(linkedDiscussionPeer))) - } - }) - } - case .call: - self.requestCall(isVideo: false) - case .videoCall: - self.requestCall(isVideo: true) - case .voiceChat: - self.requestCall(isVideo: false, gesture: gesture) - case .mute: - var displayCustomNotificationSettings = false - - let chatIsMuted = peerInfoIsChatMuted(peer: self.data?.peer, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings) - if chatIsMuted { - } else { - displayCustomNotificationSettings = true - } - if self.data?.threadData == nil, let channel = self.data?.peer as? TelegramChannel, channel.isForumOrMonoForum { - displayCustomNotificationSettings = true - } - - let peerId = self.data?.peer?.id ?? self.peerId - - if !displayCustomNotificationSettings { - let _ = self.context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: self.chatLocation.threadId, muteInterval: 0).startStandalone() - - let iconColor: UIColor = .white - self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ - "Middle.Group 1.Fill 1": iconColor, - "Top.Group 1.Fill 1": iconColor, - "Bottom.Group 1.Fill 1": iconColor, - "EXAMPLE.Group 1.Fill 1": iconColor, - "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: self.presentationData.strings.PeerInfo_TooltipUnmuted, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - } else { - self.state = self.state.withHighlightedButton(.mute) - if let (layout, navigationHeight) = self.validLayout { - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) - } - - var items: [ContextMenuItem] = [] - - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_MuteFor, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Mute2d"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] c, _ in - guard let strongSelf = self else { - return - } - var subItems: [ContextMenuItem] = [] - - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) - }, iconPosition: .left, action: { c, _ in - c?.popItems() - }))) - subItems.append(.separator) - - let presetValues: [Int32] = [ - 1 * 60 * 60, - 8 * 60 * 60, - 1 * 24 * 60 * 60, - 7 * 24 * 60 * 60 - ] - - for value in presetValues { - subItems.append(.action(ContextMenuActionItem(text: muteForIntervalString(strings: strongSelf.presentationData.strings, value: value), icon: { _ in - return nil - }, action: { _, f in - f(.default) - - let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: strongSelf.chatLocation.threadId, muteInterval: value).startStandalone() - - strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedFor(mutedForTimeIntervalString(strings: strongSelf.presentationData.strings, value: value)).string, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - }))) - } - - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_MuteForCustom, icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.openCustomMute() - }))) - - c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) - }))) - - items.append(.separator) - - var isSoundEnabled = true - let notificationSettings = self.data?.threadNotificationSettings ?? self.data?.peerNotificationSettings - if let notificationSettings { - switch notificationSettings.messageSound { - case .none: - isSoundEnabled = false - default: - break - } - } - - if !chatIsMuted { - if !isSoundEnabled { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_EnableSound, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.default) - - guard let strongSelf = self else { - return - } - let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: strongSelf.chatLocation.threadId, sound: .default).startStandalone() - - strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_sound_on", scale: 0.056, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipSoundEnabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - }))) - } else { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_DisableSound, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOff"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.default) - - guard let strongSelf = self else { - return - } - let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: strongSelf.chatLocation.threadId, sound: .none).startStandalone() - - strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_sound_off", scale: 0.056, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipSoundDisabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - }))) - } - } - - let context = self.context - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_NotificationsCustomize, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - let _ = (context.engine.data.get( - TelegramEngine.EngineData.Item.NotificationSettings.Global() - ) - |> deliverOnMainQueue).startStandalone(next: { globalSettings in - guard let strongSelf = self, let peer = strongSelf.data?.peer else { - return - } - let threadId = strongSelf.chatLocation.threadId - - let context = strongSelf.context - let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in - return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue - } - - let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in - return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue - } - - let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal = { - peerId, displayPreviews in - return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue - } - - let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal = { - peerId, mute in - return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue - } - - let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal = { - peerId, hideSender in - return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue - } - - let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in - return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue - } - - let mode: NotificationExceptionMode - let defaultSound: PeerMessageSound - if let _ = peer as? TelegramUser { - mode = .users([:]) - defaultSound = globalSettings.privateChats.sound._asMessageSound() - } else if let _ = peer as? TelegramSecretChat { - mode = .users([:]) - defaultSound = globalSettings.privateChats.sound._asMessageSound() - } else if let channel = peer as? TelegramChannel { - if case .broadcast = channel.info { - mode = .channels([:]) - defaultSound = globalSettings.channels.sound._asMessageSound() - } else { - mode = .groups([:]) - defaultSound = globalSettings.groupChats.sound._asMessageSound() - } - } else { - mode = .groups([:]) - defaultSound = globalSettings.groupChats.sound._asMessageSound() - } - let _ = mode - - let canRemove = false - - let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: EnginePeer(peer), threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: globalSettings.privateChats.storySettings.sound, edit: true, updatePeerSound: { peerId, sound in - let _ = (updatePeerSound(peer.id, sound) - |> deliverOnMainQueue).startStandalone(next: { _ in - }) - }, updatePeerNotificationInterval: { peerId, muteInterval in - let _ = (updatePeerNotificationInterval(peerId, muteInterval) - |> deliverOnMainQueue).startStandalone(next: { _ in - guard let strongSelf = self else { - return - } - if let muteInterval = muteInterval, muteInterval == Int32.max { - let iconColor: UIColor = .white - strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ - "Middle.Group 1.Fill 1": iconColor, - "Top.Group 1.Fill 1": iconColor, - "Bottom.Group 1.Fill 1": iconColor, - "EXAMPLE.Group 1.Fill 1": iconColor, - "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - } - }) - }, updatePeerDisplayPreviews: { peerId, displayPreviews in - let _ = (updatePeerDisplayPreviews(peerId, displayPreviews) - |> deliverOnMainQueue).startStandalone(next: { _ in - - }) - }, updatePeerStoriesMuted: { peerId, mute in - let _ = (updatePeerStoriesMuted(peerId, mute) - |> deliverOnMainQueue).startStandalone() - }, updatePeerStoriesHideSender: { peerId, hideSender in - let _ = (updatePeerStoriesHideSender(peerId, hideSender) - |> deliverOnMainQueue).startStandalone() - }, updatePeerStorySound: { peerId, sound in - let _ = (updatePeerStorySound(peer.id, sound) - |> deliverOnMainQueue).startStandalone() - }, removePeerFromExceptions: { - }, modifiedPeer: { - }) - exceptionController.navigationPresentation = .modal - controller.push(exceptionController) - }) - }))) - - if chatIsMuted { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_ButtonUnmute, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unmute"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.default) - - guard let self else { - return - } - - let _ = self.context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: self.chatLocation.threadId, muteInterval: 0).startStandalone() - - let iconColor: UIColor = .white - self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ - "Middle.Group 1.Fill 1": iconColor, - "Top.Group 1.Fill 1": iconColor, - "Bottom.Group 1.Fill 1": iconColor, - "EXAMPLE.Group 1.Fill 1": iconColor, - "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: self.presentationData.strings.PeerInfo_TooltipUnmuted, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - }))) - } else { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_MuteForever, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Muted"), color: theme.contextMenu.destructiveColor) - }, action: { [weak self] _, f in - f(.default) - - guard let strongSelf = self else { - return - } - - let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: strongSelf.chatLocation.threadId, muteInterval: Int32.max).startStandalone() - - let iconColor: UIColor = .white - strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ - "Middle.Group 1.Fill 1": iconColor, - "Top.Group 1.Fill 1": iconColor, - "Bottom.Group 1.Fill 1": iconColor, - "EXAMPLE.Group 1.Fill 1": iconColor, - "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - }))) - } - - var tip: ContextController.Tip? - tip = nil - if !self.forumTopicNotificationExceptions.isEmpty { - items.append(.separator) - - let text: String = self.presentationData.strings.PeerInfo_TopicNotificationExceptions(Int32(self.forumTopicNotificationExceptions.count)) - - items.append(.action(ContextMenuActionItem( - text: text, - textLayout: .multiline, - textFont: .small, - parseMarkdown: true, - badge: nil, - icon: { _ in - return nil - }, - action: { [weak self] _, f in - guard let self else { - return - } - f(.default) - self.controller?.push(threadNotificationExceptionsScreen(context: self.context, peerId: self.peerId, notificationExceptions: self.forumTopicNotificationExceptions, updated: { [weak self] value in - guard let self else { - return - } - self.forumTopicNotificationExceptions = value - })) - } - ))) - } - - self.view.endEditing(true) - - if let sourceNode = self.headerNode.buttonNodes[.mute]?.referenceNode { - let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items), tip: tip)), gesture: gesture) - contextController.dismissed = { [weak self] in - if let strongSelf = self { - strongSelf.state = strongSelf.state.withHighlightedButton(nil) - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) - } - } - } - controller.presentInGlobalOverlay(contextController) - } - } - case .more: - guard let data = self.data, let peer = data.peer, let chatPeer = data.chatPeer else { - return - } - let presentationData = self.presentationData - self.state = self.state.withHighlightedButton(.more) - if let (layout, navigationHeight) = self.validLayout { - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) - } - - var mainItemsImpl: (() -> Signal<[ContextMenuItem], NoError>)? - mainItemsImpl = { [weak self] in - var items: [ContextMenuItem] = [] - guard let strongSelf = self else { - return .single(items) - } - - let allHeaderButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: false, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false, threadInfo: data.threadData?.info)) - let headerButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: true, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false, threadInfo: strongSelf.data?.threadData?.info)) - - let filteredButtons = allHeaderButtons.subtracting(headerButtons) - - var currentAutoremoveTimeout: Int32? - if let cachedData = data.cachedData as? CachedUserData { - switch cachedData.autoremoveTimeout { - case let .known(value): - currentAutoremoveTimeout = value?.peerValue - case .unknown: - break - } - } else if let cachedData = data.cachedData as? CachedGroupData { - switch cachedData.autoremoveTimeout { - case let .known(value): - currentAutoremoveTimeout = value?.peerValue - case .unknown: - break - } - } else if let cachedData = data.cachedData as? CachedChannelData { - switch cachedData.autoremoveTimeout { - case let .known(value): - currentAutoremoveTimeout = value?.peerValue - case .unknown: - break - } - } - - var canSetupAutoremoveTimeout = false - - if let secretChat = chatPeer as? TelegramSecretChat { - currentAutoremoveTimeout = secretChat.messageAutoremoveTimeout - canSetupAutoremoveTimeout = false - } else if let group = chatPeer as? TelegramGroup { - if !group.hasBannedPermission(.banChangeInfo) { - canSetupAutoremoveTimeout = true - } - } else if let user = chatPeer as? TelegramUser { - if user.id != strongSelf.context.account.peerId { - canSetupAutoremoveTimeout = true - } - } else if let channel = chatPeer as? TelegramChannel { - if channel.hasPermission(.changeInfo) { - canSetupAutoremoveTimeout = true - } - } - - if filteredButtons.contains(.call) { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ButtonCall, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - self?.requestCall(isVideo: false) - }))) - } - if filteredButtons.contains(.search) { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatSearch_SearchPlaceholder, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - self?.openChatWithMessageSearch() - }))) - } - - var hasDiscussion = false - if let channel = chatPeer as? TelegramChannel { - switch channel.info { - case let .broadcast(info): - hasDiscussion = info.flags.contains(.hasDiscussionGroup) - case .group: - hasDiscussion = false - } - } - if !headerButtons.contains(.discussion) && hasDiscussion { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ViewDiscussion, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - self?.performButtonAction(key: .discussion, gesture: nil) - }))) - } - - if let user = peer as? TelegramUser { - if user.botInfo == nil && strongSelf.data?.encryptionKeyFingerprint == nil && !user.isDeleted { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ChangeWallpaper, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) - }, action: { _, f in - f(.dismissWithoutContent) - - self?.openChatForThemeChange() - }))) - } - - if let _ = user.botInfo { - if user.addressName != nil { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ShareBot, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - self?.openShareBot() - }))) - } - - var addedPrivacy = false - var privacyPolicyUrl: String? - if let cachedData = (data.cachedData as? CachedUserData), let botInfo = cachedData.botInfo { - if let url = botInfo.privacyPolicyUrl { - privacyPolicyUrl = url - } else if botInfo.commands.contains(where: { $0.text == "privacy" }) { - - } else { - privacyPolicyUrl = presentationData.strings.WebApp_PrivacyPolicy_URL - } - } - if let privacyPolicyUrl { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotPrivacy, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - guard let self else { - return - } - self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: privacyPolicyUrl, forceExternal: false, presentationData: self.presentationData, navigationController: self.controller?.navigationController as? NavigationController, dismissInput: {}) - }))) - addedPrivacy = true - } - if let cachedData = data.cachedData as? CachedUserData, let botInfo = cachedData.botInfo { - for command in botInfo.commands { - if command.text == "settings" { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotSettings, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Bots"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - self?.performBotCommand(command: .settings) - }))) - } else if command.text == "help" { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotHelp, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Help"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - self?.performBotCommand(command: .help) - }))) - } else if command.text == "privacy" && !addedPrivacy { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotPrivacy, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - self?.performBotCommand(command: .privacy) - }))) - } - } - } - } - - if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) { - if let cachedUserData = strongSelf.data?.cachedData as? CachedUserData, let _ = cachedUserData.sendPaidMessageStars { - - } else { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_StartSecretChat, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Lock"), color: theme.contextMenu.primaryColor) - }, action: { _, f in - f(.dismissWithoutContent) - - self?.openStartSecretChat() - }))) - } - } - - if user.botInfo == nil && data.isContact, let peer = strongSelf.data?.peer as? TelegramUser, let phone = peer.phone { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_ShareContactButton, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - if let strongSelf = self { - let contact = TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: phone, peerId: peer.id, vCardData: nil) - let shareController = ShareController(context: strongSelf.context, subject: .media(.standalone(media: contact), nil), updatedPresentationData: strongSelf.controller?.updatedPresentationData) - shareController.completed = { [weak self] peerIds in - if let strongSelf = self { - let _ = (strongSelf.context.engine.data.get( - EngineDataList( - peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) - ) - ) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in - guard let strongSelf = self else { - return - } - - let peers = peerList.compactMap { $0 } - - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - - let text: String - var savedMessages = false - if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId { - text = presentationData.strings.UserInfo_ContactForwardTooltip_SavedMessages_One - savedMessages = true - } else { - if peers.count == 1, let peer = peers.first { - let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - text = presentationData.strings.UserInfo_ContactForwardTooltip_Chat_One(peerName).string - } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { - let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - text = presentationData.strings.UserInfo_ContactForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string - } else if let peer = peers.first { - let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - text = presentationData.strings.UserInfo_ContactForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string - } else { - text = "" - } - } - - strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in - if savedMessages, let self, action == .info { - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let peer else { - return - } - guard let navigationController = self.controller?.navigationController as? NavigationController else { - return - } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) - }) - } - return false - }), in: .current) - }) - } - } - strongSelf.controller?.present(shareController, in: .window(.root)) - } - }))) - } - - if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser, !user.isDeleted && user.botInfo == nil && !user.flags.contains(.isSupport) { - if let cachedData = data.cachedData as? CachedUserData, cachedData.disallowedGifts == .All { - } else { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_SendGift, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - if let self { - self.openPremiumGift() - } - }))) - } - } - - if let cachedData = data.cachedData as? CachedUserData, canTranslateChats(context: strongSelf.context), cachedData.flags.contains(.translationHidden) { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - if let strongSelf = self { - let _ = updateChatTranslationStateInteractively(engine: strongSelf.context.engine, peerId: strongSelf.peerId, threadId: nil, { current in - return current?.withIsEnabled(true) - }).startStandalone() - - Queue.mainQueue().after(0.2, { - let _ = (strongSelf.context.engine.messages.togglePeerMessagesTranslationHidden(peerId: strongSelf.peerId, hidden: false) - |> deliverOnMainQueue).startStandalone(completed: { [weak self] in - self?.openChatForTranslation() - }) - }) - } - }))) - } - - let itemsCount = items.count - - if canSetupAutoremoveTimeout { - let strings = strongSelf.presentationData.strings - items.append(.action(ContextMenuActionItem(text: currentAutoremoveTimeout == nil ? strongSelf.presentationData.strings.PeerInfo_EnableAutoDelete : strongSelf.presentationData.strings.PeerInfo_AdjustAutoDelete, icon: { theme in - if let currentAutoremoveTimeout = currentAutoremoveTimeout { - let text = NSAttributedString(string: shortTimeIntervalString(strings: strings, value: currentAutoremoveTimeout), font: Font.regular(14.0), textColor: theme.contextMenu.primaryColor) - let bounds = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - return generateImage(bounds.size.integralFloor, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - text.draw(in: bounds) - UIGraphicsPopContext() - }) - } else { - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor) - } - }, action: { [weak self] c, _ in - var subItems: [ContextMenuItem] = [] - - subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) - }, iconPosition: .left, action: { c, _ in - c?.popItems() - }))) - subItems.append(.separator) - - let presetValues: [Int32] = [ - 1 * 24 * 60 * 60, - 7 * 24 * 60 * 60, - 31 * 24 * 60 * 60 - ] - - for value in presetValues { - subItems.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: strings, value: value), icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.setAutoremove(timeInterval: value) - }))) - } - - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteSettingOther, icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.openAutoremove(currentValue: currentAutoremoveTimeout) - }))) - - if let _ = currentAutoremoveTimeout { - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteDisable, textColor: .destructive, icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.setAutoremove(timeInterval: nil) - }))) - } - - subItems.append(.separator) - - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in - return nil - }, textLinkAction: { [weak c] in - c?.dismiss(completion: nil) - - guard let self else { - return - } - self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, forceUpdate: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in - guard let self else { - return - } - self.controller?.view.endEditing(true) - }, contentContext: nil, progress: nil, completion: nil) - }, action: nil as ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) - - c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) - }))) - } - - let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: user, chatPeer: chatPeer, cachedData: strongSelf.data?.cachedData) - if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) - }, action: { c, _ in - if let c { - self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: user, chatPeer: user) - } - }))) - } - - if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) { - if data.isContact { - if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked { - } else { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_BlockUser, textColor: .destructive, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.destructiveColor) - }, action: { _, f in - f(.dismissWithoutContent) - - self?.updateBlocked(block: true) - }))) - } - } - } else if strongSelf.peerId.namespace == Namespaces.Peer.SecretChat && data.isContact { - if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked { - } else { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_BlockUser, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.updateBlocked(block: true) - }))) - } - } - - let finalItemsCount = items.count - - if finalItemsCount > itemsCount { - items.insert(.separator, at: itemsCount) - } - } else if let channel = peer as? TelegramChannel { - if let cachedData = strongSelf.data?.cachedData as? CachedChannelData { - if case .broadcast = channel.info, cachedData.flags.contains(.starGiftsAvailable) { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_SendGift, badge: nil, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.openPremiumGift() - }))) - } - - let boostTitle: String - switch channel.info { - case .group: - boostTitle = presentationData.strings.PeerInfo_Group_Boost - case .broadcast: - boostTitle = presentationData.strings.PeerInfo_Channel_Boost - } - items.append(.action(ContextMenuActionItem(text: boostTitle, badge: nil, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Boost"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.openBoost() - }))) - - if channel.hasPermission(.editStories) { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_Channel_ArchivedStories, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.openStoryArchive() - }))) - } - if cachedData.flags.contains(.canViewStats) { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_Stats, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.openStats(section: .stats) - }))) - } - if cachedData.flags.contains(.translationHidden) { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - if let strongSelf = self { - let _ = updateChatTranslationStateInteractively(engine: strongSelf.context.engine, peerId: strongSelf.peerId, threadId: nil, { current in - return current?.withIsEnabled(true) - }).startStandalone() - - Queue.mainQueue().after(0.2, { - let _ = (strongSelf.context.engine.messages.togglePeerMessagesTranslationHidden(peerId: strongSelf.peerId, hidden: false) - |> deliverOnMainQueue).startStandalone(completed: { [weak self] in - self?.openChatForTranslation() - }) - }) - } - }))) - } - } - - var canReport = true - if channel.adminRights != nil { - canReport = false - } - if channel.flags.contains(.isCreator) { - canReport = false - } - if canReport { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.ReportPeer_Report, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] c, f in - self?.openReport(type: .default, contextController: c, backAction: { c in - if let mainItemsImpl = mainItemsImpl { - c.setItems(mainItemsImpl() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) - } - }) - }))) - } - - if canSetupAutoremoveTimeout { - let strings = strongSelf.presentationData.strings - items.append(.action(ContextMenuActionItem(text: currentAutoremoveTimeout == nil ? strongSelf.presentationData.strings.PeerInfo_EnableAutoDelete : strongSelf.presentationData.strings.PeerInfo_AdjustAutoDelete, icon: { theme in - if let currentAutoremoveTimeout = currentAutoremoveTimeout { - let text = NSAttributedString(string: shortTimeIntervalString(strings: strings, value: currentAutoremoveTimeout), font: Font.regular(14.0), textColor: theme.contextMenu.primaryColor) - let bounds = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - return generateImage(bounds.size.integralFloor, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - text.draw(in: bounds) - UIGraphicsPopContext() - }) - } else { - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor) - } - }, action: { [weak self] c, _ in - var subItems: [ContextMenuItem] = [] - - subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) - }, iconPosition: .left, action: { c, _ in - c?.popItems() - }))) - subItems.append(.separator) - - let presetValues: [Int32] = [ - 1 * 24 * 60 * 60, - 7 * 24 * 60 * 60, - 31 * 24 * 60 * 60 - ] - - for value in presetValues { - subItems.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: strings, value: value), icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.setAutoremove(timeInterval: value) - }))) - } - - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteSettingOther, icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.openAutoremove(currentValue: currentAutoremoveTimeout) - }))) - - if let _ = currentAutoremoveTimeout { - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteDisable, textColor: .destructive, icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.setAutoremove(timeInterval: nil) - }))) - } - - subItems.append(.separator) - - let baseText: String - if case .broadcast = channel.info { - baseText = strongSelf.presentationData.strings.PeerInfo_ChannelAutoDeleteInfo - } else { - baseText = strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo - } - - subItems.append(.action(ContextMenuActionItem(text: baseText + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in - return nil - }, textLinkAction: { [weak c] in - c?.dismiss(completion: nil) - - guard let self else { - return - } - self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, forceUpdate: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in - guard let self else { - return - } - self.controller?.view.endEditing(true) - }, contentContext: nil, progress: nil, completion: nil) - }, action: nil as ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) - - c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) - }))) - } - - let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: channel, chatPeer: channel, cachedData: strongSelf.data?.cachedData) - if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) - }, action: { c, _ in - if let c { - self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: channel, chatPeer: channel) - } - }))) - } - - switch channel.info { - case .broadcast: - if case .member = channel.participationStatus, !headerButtons.contains(.leave) { - if !items.isEmpty { - items.append(.separator) - } - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Channel_LeaveChannel, textColor: .destructive, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Logout"), color: theme.contextMenu.destructiveColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.openLeavePeer(delete: false) - }))) - } - case .group: - if case .member = channel.participationStatus, !headerButtons.contains(.leave) { - if !items.isEmpty { - items.append(.separator) - } - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_LeaveGroup, textColor: .primary, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Logout"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.openLeavePeer(delete: false) - }))) - if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canDeleteHistory) { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_DeleteGroup, textColor: .destructive, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.openLeavePeer(delete: true) - }))) - } - } - } - } else if let group = peer as? TelegramGroup { - if canSetupAutoremoveTimeout { - let strings = strongSelf.presentationData.strings - items.append(.action(ContextMenuActionItem(text: currentAutoremoveTimeout == nil ? strongSelf.presentationData.strings.PeerInfo_EnableAutoDelete : strongSelf.presentationData.strings.PeerInfo_AdjustAutoDelete, icon: { theme in - if let currentAutoremoveTimeout = currentAutoremoveTimeout { - let text = NSAttributedString(string: shortTimeIntervalString(strings: strings, value: currentAutoremoveTimeout), font: Font.regular(14.0), textColor: theme.contextMenu.primaryColor) - let bounds = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - return generateImage(bounds.size.integralFloor, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - text.draw(in: bounds) - UIGraphicsPopContext() - }) - } else { - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor) - } - }, action: { [weak self] c, _ in - var subItems: [ContextMenuItem] = [] - - subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) - }, iconPosition: .left, action: { c, _ in - c?.popItems() - }))) - subItems.append(.separator) - - let presetValues: [Int32] = [ - 1 * 24 * 60 * 60, - 7 * 24 * 60 * 60, - 31 * 24 * 60 * 60 - ] - - for value in presetValues { - subItems.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: strings, value: value), icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.setAutoremove(timeInterval: value) - }))) - } - - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteSettingOther, icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.openAutoremove(currentValue: currentAutoremoveTimeout) - }))) - - if let _ = currentAutoremoveTimeout { - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteDisable, textColor: .destructive, icon: { _ in - return nil - }, action: { _, f in - f(.default) - - self?.setAutoremove(timeInterval: nil) - }))) - } - - subItems.append(.separator) - - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in - return nil - }, textLinkAction: { [weak c] in - c?.dismiss(completion: nil) - - guard let self else { - return - } - self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, forceUpdate: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in - guard let self else { - return - } - self.controller?.view.endEditing(true) - }, contentContext: nil, progress: nil, completion: nil) - }, action: nil as ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) - - c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) - }))) - } - - if let cachedData = data.cachedData as? CachedGroupData, cachedData.flags.contains(.translationHidden) { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - if let strongSelf = self { - let _ = updateChatTranslationStateInteractively(engine: strongSelf.context.engine, peerId: strongSelf.peerId, threadId: nil, { current in - return current?.withIsEnabled(true) - }).startStandalone() - - Queue.mainQueue().after(0.2, { - let _ = (strongSelf.context.engine.messages.togglePeerMessagesTranslationHidden(peerId: strongSelf.peerId, hidden: false) - |> deliverOnMainQueue).startStandalone(completed: { [weak self] in - self?.openChatForTranslation() - }) - }) - } - }))) - } - - var canReport = true - if case .creator = group.role { - canReport = false - } - if canReport { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.ReportPeer_Report, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] c, f in - self?.openReport(type: .default, contextController: c, backAction: { c in - if let mainItemsImpl = mainItemsImpl { - c.setItems(mainItemsImpl() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) - } - }) - }))) - } - - let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: group, chatPeer: group, cachedData: strongSelf.data?.cachedData) - if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) - }, action: { c, _ in - if let c { - self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: group, chatPeer: group) - } - }))) - } - - if case .Member = group.membership, !headerButtons.contains(.leave) { - if !items.isEmpty { - items.append(.separator) - } - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_LeaveGroup, textColor: .destructive, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Logout"), color: theme.contextMenu.destructiveColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.openLeavePeer(delete: false) - }))) - - if case .creator = group.role { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_DeleteGroup, textColor: .destructive, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.openLeavePeer(delete: true) - }))) - } - } - } - - return .single(items) - } - - self.view.endEditing(true) - - if let sourceNode = self.headerNode.buttonNodes[.more]?.referenceNode { - let items = mainItemsImpl?() ?? .single([]) - let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) - contextController.dismissed = { [weak self] in - if let strongSelf = self { - strongSelf.state = strongSelf.state.withHighlightedButton(nil) - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) - } - } - } - controller.presentInGlobalOverlay(contextController) - } - case .addMember: - self.openAddMember() - case .search: - self.openChatWithMessageSearch() - case .leave: - self.openLeavePeer(delete: false) - case .stop: - self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: self.presentationData.strings.PeerInfo_BotBlockedTitle, text: self.presentationData.strings.PeerInfo_BotBlockedText, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - self.updateBlocked(block: true) - case .addContact: - self.openAddContact() - } - } - - private func openChatWithMessageSearch() { - if let navigationController = (self.controller?.navigationController as? NavigationController) { - if case let .replyThread(currentMessage) = self.chatLocation, let current = navigationController.viewControllers.first(where: { controller in - if let controller = controller as? ChatController, case let .replyThread(message) = controller.chatLocation, message.peerId == currentMessage.peerId, message.threadId == currentMessage.threadId { - return true - } - return false - }) as? ChatController { - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(of: current) { - viewControllers.removeSubrange(index + 1 ..< viewControllers.count) - } - navigationController.setViewControllers(viewControllers, animated: true) - current.activateSearch(domain: .everything, query: "") - } else if let peer = self.data?.chatPeer { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, activateMessageSearch: (.everything, ""), peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in - if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { controller in - if controller is PeerInfoScreen { - return false - } - return true - } - navigationController.setViewControllers(viewControllers, animated: false) - } - })) - } - } - } - - private func openChatForReporting(title: String, option: Data, message: String?) { - if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) { - if let channel = peer as? TelegramChannel, channel.isForumOrMonoForum { - //let _ = self.context.engine.peers.reportPeer(peerId: peer.id, reason: reason, message: "").startStandalone() - //self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .emoji(name: "PoliceCar", text: self.presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current) - } else { - self.context.sharedContext.navigateToChatController( - NavigateToChatControllerParams( - navigationController: navigationController, - context: self.context, - chatLocation: .peer(EnginePeer(peer)), - keepStack: .default, - reportReason: NavigateToChatControllerParams.ReportReason(title: title, option: option, message: message), - completion: { _ in - } - ) - ) - } - } - } - - private func openChatForThemeChange() { - if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, changeColors: true, completion: { _ in - })) - } - } - - private func openChatForTranslation() { - if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, changeColors: false, completion: { _ in - })) - } - } - - private func openAutoremove(currentValue: Int32?) { + func openAutoremove(currentValue: Int32?) { let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, style: .default, mode: .autoremove, currentTime: currentValue, dismissByTapOutside: true, completion: { [weak self] value in guard let strongSelf = self else { return @@ -7530,7 +3031,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.controller?.present(controller, in: .window(.root)) } - private func openCustomMute() { + func openCustomMute() { let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak self] value in guard let strongSelf = self, let peer = strongSelf.data?.peer else { return @@ -7549,7 +3050,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.controller?.present(controller, in: .window(.root)) } - private func setAutoremove(timeInterval: Int32?) { + func setAutoremove(timeInterval: Int32?) { let _ = (self.context.engine.peers.setChatMessageAutoremoveTimeoutInteractively(peerId: self.peerId, timeout: timeInterval) |> deliverOnMainQueue).startStandalone(completed: { [weak self] in guard let strongSelf = self else { @@ -7569,7 +3070,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }) } - private func openStartSecretChat() { + func openStartSecretChat() { guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { return } @@ -7656,7 +3157,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }) } - private func openClearHistory(contextController: ContextControllerProtocol, clearPeerHistory: ClearPeerHistory, peer: Peer, chatPeer: Peer) { + func openClearHistory(contextController: ContextControllerProtocol, clearPeerHistory: ClearPeerHistory, peer: Peer, chatPeer: Peer) { var subItems: [ContextMenuItem] = [] subItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Common_Back, icon: { theme in @@ -7729,853 +3230,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro contextController.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) } - private func openUsername(value: String, isMainUsername: Bool, progress: Promise?) { - let url: String - if value.hasPrefix("https://") { - url = value - } else { - url = "https://t.me/\(value)" - } - - let openShare: (TelegramCollectibleItemInfo?) -> Void = { [weak self] collectibleItemInfo in - guard let self else { - return - } - let shareController = ShareController(context: self.context, subject: .url(url), updatedPresentationData: self.controller?.updatedPresentationData, collectibleItemInfo: collectibleItemInfo) - shareController.completed = { [weak self] peerIds in - guard let strongSelf = self else { - return - } - let _ = (strongSelf.context.engine.data.get( - EngineDataList( - peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) - ) - ) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in - guard let strongSelf = self else { - return - } - - let peers = peerList.compactMap { $0 } - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - - let text: String - var savedMessages = false - if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId { - text = presentationData.strings.UserInfo_LinkForwardTooltip_SavedMessages_One - savedMessages = true - } else { - if peers.count == 1, let peer = peers.first { - let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - text = presentationData.strings.UserInfo_LinkForwardTooltip_Chat_One(peerName).string - } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { - let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - text = presentationData.strings.UserInfo_LinkForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string - } else if let peer = peers.first { - let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - text = presentationData.strings.UserInfo_LinkForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string - } else { - text = "" - } - } - - strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in - if savedMessages, let self, action == .info { - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let peer else { - return - } - guard let navigationController = self.controller?.navigationController as? NavigationController else { - return - } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) - }) - } - return false - }), in: .current) - }) - } - shareController.actionCompleted = { [weak self] in - if let strongSelf = self { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - } - self.view.endEditing(true) - self.controller?.present(shareController, in: .window(.root)) - } - - if let pathComponents = URL(string: url)?.pathComponents, pathComponents.count >= 2, !pathComponents[1].isEmpty { - let namePart = pathComponents[1] - progress?.set(.single(true)) - let _ = (self.context.sharedContext.makeCollectibleItemInfoScreenInitialData(context: self.context, peerId: self.peerId, subject: .username(namePart)) - |> deliverOnMainQueue).start(next: { [weak self] initialData in - guard let self else { - return - } - - progress?.set(.single(false)) - - if let initialData { - if isMainUsername { - openShare(initialData.collectibleItemInfo) - } else { - self.view.endEditing(true) - self.controller?.push(self.context.sharedContext.makeCollectibleItemInfoScreen(context: self.context, initialData: initialData)) - } - } else { - openShare(nil) - } - }) - } else { - openShare(nil) - } - } - - private func requestCall(isVideo: Bool, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) { - guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { - return - } - let peerId = self.peerId - let requestCall: (PeerId?, EngineGroupCallDescription?) -> Void = { [weak self] defaultJoinAsPeerId, activeCall in - if let activeCall = activeCall { - self?.context.joinGroupCall(peerId: peerId, invite: nil, requestJoinAsPeerId: { completion in - if let defaultJoinAsPeerId = defaultJoinAsPeerId { - result?(.dismissWithoutContent) - completion(defaultJoinAsPeerId) - } else { - self?.openVoiceChatDisplayAsPeerSelection(completion: { joinAsPeerId in - completion(joinAsPeerId) - }, gesture: gesture, contextController: contextController, result: result, backAction: backAction) - } - }, activeCall: activeCall) - } else { - self?.openVoiceChatOptions(defaultJoinAsPeerId: defaultJoinAsPeerId, gesture: gesture, contextController: contextController) - } - } - - if let cachedChannelData = self.data?.cachedData as? CachedChannelData { - requestCall(cachedChannelData.callJoinPeerId, cachedChannelData.activeCall.flatMap(EngineGroupCallDescription.init)) - return - } else if let cachedGroupData = self.data?.cachedData as? CachedGroupData { - requestCall(cachedGroupData.callJoinPeerId, cachedGroupData.activeCall.flatMap(EngineGroupCallDescription.init)) - return - } - - guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else { - return - } - if cachedUserData.callsPrivate { - self.controller?.push(self.context.sharedContext.makeSendInviteLinkScreen(context: self.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer( - peer: EnginePeer(peer), - canInviteWithPremium: false, - premiumRequiredToContact: false - )], theme: self.presentationData.theme)) - return - } - - self.context.requestCall(peerId: peer.id, isVideo: isVideo, completion: {}) - } - - private func scheduleGroupCall() { - guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { - return - } - self.context.scheduleGroupCall(peerId: self.peerId, parentController: controller) - } - - private func createExternalStream(credentialsPromise: Promise?) { - guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { - return - } - self.controller?.push(CreateExternalMediaStreamScreen(context: self.context, peerId: self.peerId, credentialsPromise: credentialsPromise, mode: .create(liveStream: false))) - } - - private func createAndJoinGroupCall(peerId: PeerId, joinAsPeerId: PeerId?) { - guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { - return - } - if let _ = self.context.sharedContext.callManager { - let startCall: (Bool) -> Void = { [weak self] endCurrentIfAny in - guard let strongSelf = self else { - return - } - - var cancelImpl: (() -> Void)? - let presentationData = strongSelf.presentationData - let progressSignal = Signal { [weak self] subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - self?.controller?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - let createSignal = strongSelf.context.engine.calls.createGroupCall(peerId: peerId, title: nil, scheduleDate: nil, isExternalStream: false) - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - cancelImpl = { [weak self] in - self?.activeActionDisposable.set(nil) - } - strongSelf.activeActionDisposable.set((createSignal - |> deliverOnMainQueue).start(next: { [weak self] info in - guard let strongSelf = self else { - return - } - strongSelf.context.joinGroupCall(peerId: peerId, invite: nil, requestJoinAsPeerId: { result in - result(joinAsPeerId) - }, activeCall: EngineGroupCallDescription(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: info.isStream)) - }, error: { [weak self] error in - guard let strongSelf = self else { - return - } - strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil) - - let text: String - switch error { - case .generic, .scheduledTooLate: - text = strongSelf.presentationData.strings.Login_UnknownError - case .anonymousNotAllowed: - if let channel = strongSelf.data?.peer as? TelegramChannel, case .broadcast = channel.info { - text = strongSelf.presentationData.strings.LiveStream_AnonymousDisabledAlertText - } else { - text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText - } - } - strongSelf.controller?.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - })) - } - - startCall(true) - } - } - - private func openUsernameContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { - guard let sourceNode = node as? ContextExtractedContentContainingNode else { - return - } - guard let peer = self.data?.peer else { - return - } - guard let username = peer.addressName else { - return - } - - let copyAction = { [weak self] in - guard let self else { - return - } - UIPasteboard.general.string = "@\(username)" - - self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: self.presentationData.strings.Conversation_UsernameCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - - var items: [ContextMenuItem] = [] - - if self.isMyProfile { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_UsernameActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - self.openSettings(section: .username) - } - }))) - } - - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_UsernameActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - copyAction() - } - }))) - - let actions = ContextController.Items(content: .list(items)) - - let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) - self.controller?.present(contextController, in: .window(.root)) - } - - private func openBioContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { - let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak self] sharedData in - guard let self else { - return - } - - let translationSettings: TranslationSettings - if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { - translationSettings = current - } else { - translationSettings = TranslationSettings.defaultSettings - } - - guard let sourceNode = node as? ContextExtractedContentContainingNode else { - return - } - guard let cachedData = self.data?.cachedData else { - return - } - - var bioText: String? - if let cachedData = cachedData as? CachedUserData { - bioText = cachedData.about - } else if let cachedData = cachedData as? CachedChannelData { - bioText = cachedData.about - } else if let cachedData = cachedData as? CachedGroupData { - bioText = cachedData.about - } - - guard let bioText, !bioText.isEmpty else { - return - } - - let copyAction = { [weak self] in - guard let self else { - return - } - UIPasteboard.general.string = bioText - - let toastText: String - if let _ = self.data?.peer as? TelegramUser { - toastText = self.presentationData.strings.MyProfile_ToastBioCopied - } else { - toastText = self.presentationData.strings.ChannelProfile_ToastAboutCopied - } - - self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: toastText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - - var items: [ContextMenuItem] = [] - - if self.isMyProfile { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_BioActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) - - for (_, section) in self.editingSections { - for (id, itemNode) in section.itemNodes { - if id == AnyHashable("bio_edit") { - if let itemNode = itemNode as? PeerInfoScreenMultilineInputItemNode { - itemNode.focus() - } - break - } - } - } - } - }))) - } - - let copyText: String - if let _ = self.data?.peer as? TelegramUser { - copyText = self.presentationData.strings.MyProfile_BioActionCopy - } else { - copyText = self.presentationData.strings.ChannelProfile_AboutActionCopy - } - items.append(.action(ContextMenuActionItem(text: copyText, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - copyAction() - } - }))) - - let (canTranslate, language) = canTranslateText(context: self.context, text: bioText, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages) - if canTranslate { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - - let controller = TranslateScreen(context: self.context, text: bioText, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) - controller.pushController = { [weak self] c in - (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true - self?.controller?.push(c) - } - controller.presentController = { [weak self] c in - self?.controller?.present(c, in: .window(.root)) - } - self.controller?.present(controller, in: .window(.root)) - } - }))) - } - - let actions = ContextController.Items(content: .list(items)) - - let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) - self.controller?.present(contextController, in: .window(.root)) - }) - } - - private func openWorkingHoursContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { - guard let sourceNode = node as? ContextExtractedContentContainingNode else { - return - } - guard let cachedData = self.data?.cachedData else { - return - } - - var businessHours: TelegramBusinessHours? - if let cachedData = cachedData as? CachedUserData { - businessHours = cachedData.businessHours - } - - guard let businessHours else { - return - } - - let copyAction = { [weak self] in - guard let self else { - return - } - UIPasteboard.general.string = businessHoursTextToCopy(businessHours: businessHours, presentationData: self.presentationData, displayLocalTimezone: false) - - self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: self.presentationData.strings.MyProfile_ToastHoursCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - - var items: [ContextMenuItem] = [] - - if self.isMyProfile { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - let businessHoursSetupScreen = self.context.sharedContext.makeBusinessHoursSetupScreen(context: self.context, initialValue: businessHours, completion: { _ in }) - self.controller?.push(businessHoursSetupScreen) - } - }))) - } - - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - copyAction() - } - }))) - - if self.isMyProfile { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursActionRemove, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ - in - guard let self else { - return - } - - var subItems: [ContextMenuItem] = [] - let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil - subItems.append(.action(ContextMenuActionItem( - text: self.presentationData.strings.MyProfile_HoursRemoveConfirmation_Title, - textLayout: .multiline, - textFont: .small, - icon: { _ in nil }, - action: noAction - ))) - subItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursRemoveConfirmation_Action, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - let _ = self.context.engine.accountData.updateAccountBusinessHours(businessHours: nil).startStandalone() - } - }))) - c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) - }))) - } - - let actions = ContextController.Items(content: .list(items)) - - let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) - self.controller?.present(contextController, in: .window(.root)) - } - - private func openBusinessLocationContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { - guard let sourceNode = node as? ContextExtractedContentContainingNode else { - return - } - guard let cachedData = self.data?.cachedData else { - return - } - - var businessLocation: TelegramBusinessLocation? - if let cachedData = cachedData as? CachedUserData { - businessLocation = cachedData.businessLocation - } - - guard let businessLocation else { - return - } - - let copyAction = { [weak self] in - guard let self else { - return - } - UIPasteboard.general.string = businessLocation.address - - self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: self.presentationData.strings.MyProfile_ToastLocationCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - - var items: [ContextMenuItem] = [] - - if businessLocation.coordinates != nil { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionOpen, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LocationSmall"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss(completion: { - guard let self else { - return - } - self.interaction.openLocation() - }) - }))) - } - - if !businessLocation.address.isEmpty { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - copyAction() - } - }))) - } - - if self.isMyProfile { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - let businessLocationSetupScreen = self.context.sharedContext.makeBusinessLocationSetupScreen(context: self.context, initialValue: businessLocation, completion: { _ in }) - self.controller?.push(businessLocationSetupScreen) - } - }))) - - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionRemove, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in - guard let self else { - return - } - - var subItems: [ContextMenuItem] = [] - let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil - subItems.append(.action(ContextMenuActionItem( - text: self.presentationData.strings.MyProfile_LocationRemoveConfirmation_Title, - textLayout: .multiline, - textFont: .small, - icon: { _ in nil }, - action: noAction - ))) - subItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationRemoveConfirmation_Action, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - let _ = self.context.engine.accountData.updateAccountBusinessLocation(businessLocation: nil).startStandalone() - } - }))) - c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) - }))) - } - - let actions = ContextController.Items(content: .list(items)) - - let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) - self.controller?.present(contextController, in: .window(.root)) - } - - private func openNoteContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { - guard let sourceNode = node as? ContextExtractedContentContainingNode else { - return - } - guard let cachedData = self.data?.cachedData else { - return - } - - var noteText: String? - var noteEntities: [MessageTextEntity]? - if let cachedData = cachedData as? CachedUserData { - noteText = cachedData.note?.text - noteEntities = cachedData.note?.entities - } - - guard let noteText, !noteText.isEmpty else { - return - } - - let copyAction = { [weak self] in - guard let self else { - return - } - storeMessageTextInPasteboard(noteText, entities: noteEntities ?? []) - - let toastText = self.presentationData.strings.PeerInfo_ToastNoteCopied - self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: toastText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - - var items: [ContextMenuItem] = [] - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_NoteActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) - - for (_, section) in self.editingSections { - for (id, itemNode) in section.itemNodes { - if id == AnyHashable("note_edit") { - if let itemNode = itemNode as? PeerInfoScreenNoteListItemNode { - itemNode.focus() - } - break - } - } - } - } - }))) - - let copyText = self.presentationData.strings.PeerInfo_NoteActionCopy - items.append(.action(ContextMenuActionItem(text: copyText, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - copyAction() - } - }))) - - let actions = ContextController.Items(content: .list(items)) - - let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) - self.controller?.present(contextController, in: .window(.root)) - } - - private func openBirthdayContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { - guard let sourceNode = node as? ContextExtractedContentContainingNode else { - return - } - guard let cachedData = self.data?.cachedData else { - return - } - - var birthday: TelegramBirthday? - if let cachedData = cachedData as? CachedUserData { - birthday = cachedData.birthday - } - - guard let birthday else { - return - } - - let copyAction = { [weak self] in - guard let self else { - return - } - let presentationData = self.presentationData - let text = stringForCompactBirthday(birthday, strings: presentationData.strings) - - UIPasteboard.general.string = text - - self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: self.presentationData.strings.MyProfile_ToastBirthdayCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - - var items: [ContextMenuItem] = [] - - if self.isMyProfile { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_BirthdayActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - - self.state = self.state.withIsEditingBirthDate(true) - self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) - } - }))) - } - - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_BirthdayActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - copyAction() - } - }))) - - let actions = ContextController.Items(content: .list(items)) - - let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) - self.controller?.present(contextController, in: .window(.root)) - } - - private func openPhone(value: String, node: ASDisplayNode, gesture: ContextGesture?, progress: Promise?) { - guard let sourceNode = node as? ContextExtractedContentContainingNode else { - return - } - - let formattedPhoneNumber = formatPhoneNumber(context: self.context, number: value) - if gesture == nil, formattedPhoneNumber.hasPrefix("+888") { - let collectibleInfo = Promise() - collectibleInfo.set(self.context.sharedContext.makeCollectibleItemInfoScreenInitialData(context: self.context, peerId: self.peerId, subject: .phoneNumber(value))) - - progress?.set(.single(true)) - let _ = (collectibleInfo.get() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] initialData in - progress?.set(.single(false)) - - guard let self else { - return - } - if let initialData { - self.view.endEditing(true) - self.controller?.push(self.context.sharedContext.makeCollectibleItemInfoScreen(context: self.context, initialData: initialData)) - } else { - self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: "https://fragment.com/numbers", forceExternal: true, presentationData: self.presentationData, navigationController: nil, dismissInput: {}) - } - }) - - return - } - - let _ = (combineLatest( - getUserPeer(engine: self.context.engine, peerId: self.peerId), - getUserPeer(engine: self.context.engine, peerId: self.context.account.peerId) - ) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer, accountPeer in - guard let strongSelf = self else { - return - } - let presentationData = strongSelf.presentationData - - let telegramCallAction: (Bool) -> Void = { [weak self] isVideo in - guard let strongSelf = self else { - return - } - strongSelf.requestCall(isVideo: isVideo) - } - - let phoneCallAction = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.context.sharedContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(context: strongSelf.context, number: value).replacingOccurrences(of: " ", with: ""))") - } - - let copyAction = { [weak self] in - guard let strongSelf = self else { - return - } - UIPasteboard.general.string = formatPhoneNumber(context: strongSelf.context, number: value) - - strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_PhoneCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - - var accountIsFromUS = false - if let accountPeer, case let .user(user) = accountPeer, let phone = user.phone { - if let (country, _) = lookupCountryIdByNumber(phone, configuration: strongSelf.context.currentCountriesConfiguration.with { $0 }) { - if country.id == "US" { - accountIsFromUS = true - } - } - } - - var isAnonymousNumber = false - var items: [ContextMenuItem] = [] - - if strongSelf.isMyProfile { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.MyProfile_PhoneActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c?.dismiss { - guard let self else { - return - } - self.openSettings(section: .phoneNumber) - } - }))) - } - - if case let .user(peer) = peer, let peerPhoneNumber = peer.phone, formattedPhoneNumber == formatPhoneNumber(context: strongSelf.context, number: peerPhoneNumber) { - if !strongSelf.isMyProfile { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_TelegramCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - telegramCallAction(false) - } - }))) - items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_TelegramVideoCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoCall"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - telegramCallAction(true) - } - }))) - } - if !formattedPhoneNumber.hasPrefix("+888") { - if !strongSelf.isMyProfile { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_PhoneCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PhoneCall"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - phoneCallAction() - } - }))) - } - } else { - isAnonymousNumber = true - } - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.MyProfile_PhoneActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - copyAction() - } - }))) - } else { - if !formattedPhoneNumber.hasPrefix("+888") { - if !strongSelf.isMyProfile { - items.append( - .action(ContextMenuActionItem(text: presentationData.strings.UserInfo_PhoneCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PhoneCall"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - phoneCallAction() - } - })) - ) - } - } else { - isAnonymousNumber = true - } - items.append( - .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.MyProfile_PhoneActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c?.dismiss { - copyAction() - } - })) - ) - } - var actions = ContextController.Items(content: .list(items)) - if isAnonymousNumber && !accountIsFromUS { - let collectibleInfo = Promise() - collectibleInfo.set(strongSelf.context.sharedContext.makeCollectibleItemInfoScreenInitialData(context: strongSelf.context, peerId: strongSelf.peerId, subject: .phoneNumber(value))) - - actions.tip = .animatedEmoji(text: strongSelf.presentationData.strings.UserInfo_AnonymousNumberInfo, arguments: nil, file: nil, action: { [weak self] in - guard let self else { - return - } - - let _ = (collectibleInfo.get() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] initialData in - guard let self else { - return - } - if let initialData { - self.view.endEditing(true) - self.controller?.push(self.context.sharedContext.makeCollectibleItemInfoScreen(context: self.context, initialData: initialData)) - } else { - self.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: "https://fragment.com/numbers", forceExternal: true, presentationData: self.presentationData, navigationController: nil, dismissInput: {}) - } - }) - }) - } - let contextController = ContextController(presentationData: strongSelf.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) - strongSelf.controller?.present(contextController, in: .window(.root)) - }) - } - private func editingOpenNotificationSettings() { let _ = (combineLatest( self.context.engine.data.get( @@ -8717,63 +3371,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.controller?.present(actionSheet, in: .window(.root)) } - private func openChat(peerId: EnginePeer.Id?) { - if let peerId { - let _ = (self.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) - ) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in - guard let self, let peer else { - return - } - guard let navigationController = self.controller?.navigationController as? NavigationController else { - return - } - - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .always)) - }) - return - } - - if let peer = self.data?.peer, let navigationController = self.controller?.navigationController as? NavigationController { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in - if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { controller in - if controller is PeerInfoScreen { - return false - } - return true - } - navigationController.setViewControllers(viewControllers, animated: false) - } - })) - } - } - - private func openChatWithClearedHistory(type: InteractiveHistoryClearingType) { - guard let peer = self.data?.chatPeer, let navigationController = self.controller?.navigationController as? NavigationController else { - return - } - - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), setupController: { controller in - controller.beginClearHistory(type: type) - }, completion: { [weak self] _ in - if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { controller in - if controller is PeerInfoScreen { - return false - } - return true - } - - navigationController.setViewControllers(viewControllers, animated: false) - } - })) - } - - private func openAddContact() { + func openAddContact() { let _ = (getUserPeer(engine: self.context.engine, peerId: self.peerId) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in guard let strongSelf = self, let peer = peer else { @@ -8791,7 +3389,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }) } - private func updateBlocked(block: Bool) { + func updateBlocked(block: Bool) { let _ = (getUserPeer(engine: self.context.engine, peerId: self.peerId) |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in @@ -8859,11 +3457,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }) } - private func openStoryArchive() { + func openStoryArchive() { self.controller?.push(PeerInfoStoryGridScreen(context: self.context, peerId: self.peerId, scope: .archive)) } - private func openStats(section: ChannelStatsSection, boostStatus: ChannelBoostStatus? = nil) { + func openStats(section: ChannelStatsSection, boostStatus: ChannelBoostStatus? = nil) { guard let controller = self.controller, let data = self.data, let peer = data.peer else { return } @@ -8882,7 +3480,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro controller.push(statsController) } - private func openBoost() { + func openBoost() { guard let peer = self.data?.peer, let channel = peer as? TelegramChannel, let controller = self.controller else { return } @@ -8915,224 +3513,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } - private func openVoiceChatOptions(defaultJoinAsPeerId: PeerId?, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil) { - guard let chatPeer = self.data?.peer else { - return - } - let context = self.context - let peerId = self.peerId - let defaultJoinAsPeerId = defaultJoinAsPeerId ?? self.context.account.peerId - let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) - |> map { peer in - return [FoundPeer(peer: peer, subscribers: nil)] - } - let _ = (combineLatest(queue: Queue.mainQueue(), currentAccountPeer, self.displayAsPeersPromise.get() |> take(1)) - |> map { currentAccountPeer, availablePeers -> [FoundPeer] in - var result = currentAccountPeer - result.append(contentsOf: availablePeers) - return result - }).startStandalone(next: { [weak self] peers in - guard let strongSelf = self else { - return - } - - var items: [ContextMenuItem] = [] - - if peers.count > 1 { - var selectedPeer: FoundPeer? - for peer in peers { - if peer.peer.id == defaultJoinAsPeerId { - selectedPeer = peer - } - } - if let peer = selectedPeer { - let avatarSize = CGSize(width: 28.0, height: 28.0) - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { c, f in - guard let strongSelf = self else { - return - } - - strongSelf.openVoiceChatDisplayAsPeerSelection(completion: { joinAsPeerId in - let _ = context.engine.calls.updateGroupCallJoinAsPeer(peerId: peerId, joinAs: joinAsPeerId).startStandalone() - self?.openVoiceChatOptions(defaultJoinAsPeerId: joinAsPeerId, gesture: nil, contextController: c) - }, gesture: gesture, contextController: c, result: f, backAction: { [weak self] c in - self?.openVoiceChatOptions(defaultJoinAsPeerId: defaultJoinAsPeerId, gesture: nil, contextController: c) - }) - - }))) - items.append(.separator) - } - } - - let createVoiceChatTitle: String - let scheduleVoiceChatTitle: String - if let channel = strongSelf.data?.peer as? TelegramChannel, case .broadcast = channel.info { - createVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_CreateLiveStream - scheduleVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_ScheduleLiveStream - } else { - createVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_CreateVoiceChat - scheduleVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_ScheduleVoiceChat - } - - items.append(.action(ContextMenuActionItem(text: createVoiceChatTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VoiceChat"), color: theme.contextMenu.primaryColor) }, action: { _, f in - f(.dismissWithoutContent) - - self?.createAndJoinGroupCall(peerId: peerId, joinAsPeerId: defaultJoinAsPeerId) - }))) - - items.append(.action(ContextMenuActionItem(text: scheduleVoiceChatTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Schedule"), color: theme.contextMenu.primaryColor) }, action: { _, f in - f(.dismissWithoutContent) - - self?.scheduleGroupCall() - }))) - - var credentialsPromise: Promise? - var canCreateStream = false - switch chatPeer { - case let group as TelegramGroup: - if case .creator = group.role { - canCreateStream = true - } - case let channel as TelegramChannel: - if channel.hasPermission(.manageCalls) { - canCreateStream = true - credentialsPromise = Promise() - credentialsPromise?.set(context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: false, revokePreviousCredentials: false) |> `catch` { _ -> Signal in return .never() }) - } - default: - break - } - - if canCreateStream { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChannelInfo_CreateExternalStream, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VoiceChat"), color: theme.contextMenu.primaryColor) }, action: { _, f in - f(.dismissWithoutContent) - - self?.createExternalStream(credentialsPromise: credentialsPromise) - }))) - } - - if let contextController = contextController { - contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) - } else { - strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat) - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) - } - - if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller { - let contextController = ContextController(presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - contextController.dismissed = { [weak self] in - if let strongSelf = self { - strongSelf.state = strongSelf.state.withHighlightedButton(nil) - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) - } - } - } - controller.presentInGlobalOverlay(contextController) - } - } - }) - } - - private func openVoiceChatDisplayAsPeerSelection(completion: @escaping (PeerId) -> Void, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) { - let dismissOnSelection = contextController == nil - let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(context.account.peerId) - |> map { peer in - return [FoundPeer(peer: peer, subscribers: nil)] - } - let _ = (combineLatest(queue: Queue.mainQueue(), currentAccountPeer, self.displayAsPeersPromise.get() |> take(1)) - |> map { currentAccountPeer, availablePeers -> [FoundPeer] in - var result = currentAccountPeer - result.append(contentsOf: availablePeers) - return result - }).startStandalone(next: { [weak self] peers in - guard let strongSelf = self else { - return - } - if peers.count == 1, let peer = peers.first { - result?(.dismissWithoutContent) - completion(peer.peer.id) - } else { - var items: [ContextMenuItem] = [] - - var isGroup = false - for peer in peers { - if peer.peer is TelegramGroup { - isGroup = true - break - } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { - isGroup = true - break - } - } - - items.append(.custom(VoiceChatInfoContextItem(text: isGroup ? strongSelf.presentationData.strings.VoiceChat_DisplayAsInfoGroup : strongSelf.presentationData.strings.VoiceChat_DisplayAsInfo, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Accounts"), color: theme.actionSheet.primaryTextColor) - }), true)) - - for peer in peers { - var subtitle: String? - if peer.peer.id.namespace == Namespaces.Peer.CloudUser { - subtitle = strongSelf.presentationData.strings.VoiceChat_PersonalAccount - } else if let subscribers = peer.subscribers { - if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { - subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) - } else { - subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers) - } - } - - let avatarSize = CGSize(width: 28.0, height: 28.0) - let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize) - items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { _, f in - if dismissOnSelection { - f(.dismissWithoutContent) - } - completion(peer.peer.id) - }))) - - if peer.peer.id.namespace == Namespaces.Peer.CloudUser { - items.append(.separator) - } - } - if backAction != nil { - items.append(.separator) - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) - }, iconPosition: .left, action: { (c, _) in - if let c, let backAction = backAction { - backAction(c) - } - }))) - } - - if let contextController = contextController { - contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) - } else { - strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat) - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) - } - - if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller { - let contextController = ContextController(presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - contextController.dismissed = { [weak self] in - if let strongSelf = self { - strongSelf.state = strongSelf.state.withHighlightedButton(nil) - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) - } - } - } - controller.presentInGlobalOverlay(contextController) - } - } - } - }) - } - - private func openReport(type: PeerInfoReportType, contextController: ContextControllerProtocol?, backAction: ((ContextControllerProtocol) -> Void)?) { + func openReport(type: PeerInfoReportType, contextController: ContextControllerProtocol?, backAction: ((ContextControllerProtocol) -> Void)?) { self.view.endEditing(true) switch type { @@ -9189,7 +3570,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.controller?.push(SecretChatKeyController(context: self.context, fingerprint: encryptionKeyFingerprint, peer: EnginePeer(peer))) } - private func openShareLink(url: String) { + func openShareLink(url: String) { let shareController = ShareController(context: self.context, subject: .url(url), updatedPresentationData: self.controller?.updatedPresentationData) shareController.completed = { [weak self] peerIds in guard let strongSelf = self else { @@ -9256,7 +3637,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.controller?.present(shareController, in: .window(.root)) } - private func openShareBot() { + func openShareBot() { let _ = (getUserPeer(engine: self.context.engine, peerId: self.peerId) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in guard let strongSelf = self else { @@ -9286,7 +3667,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, contentContext: nil, progress: nil, completion: nil) } - private func performBotCommand(command: PeerInfoBotCommand) { + func performBotCommand(command: PeerInfoBotCommand) { let _ = (self.context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in guard let strongSelf = self else { @@ -9750,31 +4131,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } - private func openRecentActions() { - guard let peer = self.data?.peer else { - return - } - let controller = self.context.sharedContext.makeChatRecentActionsController(context: self.context, peer: peer, adminPeerId: nil, starsState: self.data?.starsRevenueStatsState) - self.controller?.push(controller) - } - - private func openChannelMessages() { - guard let channel = self.data?.peer as? TelegramChannel, let linkedMonoforumId = channel.linkedMonoforumId else { - return - } - let _ = (self.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId) - ) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in - guard let self, let peer else { - return - } - if let controller = self.controller, let navigationController = controller.navigationController as? NavigationController { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) - } - }) - } - private func editingOpenPreHistorySetup() { guard let data = self.data, let peer = data.peer else { return @@ -9809,13 +4165,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.controller?.push(channelPermissionsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id)) } - private func editingOpenStickerPackSetup() { - guard let data = self.data, let peer = data.peer, let cachedData = data.cachedData as? CachedChannelData else { - return - } - self.controller?.push(groupStickerPackSetupController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id, currentPackInfo: cachedData.stickerPack)) - } - private func openLocation() { guard let data = self.data, let peer = data.peer else { return @@ -10009,170 +4358,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } - private func openPeerInfoContextMenu(subject: PeerInfoContextSubject, sourceNode: ASDisplayNode, sourceRect: CGRect?) { - guard let data = self.data, let peer = data.peer, let controller = self.controller else { - return - } - let context = self.context - switch subject { - case .birthday: - if let cachedData = data.cachedData as? CachedUserData, let birthday = cachedData.birthday { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let text = stringForCompactBirthday(birthday, strings: presentationData.strings) - - let actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in - UIPasteboard.general.string = text - - self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - })] - let contextMenuController = makeContextMenuController(actions: actions) - controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in - if let controller = self?.controller, let sourceNode = sourceNode { - var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) - if let sourceRect = sourceRect { - rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) - } - return (sourceNode, rect, controller.displayNode, controller.view.bounds) - } else { - return nil - } - })) - } - case .bio: - var text: String? - if let cachedData = data.cachedData as? CachedUserData { - text = cachedData.about - } else if let cachedData = data.cachedData as? CachedGroupData { - text = cachedData.about - } else if let cachedData = data.cachedData as? CachedChannelData { - text = cachedData.about - } - if let text = text, !text.isEmpty { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak self] sharedData in - let translationSettings: TranslationSettings - if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { - translationSettings = current - } else { - translationSettings = TranslationSettings.defaultSettings - } - - var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in - UIPasteboard.general.string = text - - self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - })] - - let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages) - if canTranslate { - actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in - - let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) - controller.pushController = { [weak self] c in - (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true - self?.controller?.push(c) - } - controller.presentController = { [weak self] c in - self?.controller?.present(c, in: .window(.root)) - } - self?.controller?.present(controller, in: .window(.root)) - })) - } - - let contextMenuController = makeContextMenuController(actions: actions) - controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in - if let controller = self?.controller, let sourceNode = sourceNode { - var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) - if let sourceRect = sourceRect { - rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) - } - return (sourceNode, rect, controller.displayNode, controller.view.bounds) - } else { - return nil - } - })) - }) - } - case let .phone(phone): - let contextMenuController = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in - UIPasteboard.general.string = phone - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_PhoneCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - })]) - controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in - if let controller = self?.controller, let sourceNode = sourceNode { - var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) - if let sourceRect = sourceRect { - rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) - } - return (sourceNode, rect, controller.displayNode, controller.view.bounds) - } else { - return nil - } - })) - case let .link(customLink): - let text: String - let content: UndoOverlayContent - if let customLink = customLink { - text = customLink - content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied) - } else if let addressName = peer.addressName { - if peer is TelegramChannel { - text = "https://t.me/\(addressName)" - content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied) - } else { - text = "@" + addressName - content = .copy(text: self.presentationData.strings.Conversation_UsernameCopied) - } - } else { - text = "https://t.me/@id\(peer.id.id._internalGetInt64Value())" - content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied) - } - - let contextMenuController = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in - UIPasteboard.general.string = text - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - })]) - controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in - if let controller = self?.controller, let sourceNode = sourceNode { - var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) - if let sourceRect = sourceRect { - rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) - } - return (sourceNode, rect, controller.displayNode, controller.view.bounds) - } else { - return nil - } - })) - case .businessHours(let text), .businessLocation(let text): - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - let actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in - UIPasteboard.general.string = text - - self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - })] - - let contextMenuController = makeContextMenuController(actions: actions) - controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in - if let controller = self?.controller, let sourceNode = sourceNode { - var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) - if let sourceRect = sourceRect { - rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) - } - return (sourceNode, rect, controller.displayNode, controller.view.bounds) - } else { - return nil - } - })) - } - } - private func performBioLinkAction(action: TextLinkItemActionType, item: TextLinkItem) { guard let data = self.data, let peer = data.peer, let controller = self.controller else { return @@ -10229,7 +4414,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }) } - private func openLeavePeer(delete: Bool) { + func openLeavePeer(delete: Bool) { let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId)) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in guard let strongSelf = self, let peer = peer else { @@ -10270,8 +4455,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } strongSelf.view.endEditing(true) - - strongSelf.controller?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [ + + strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: title, text: text, actions: [ TextAlertAction(type: .destructiveAction, title: actionText, action: { self?.deletePeerChat(peer: peer._asPeer(), globally: delete) }), @@ -10315,178 +4500,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } - func oldOpenAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) { - guard let peer = self.data?.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else { - return - } - - self.view.endEditing(true) - - var currentIsVideo = false - var emojiMarkup: TelegramMediaImage.EmojiMarkup? - let item = self.headerNode.avatarListNode.listContainerNode.currentItemNode?.item - if let item = item, case let .image(_, _, videoRepresentations, _, _, emojiMarkupValue) = item { - currentIsVideo = !videoRepresentations.isEmpty - emojiMarkup = emojiMarkupValue - } - let _ = emojiMarkup - - let peerId = self.peerId - let _ = (self.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), - TelegramEngine.EngineData.Item.Configuration.SearchBots() - ) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peer, searchBotsConfiguration in - guard let strongSelf = self, let peer = peer else { - return - } - - let presentationData = strongSelf.presentationData - - var hasPhotos = false - if !peer.profileImageRepresentations.isEmpty { - hasPhotos = true - } - - var isForum = false - if let peer = strongSelf.data?.peer as? TelegramChannel, peer.isForumOrMonoForum { - isForum = true - } - - var hasDeleteButton = false - if case .generic = mode { - hasDeleteButton = hasPhotos && !fromGallery - } else if case .custom = mode { - hasDeleteButton = peer.profileImageRepresentations.first?.isPersonal == true - } else if case .fallback = mode { - if let cachedData = strongSelf.data?.cachedData as? CachedUserData, case let .known(photo) = cachedData.fallbackPhoto { - hasDeleteButton = photo != nil - } - } - - let title: String? - let confirmationTextPhoto: String? - let confirmationTextVideo: String? - let confirmationAction: String? - switch mode { - case .suggest: - title = strongSelf.presentationData.strings.UserInfo_SuggestPhotoTitle(peer.compactDisplayTitle).string - confirmationTextPhoto = strongSelf.presentationData.strings.UserInfo_SuggestPhoto_AlertPhotoText(peer.compactDisplayTitle).string - confirmationTextVideo = strongSelf.presentationData.strings.UserInfo_SuggestPhoto_AlertVideoText(peer.compactDisplayTitle).string - confirmationAction = strongSelf.presentationData.strings.UserInfo_SuggestPhoto_AlertSuggest - case .custom: - title = strongSelf.presentationData.strings.UserInfo_SetCustomPhotoTitle(peer.compactDisplayTitle).string - confirmationTextPhoto = strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_AlertPhotoText(peer.compactDisplayTitle, peer.compactDisplayTitle).string - confirmationTextVideo = strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_AlertVideoText(peer.compactDisplayTitle, peer.compactDisplayTitle).string - confirmationAction = strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_AlertSet - default: - title = nil - confirmationTextPhoto = nil - confirmationTextVideo = nil - confirmationAction = nil - } - - let keyboardInputData = Promise() - keyboardInputData.set(AvatarEditorScreen.inputData(context: strongSelf.context, isGroup: peer.id.namespace != Namespaces.Peer.CloudUser)) - - let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) - legacyController.statusBar.statusBarStyle = .Ignore - - let emptyController = LegacyEmptyController(context: legacyController.context)! - let navigationController = makeLegacyNavigationController(rootController: emptyController) - navigationController.setNavigationBarHidden(true, animated: false) - navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) - - legacyController.bind(controller: navigationController) - - let parentController = (strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController as? ViewController - parentController?.present(legacyController, in: .window(.root)) - - let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings || strongSelf.isMyProfile, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title, isSuggesting: [.custom, .suggest].contains(mode))! - mixin.stickersContext = LegacyPaintStickersContext(context: strongSelf.context) - let _ = strongSelf.currentAvatarMixin.swap(mixin) - let isFromEditor = !"".isEmpty -// mixin.requestAvatarEditor = { [weak self, weak parentController] imageCompletion, videoCompletion in -// guard let strongSelf = self, let imageCompletion, let videoCompletion else { -// return -// } -// let peerType: AvatarEditorScreen.PeerType -// if mode == .suggest { -// peerType = .suggest -// } else if case .legacyGroup = peer { -// peerType = .group -// } else if case let .channel(channel) = peer { -// if case .group = channel.info { -// peerType = channel.flags.contains(.isForum) ? .forum : .group -// } else { -// peerType = .channel -// } -// } else { -// peerType = .user -// } -// let controller = AvatarEditorScreen(context: strongSelf.context, inputData: keyboardInputData.get(), peerType: peerType, markup: emojiMarkup) -// controller.imageCompletion = imageCompletion -// controller.videoCompletion = videoCompletion -// parentController?.push(controller) -// isFromEditor = true -// } - - if let confirmationTextPhoto, let confirmationAction { - mixin.willFinishWithImage = { [weak self, weak parentController] image, commit in - if let strongSelf = self, let image { - let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextPhoto, doneTitle: confirmationAction, commit: { - commit?() - }) - parentController?.presentInGlobalOverlay(controller) - } - } - } - if let confirmationTextVideo, let confirmationAction { - mixin.willFinishWithVideo = { [weak self, weak parentController] image, commit in - if let strongSelf = self, let image { - let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextVideo, doneTitle: confirmationAction, isDark: !isFromEditor, commit: { - commit?() - }) - parentController?.presentInGlobalOverlay(controller) - } - } - } - mixin.didFinishWithImage = { [weak self] image in - if let image = image { - completion(image) - self?.controller?.updateProfilePhoto(image, mode: mode, uploadStatus: nil) - } - } -// mixin.didFinishWithVideo = { [weak self] image, asset, adjustments, _ in -// if let image = image, let asset = asset { -// completion(image) -// self?.controller?.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: mode) -// } -// } - mixin.didFinishWithDelete = { - guard let strongSelf = self else { - return - } - - strongSelf.controller?.openAvatarRemoval(mode: mode, peer: peer, item: item) - } - mixin.didDismiss = { [weak legacyController] in - guard let strongSelf = self else { - return - } - let _ = strongSelf.currentAvatarMixin.swap(nil) - legacyController?.dismiss() - } - let menuController = mixin.present() - if let menuController = menuController { - menuController.customRemoveFromParentViewController = { [weak legacyController] in - legacyController?.dismiss() - } - } - }) - } - - private func openAddMember() { + func openAddMember() { guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { return } @@ -10497,7 +4511,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro presentAddMembersImpl(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, parentController: controller, groupPeer: groupPeer, selectAddMemberDisposable: self.selectAddMemberDisposable, addMemberDisposable: self.addMemberDisposable) } - private func openQrCode() { + func openQrCode() { guard let data = self.data, let peer = data.peer, let controller = self.controller else { return } @@ -10515,7 +4529,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro controller.push(qrController) } - private func openPremiumGift() { + func openPremiumGift() { guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { return } @@ -10636,7 +4650,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro switch status { case .available: var cameraTransitionIn: StoryCameraTransitionIn? - if let rightButton = self.headerNode.navigationButtonContainer.rightButtonNodes[.postStory] { + if let rightButton = self.headerNode.navigationButtonContainer.rightButtonNodes.first(where: { $0.key.key == .postStory })?.value { cameraTransitionIn = StoryCameraTransitionIn( sourceView: rightButton.view, sourceRect: rightButton.view.bounds, @@ -10745,287 +4759,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } - fileprivate func openSettings(section: PeerInfoSettingsSection) { - let push: (ViewController) -> Void = { [weak self] c in - guard let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController else { - return - } - - if strongSelf.isMyProfile { - navigationController.pushViewController(c) - } else { - var updatedControllers = navigationController.viewControllers - for controller in navigationController.viewControllers.reversed() { - if controller !== strongSelf && !(controller is TabBarController) { - updatedControllers.removeLast() - } else { - break - } - } - updatedControllers.append(c) - - var animated = true - if let validLayout = strongSelf.validLayout?.0, case .regular = validLayout.metrics.widthClass { - animated = false - } - navigationController.setViewControllers(updatedControllers, animated: animated) - } - } - switch section { - case .avatar: - self.controller?.openAvatarForEditing() - case .edit: - self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) - case .proxy: - self.controller?.push(proxySettingsController(context: self.context)) - case .profile: - self.controller?.push(PeerInfoScreenImpl( - context: self.context, - updatedPresentationData: self.controller?.updatedPresentationData, - peerId: self.context.account.peerId, - avatarInitiallyExpanded: false, - isOpenedFromChat: false, - nearbyPeerDistance: nil, - reactionSourceMessageId: nil, - callMessages: [], - isMyProfile: true, - profileGiftsContext: self.data?.profileGiftsContext - )) - case .stories: - push(PeerInfoStoryGridScreen(context: self.context, peerId: self.context.account.peerId, scope: .saved)) - case .savedMessages: - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in - guard let self, let peer = peer else { - return - } - if let controller = self.controller, let navigationController = controller.navigationController as? NavigationController { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) - } - }) - case .recentCalls: - push(CallListController(context: context, mode: .navigation)) - case .devices: - let _ = (self.activeSessionsContextAndCount.get() - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak self] activeSessionsContextAndCount in - if let strongSelf = self, let activeSessionsContextAndCount = activeSessionsContextAndCount { - let (activeSessionsContext, _, webSessionsContext) = activeSessionsContextAndCount - push(recentSessionsController(context: strongSelf.context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false)) - } - }) - case .chatFolders: - let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: false, scrollToTags: false, dismissed: nil) - push(controller) - case .notificationsAndSounds: - if let settings = self.data?.globalSettings { - push(notificationsAndSoundsController(context: self.context, exceptionsList: settings.notificationExceptions)) - } - case .privacyAndSecurity: - if let settings = self.data?.globalSettings { - let _ = (combineLatest(self.blockedPeers.get(), self.hasTwoStepAuth.get()) - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak self] blockedPeersContext, hasTwoStepAuth in - if let strongSelf = self { - let loginEmailPattern = strongSelf.twoStepAuthData.get() |> map { data -> String? in - return data?.loginEmailPattern - } - push(privacyAndSecurityController(context: strongSelf.context, initialSettings: settings.privacySettings, updatedSettings: { [weak self] settings in - self?.privacySettings.set(.single(settings)) - }, updatedBlockedPeers: { [weak self] blockedPeersContext in - self?.blockedPeers.set(.single(blockedPeersContext)) - }, updatedHasTwoStepAuth: { [weak self] hasTwoStepAuthValue in - self?.hasTwoStepAuth.set(.single(hasTwoStepAuthValue)) - }, focusOnItemTag: nil, activeSessionsContext: settings.activeSessionsContext, webSessionsContext: settings.webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth, loginEmailPattern: loginEmailPattern, updatedTwoStepAuthData: { [weak self] in - if let strongSelf = self { - strongSelf.twoStepAuthData.set( - strongSelf.context.engine.auth.twoStepAuthData() - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - ) - } - }, requestPublicPhotoSetup: { [weak self] completion in - if let self { - self.controller?.openAvatarForEditing(mode: .fallback, completion: completion) - } - }, requestPublicPhotoRemove: { [weak self] completion in - if let self { - self.controller?.openAvatarRemoval(mode: .fallback, completion: completion) - } - })) - } - }) - } - case .passwordSetup: - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.6, execute: { [weak self] in - guard let self else { - return - } - let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPassword.id).startStandalone() - }) - - let controller = self.context.sharedContext.makeSetupTwoFactorAuthController(context: self.context) - push(controller) - case .dataAndStorage: - push(dataAndStorageController(context: self.context)) - case .appearance: - push(themeSettingsController(context: self.context)) - case .language: - push(LocalizationListController(context: self.context)) - case .premium: - let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .settings, forceDark: false, dismissed: nil) - self.controller?.push(controller) - case .premiumGift: - guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { - return - } - let _ = (self.context.account.stateManager.contactBirthdays - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] birthdays in - guard let self else { - return - } - let giftsController = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .settings(birthdays), completion: nil) - self.controller?.push(giftsController) - }) - case .stickers: - if let settings = self.data?.globalSettings { - push(installedStickerPacksController(context: self.context, mode: .general, archivedPacks: settings.archivedStickerPacks, updatedPacks: { [weak self] packs in - self?.archivedPacks.set(.single(packs)) - })) - } - case .passport: - self.controller?.push(SecureIdAuthController(context: self.context, mode: .list)) - case .watch: - push(watchSettingsController(context: self.context)) - case .support: - let supportPeer = Promise() - supportPeer.set(context.engine.peers.supportPeerId()) - - self.controller?.present(textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.Settings_FAQ_Intro, actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_FAQ_Button, action: { [weak self] in - self?.openFaq() - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { [weak self] in - guard let self else { - return - } - self.supportPeerDisposable.set((supportPeer.get() |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak self] peerId in - if let strongSelf = self, let peerId = peerId { - push(strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil)) - } - })) - })]), in: .window(.root)) - case .faq: - self.openFaq() - case .tips: - self.openTips() - case .phoneNumber: - guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { - return - } - if let user = self.data?.peer as? TelegramUser, let phoneNumber = user.phone { - let introController = PrivacyIntroController(context: self.context, mode: .changePhoneNumber(phoneNumber), proceedAction: { [weak self] in - if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { - navigationController.replaceTopController(ChangePhoneNumberController(context: strongSelf.context), animated: true) - } - }) - push(introController) - } - case .username: - guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { - return - } - push(usernameSetupController(context: self.context)) - case .addAccount: - let _ = (activeAccountsAndPeers(context: context) - |> take(1) - |> deliverOnMainQueue - ).startStandalone(next: { [weak self] accountAndPeer, accountsAndPeers in - guard let strongSelf = self else { - return - } - var maximumAvailableAccounts: Int = 3 - if accountAndPeer?.1.isPremium == true && !strongSelf.context.account.testingEnvironment { - maximumAvailableAccounts = 4 - } - var count: Int = 1 - for (accountContext, peer, _) in accountsAndPeers { - if !accountContext.account.testingEnvironment { - if peer.isPremium { - maximumAvailableAccounts = 4 - } - count += 1 - } - } - - if count >= maximumAvailableAccounts { - var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: strongSelf.context, subject: .accounts, count: Int32(count), action: { - let controller = PremiumIntroScreen(context: strongSelf.context, source: .accounts) - replaceImpl?(controller) - return true - }) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - navigationController.pushViewController(controller) - } - } else { - strongSelf.context.sharedContext.beginNewAuth(testingEnvironment: strongSelf.context.account.testingEnvironment) - } - }) - case .logout: - if let user = self.data?.peer as? TelegramUser, let phoneNumber = user.phone { - if let controller = self.controller, let navigationController = controller.navigationController as? NavigationController { - self.controller?.push(logoutOptionsController(context: self.context, navigationController: navigationController, canAddAccounts: true, phoneNumber: phoneNumber)) - } - } - case .rememberPassword: - let context = self.context - let controller = TwoFactorDataInputScreen(sharedContext: self.context.sharedContext, engine: .authorized(self.context.engine), mode: .rememberPassword(doneText: self.presentationData.strings.TwoFactorSetup_Done_Action), stateUpdated: { _ in - }, presentation: .modalInLargeLayout) - controller.twoStepAuthSettingsController = { configuration in - return twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: false, data: .single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: configuration, password: nil))))) - } - controller.passwordRemembered = { - let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.validatePassword.id).startStandalone() - } - push(controller) - case .emojiStatus: - self.headerNode.invokeDisplayPremiumIntro() - case .profileColor: - self.interaction.editingOpenNameColorSetup() - case .powerSaving: - push(energySavingSettingsScreen(context: self.context)) - case .businessSetup: - guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { - return - } - push(self.context.sharedContext.makeBusinessSetupScreen(context: self.context)) - case .premiumManagement: - guard let controller = self.controller else { - return - } - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) - let url = premiumConfiguration.subscriptionManagementUrl - guard !url.isEmpty else { - return - } - self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: !url.hasPrefix("tg://") && !url.contains("?start="), presentationData: self.context.sharedContext.currentPresentationData.with({$0}), navigationController: controller.navigationController as? NavigationController, dismissInput: {}) - case .stars: - if let starsContext = self.controller?.starsContext { - push(self.context.sharedContext.makeStarsTransactionsScreen(context: self.context, starsContext: starsContext)) - } - case .ton: - if let tonContext = self.controller?.tonContext { - push(self.context.sharedContext.makeStarsTransactionsScreen(context: self.context, starsContext: tonContext)) - } - } - } - fileprivate func openPaymentMethod() { self.controller?.push(AddPaymentMethodSheetScreen(context: self.context, action: { [weak self] in guard let strongSelf = self else { @@ -11040,135 +4773,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro })) } - private func openFaq(anchor: String? = nil) { - let presentationData = self.presentationData - let progressSignal = Signal { [weak self] subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) - self?.controller?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - let _ = (self.cachedFaq.get() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] resolvedUrl in - progressDisposable.dispose() - - if let strongSelf = self, let resolvedUrl = resolvedUrl { - var resolvedUrl = resolvedUrl - if case let .instantView(webPage, _) = resolvedUrl, let customAnchor = anchor { - resolvedUrl = .instantView(webPage, customAnchor) - } - strongSelf.context.sharedContext.openResolvedUrl(resolvedUrl, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, forceExternal: false, forceUpdate: false, openPeer: { peer, navigation in - }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak self] controller, arguments in - self?.controller?.push(controller) - }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) - } - }) - } - - private func openTips() { - let controller = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil)) - self.controller?.present(controller, in: .window(.root)) - - let context = self.context - let navigationController = self.controller?.navigationController as? NavigationController - self.tipsPeerDisposable.set((self.context.engine.peers.resolvePeerByName(name: self.presentationData.strings.Settings_TipsUsername, referrer: nil) - |> mapToSignal { result -> Signal in - guard case let .result(result) = result else { - return .complete() - } - return .single(result) - } - |> deliverOnMainQueue).startStrict(next: { [weak controller] peer in - controller?.dismiss() - if let peer = peer, let navigationController = navigationController { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) - } - })) - } - - fileprivate func switchToAccount(id: AccountRecordId) { - self.accountsAndPeers.set(.never()) - self.context.sharedContext.switchToAccount(id: id, fromSettingsController: nil, withChatListController: nil) - } - - private func logoutAccount(id: AccountRecordId) { - let controller = ActionSheetController(presentationData: self.presentationData) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } - - var items: [ActionSheetItem] = [] - items.append(ActionSheetTextItem(title: self.presentationData.strings.Settings_LogoutConfirmationText.trimmingCharacters(in: .whitespacesAndNewlines))) - items.append(ActionSheetButtonItem(title: self.presentationData.strings.Settings_Logout, color: .destructive, action: { [weak self] in - dismissAction() - if let strongSelf = self { - let _ = logoutFromAccount(id: id, accountManager: strongSelf.context.sharedContext.accountManager, alreadyLoggedOutRemotely: false).startStandalone() - } - })) - controller.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - self.controller?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - } - - private func accountContextMenuItems(context: AccountContext, logout: @escaping () -> Void) -> Signal<[ContextMenuItem], NoError> { - let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings - return context.engine.messages.unreadChatListPeerIds(groupId: .root, filterPredicate: nil) - |> map { unreadChatListPeerIds -> [ContextMenuItem] in - var items: [ContextMenuItem] = [] - - if !unreadChatListPeerIds.isEmpty { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAllAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = (context.engine.messages.markAllChatsAsReadInteractively(items: [(groupId: .root, filterPredicate: nil)]) - |> deliverOnMainQueue).startStandalone(completed: { - f(.default) - }) - }))) - } - - items.append(.action(ContextMenuActionItem(text: strings.Settings_Context_Logout, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Logout"), color: theme.contextMenu.destructiveColor) }, action: { _, f in - logout() - f(.default) - }))) - - return items - } - } - - private func accountContextMenu(id: AccountRecordId, node: ASDisplayNode, gesture: ContextGesture?) { - var selectedAccount: Account? - let _ = (self.accountsAndPeers.get() - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { accountsAndPeers in - for (account, _, _) in accountsAndPeers { - if account.account.id == id { - selectedAccount = account.account - break - } - } - }) - if let selectedAccount = selectedAccount { - let accountContext = self.context.sharedContext.makeTempAccountContext(account: selectedAccount) - let chatListController = accountContext.sharedContext.makeChatListController(context: accountContext, location: .chatList(groupId: EngineChatList.Group(.root)), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) - - let contextController = ContextController(presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node)), items: accountContextMenuItems(context: accountContext, logout: { [weak self] in - self?.logoutAccount(id: id) - }) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) - self.controller?.presentInGlobalOverlay(contextController) - } else { - gesture?.cancel() - } - } - private func updateBio(_ bio: String) { self.state = self.state.withUpdatingBio(bio) if let (layout, navigationHeight) = self.validLayout { @@ -11176,280 +4780,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } - private func deleteMessages(messageIds: Set?) { - if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty { - self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(engine: self.context.engine, accountPeerId: self.context.account.peerId, messageIds: messageIds, keepUpdated: false) - |> deliverOnMainQueue).startStrict(next: { [weak self] actions in - if let strongSelf = self, let peer = strongSelf.data?.peer, !actions.options.isEmpty { - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - var items: [ActionSheetItem] = [] - var personalPeerName: String? - var isChannel = false - if let user = peer as? TelegramUser { - personalPeerName = EnginePeer(user).compactDisplayTitle - } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { - isChannel = true - } - - if actions.options.contains(.deleteGlobally) { - let globalTitle: String - if isChannel { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe - } else if let personalPeerName = personalPeerName { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string - } else { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone - } - items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) - let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() - } - })) - } - if actions.options.contains(.deleteLocally) { - var localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe - if strongSelf.context.account.peerId == strongSelf.peerId { - if messageIds.count == 1 { - localOptionText = strongSelf.presentationData.strings.Conversation_Moderate_Delete - } else { - localOptionText = strongSelf.presentationData.strings.Conversation_DeleteManyMessages - } - } - items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) - let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).startStandalone() - } - })) - } - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.view.endEditing(true) - strongSelf.controller?.present(actionSheet, in: .window(.root)) - } - })) - } - } - - func forwardMessages(messageIds: Set?) { - if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty { - let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, filter: [.onlyWriteable, .excludeDisabled], hasFilters: true, multipleSelection: true, selectForumThreads: true)) - peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in - guard let strongSelf = self, let strongController = peerSelectionController else { - return - } - strongController.dismiss() - - var result: [EnqueueMessage] = [] - if messageText.string.count > 0 { - let inputText = convertMarkdownToAttributes(messageText) - for text in breakChatInputText(trimChatInputText(inputText)) { - if text.length != 0 { - var attributes: [MessageAttribute] = [] - let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) - if !entities.isEmpty { - attributes.append(TextEntitiesMessageAttribute(entities: entities)) - } - result.append(.message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) - } - } - } - - var attributes: [MessageAttribute] = [] - attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions?.hideNames == true, hideCaptions: forwardOptions?.hideCaptions == true)) - - result.append(contentsOf: messageIds.map { messageId -> EnqueueMessage in - return .forward(source: messageId, threadId: nil, grouping: .auto, attributes: attributes, correlationId: nil) - }) - - var displayPeers: [EnginePeer] = [] - for peer in peers { - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: result) - |> deliverOnMainQueue).startStandalone(next: { messageIds in - if let strongSelf = self { - let signals: [Signal] = messageIds.compactMap({ id -> Signal? in - guard let id = id else { - return nil - } - return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) - |> mapToSignal { status, _ -> Signal in - if status != nil { - return .never() - } else { - return .single(true) - } - } - |> take(1) - }) - if strongSelf.shareStatusDisposable == nil { - strongSelf.shareStatusDisposable = MetaDisposable() - } - strongSelf.shareStatusDisposable?.set((combineLatest(signals) - |> deliverOnMainQueue).startStrict()) - } - }) - - if case let .secretChat(secretPeer) = peer { - if let peer = peerMap[secretPeer.regularPeerId] { - displayPeers.append(peer) - } - } else { - displayPeers.append(peer) - } - } - - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let text: String - var savedMessages = false - if displayPeers.count == 1, let peerId = displayPeers.first?.id, peerId == strongSelf.context.account.peerId { - text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many - savedMessages = true - } else { - if displayPeers.count == 1, let peer = displayPeers.first { - var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - peerName = peerName.replacingOccurrences(of: "**", with: "") - text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string - } else if displayPeers.count == 2, let firstPeer = displayPeers.first, let secondPeer = displayPeers.last { - var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "") - var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "") - text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string - } else if let peer = displayPeers.first { - var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - peerName = peerName.replacingOccurrences(of: "**", with: "") - text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(displayPeers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(displayPeers.count - 1)").string - } else { - text = "" - } - } - - strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in - if savedMessages, let self, action == .info { - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let peer else { - return - } - guard let navigationController = self.controller?.navigationController as? NavigationController else { - return - } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) - }) - } - return false - }), in: .current) - } - peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peer, threadId in - let peerId = peer.id - - if let strongSelf = self, let _ = peerSelectionController { - if peerId == strongSelf.context.account.peerId { - Queue.mainQueue().after(0.88) { - strongSelf.hapticFeedback.success() - } - - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: true, action: { action in - if let self, action == .info { - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let peer else { - return - } - guard let navigationController = self.controller?.navigationController as? NavigationController else { - return - } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) - }) - } - return false - }), in: .current) - - strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) - - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in - return .forward(source: id, threadId: nil, grouping: .auto, attributes: [], correlationId: nil) - }) - |> deliverOnMainQueue).startStandalone(next: { [weak self] messageIds in - if let strongSelf = self { - let signals: [Signal] = messageIds.compactMap({ id -> Signal? in - guard let id = id else { - return nil - } - return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) - |> mapToSignal { status, _ -> Signal in - if status != nil { - return .never() - } else { - return .single(true) - } - } - |> take(1) - }) - strongSelf.activeActionDisposable.set((combineLatest(signals) - |> deliverOnMainQueue).startStrict()) - } - }) - if let peerSelectionController = peerSelectionController { - peerSelectionController.dismiss() - } - } else { - let _ = (ChatInterfaceState.update(engine: strongSelf.context.engine, peerId: peerId, threadId: threadId, { currentState in - return currentState.withUpdatedForwardMessageIds(Array(messageIds)) - }) - |> deliverOnMainQueue).startStandalone(completed: { - if let strongSelf = self { - let proceed: (ChatController) -> Void = { chatController in - strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) - - if let navigationController = strongSelf.controller?.navigationController as? NavigationController { - var viewControllers = navigationController.viewControllers - if threadId != nil { - viewControllers.insert(chatController, at: viewControllers.count - 2) - } else { - viewControllers.insert(chatController, at: viewControllers.count - 1) - } - navigationController.setViewControllers(viewControllers, animated: false) - - strongSelf.activeActionDisposable.set((chatController.ready.get() - |> filter { $0 } - |> take(1) - |> deliverOnMainQueue).startStrict(next: { [weak navigationController] _ in - viewControllers.removeAll(where: { $0 is PeerSelectionController }) - navigationController?.setViewControllers(viewControllers, animated: true) - })) - } - } - - if let threadId = threadId { - let _ = (strongSelf.context.sharedContext.chatControllerForForumThread(context: strongSelf.context, peerId: peerId, threadId: threadId) - |> deliverOnMainQueue).startStandalone(next: { chatController in - proceed(chatController) - }) - } else { - let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: .none, botStart: nil, mode: .standard(.default), params: nil) - proceed(chatController) - } - } - }) - } - } - } - self.controller?.push(peerSelectionController) - } - } - - private func activateSearch() { + func activateSearch() { guard let (layout, navigationBarHeight) = self.validLayout, self.searchDisplayController == nil else { return } + guard let controller = self.controller else { + return + } if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .savedMessages = currentPaneKey, let paneNode = self.paneContainerNode.currentPane?.node as? PeerInfoChatPaneNode { paneNode.activateSearch() @@ -11462,10 +4799,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.headerNode.navigationButtonContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) if self.isSettings { - (self.controller?.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.3, curve: .linear)) - if let settings = self.data?.globalSettings { - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Settings_Search, hasBackground: true, hasSeparator: true, contentNode: SettingsSearchContainerNode(context: self.context, openResult: { [weak self] result in + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .navigation, placeholder: self.presentationData.strings.Settings_Search, hasBackground: true, hasSeparator: true, contentNode: SettingsSearchContainerNode(context: self.context, openResult: { [weak self] result in if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { result.present(strongSelf.context, navigationController, { [weak self] mode, controller in if let strongSelf = self { @@ -11492,17 +4827,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } }, resolvedFaqUrl: self.cachedFaq.get(), exceptionsList: .single(settings.notificationExceptions), archivedStickerPacks: .single(settings.archivedStickerPacks), privacySettings: .single(settings.privacySettings), hasTwoStepAuth: self.hasTwoStepAuth.get(), twoStepAuthData: self.twoStepAccessConfiguration.get(), activeSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.0 }, webSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.2 }), cancel: { [weak self] in self?.deactivateSearch() - }) + }, searchBarIsExternal: true) } } else if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .members = currentPaneKey { - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, hasBackground: true, hasSeparator: true, contentNode: ChannelMembersSearchContainerNode(context: self.context, forceTheme: nil, peerId: self.peerId, mode: .searchMembers, filters: [], searchContext: self.groupMembersSearchContext, openPeer: { [weak self] peer, participant in + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .navigation, placeholder: self.presentationData.strings.Common_Search, hasBackground: true, hasSeparator: true, contentNode: ChannelMembersSearchContainerNode(context: self.context, forceTheme: nil, peerId: self.peerId, mode: .searchMembers, filters: [], searchContext: self.groupMembersSearchContext, openPeer: { [weak self] peer, participant in self?.openPeer(peerId: peer.id, navigation: .info(nil)) }, updateActivity: { _ in }, pushController: { [weak self] c in self?.controller?.push(c) }), cancel: { [weak self] in self?.deactivateSearch() - }) + }, fieldStyle: .glass) } else if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .savedMessagesChats = currentPaneKey { let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.context.animationCache, animationRenderer: self.context.animationRenderer, filter: [.removeSearchHeader], requestPeerType: nil, location: .savedMessagesChats(peerId: self.context.account.peerId), displaySearchFilters: false, hasDownloads: false, initialFilter: .chats, openPeer: { [weak self] peer, _, _, _ in guard let self else { @@ -11596,9 +4931,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, hasBackground: true, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.peerId, threadId: self.chatLocation.threadId, tagMask: tagMask, interfaceInteraction: self.chatInterfaceInteraction), cancel: { [weak self] in + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .navigation, placeholder: self.presentationData.strings.Common_Search, hasBackground: false, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.peerId, threadId: self.chatLocation.threadId, tagMask: tagMask, interfaceInteraction: self.chatInterfaceInteraction), cancel: { [weak self] in self?.deactivateSearch() - }) + }, fieldStyle: .glass) } let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut) @@ -11606,37 +4941,54 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro transition.updateAlpha(node: navigationBar, alpha: 0.0) } - self.searchDisplayController?.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight + 10.0, transition: .immediate) + self.searchDisplayController?.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in - if let strongSelf = self, let navigationBar = strongSelf.controller?.navigationBar { - strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) + guard let self else { + return + } + if isSearchBar { + self.headerNode.searchBarContainer.addSubnode(subnode) + } else { + self.headerNode.searchContainer.addSubnode(subnode) } }, placeholder: nil) + if self.isSettings { + controller.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: true), transition: transition) + if let searchBarNode = controller.currentTabBarSearchNode?() as? SearchBarNode { + self.searchDisplayController?.setSearchBar(searchBarNode) + searchBarNode.activate() + } + } + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationBarHeight, transition: .immediate) } - private func deactivateSearch() { - guard let searchDisplayController = self.searchDisplayController else { + func deactivateSearch() { + guard let controller = self.controller, let searchDisplayController = self.searchDisplayController else { return } self.searchDisplayController = nil searchDisplayController.deactivate(placeholder: nil) if self.isSettings { - (self.controller?.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.3, curve: .linear)) + (self.controller?.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring)) + controller.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .animated(duration: 0.4, curve: .spring)) } let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .easeInOut) if let navigationBar = self.controller?.navigationBar { transition.updateAlpha(node: navigationBar, alpha: 1.0) } + if let (layout, navigationHeight) = self.validLayout { + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring), additive: false) + } } - private weak var mediaGalleryContextMenu: ContextController? + weak var mediaGalleryContextMenu: ContextController? func displaySharedMediaFastScrollingTooltip() { - guard let buttonNode = self.headerNode.navigationButtonContainer.rightButtonNodes[.more] else { + guard let buttonNode = self.headerNode.navigationButtonContainer.rightButtonNodes.first(where: { $0.key.key == .more })?.value else { return } guard let controller = self.controller else { @@ -11647,696 +4999,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return .dismiss(consume: false) }), in: .current) } - - private func displayGiftsContextMenu(source: ContextReferenceContentNode, gesture: ContextGesture?) { - guard let currentPaneKey = self.paneContainerNode.currentPaneKey, case .gifts = currentPaneKey else { - return - } - guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoGiftsPaneNode else { - return - } - guard let controller = self.controller else { - return - } - guard let data = self.data else { - return - } - - let giftsContext = pane.giftsContext - - var hasVisibility = false - if let channel = data.peer as? TelegramChannel, channel.hasPermission(.sendSomething) { - hasVisibility = true - } else if data.peer?.id == self.context.account.peerId { - hasVisibility = true - } - - let isCollection = giftsContext.collectionId != nil - - let strings = self.presentationData.strings - let items: Signal = giftsContext.state - |> map { state in - var hasPinnedGifts = false - for gift in state.gifts { - if gift.pinnedToTop { - hasPinnedGifts = true - break - } - } - return (state.filter, state.sorting, hasPinnedGifts || isCollection) - } - |> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in - let filterEquals = lhs.0 == rhs.0 - let sortingEquals = lhs.1 == rhs.1 - let canReorderEquals = lhs.2 == rhs.2 - return filterEquals && sortingEquals && canReorderEquals - }) - |> map { [weak pane, weak giftsContext] filter, sorting, canReorder -> ContextController.Items in - var items: [ContextMenuItem] = [] - - if hasVisibility { - if let pane, case .all = pane.currentCollection { - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_AddCollection, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/Gifts/AddCollection"), color: theme.contextMenu.primaryColor) - }, action: { [weak pane] _, f in - f(.default) - - if let pane { - pane.createCollection() - } - }))) - } else { - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_AddGifts, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/Gifts/AddGift"), color: theme.contextMenu.primaryColor) - }, action: { [weak pane] _, f in - f(.default) - - if let pane, case let .collection(id) = pane.currentCollection { - pane.addGiftsToCollection(id: id) - } - }))) - } - if canReorder { - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Reorder, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) - }, action: { [weak pane] _, f in - f(.default) - - if let pane { - pane.beginReordering() - } - }))) - } - - if let pane, case let .collection(id) = pane.currentCollection { - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_DeleteCollection, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) - }, action: { [weak pane] _, f in - f(.default) - - if let pane { - pane.deleteCollection(id: id) - } - }))) - } - } - - if let pane, case let .collection(id) = pane.currentCollection, let addressName = data.peer?.addressName, !addressName.isEmpty { - let shareAction: ContextMenuItem = .action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_ShareCollection, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.default) - self?.openShareLink(url: "https://t.me/\(addressName)/c/\(id)") - })) - if items.isEmpty { - items.append(shareAction) - } else { - items.insert(shareAction, at: 1) - } - } - - if !items.isEmpty { - items.append(.separator) - } - - if let pane, case .all = pane.currentCollection { - items.append(.action(ContextMenuActionItem(text: sorting == .date ? strings.PeerInfo_Gifts_SortByValue : strings.PeerInfo_Gifts_SortByDate, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: sorting == .date ? "Peer Info/SortValue" : "Peer Info/SortDate"), color: theme.contextMenu.primaryColor) - }, action: { [weak giftsContext] _, f in - f(.default) - - giftsContext?.updateSorting(sorting == .date ? .value : .date) - }))) - - items.append(.separator) - } - - let toggleFilter: (ProfileGiftsContext.Filters) -> Void = { [weak giftsContext] value in - var updatedFilter = filter - if updatedFilter.contains(value) { - updatedFilter.remove(value) - } else { - updatedFilter.insert(value) - } - if !updatedFilter.contains(.unlimited) && !updatedFilter.contains(.limitedUpgradable) && !updatedFilter.contains(.limitedNonUpgradable) && !updatedFilter.contains(.unique) { - updatedFilter.insert(.unlimited) - } - if !updatedFilter.contains(.displayed) && !updatedFilter.contains(.hidden) { - if value == .displayed { - updatedFilter.insert(.hidden) - } else { - updatedFilter.insert(.displayed) - } - } - giftsContext?.updateFilter(updatedFilter) - } - - let switchToFilter: (ProfileGiftsContext.Filters) -> Void = { [weak giftsContext] value in - var updatedFilter = filter - updatedFilter.remove(.unlimited) - updatedFilter.remove(.limitedUpgradable) - updatedFilter.remove(.limitedNonUpgradable) - updatedFilter.remove(.unique) - updatedFilter.insert(value) - giftsContext?.updateFilter(updatedFilter) - } - - let switchToVisiblityFilter: (ProfileGiftsContext.Filters) -> Void = { [weak giftsContext] value in - var updatedFilter = filter - updatedFilter.remove(.hidden) - updatedFilter.remove(.displayed) - updatedFilter.insert(value) - giftsContext?.updateFilter(updatedFilter) - } - - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Unlimited, icon: { theme in - return filter.contains(.unlimited) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil - }, action: { _, f in - toggleFilter(.unlimited) - }, longPressAction: { _, f in - switchToFilter(.unlimited) - }))) - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Limited, icon: { theme in - return filter.contains(.limitedNonUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil - }, action: { _, f in - toggleFilter(.limitedNonUpgradable) - }, longPressAction: { _, f in - switchToFilter(.limitedNonUpgradable) - }))) - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Upgradable, icon: { theme in - return filter.contains(.limitedUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil - }, action: { _, f in - toggleFilter(.limitedUpgradable) - }, longPressAction: { _, f in - switchToFilter(.limitedUpgradable) - }))) - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Unique, icon: { theme in - return filter.contains(.unique) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil - }, action: { _, f in - toggleFilter(.unique) - }, longPressAction: { _, f in - switchToFilter(.unique) - }))) - - if hasVisibility { - items.append(.separator) - - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Displayed, icon: { theme in - return filter.contains(.displayed) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil - }, action: { _, f in - toggleFilter(.displayed) - }, longPressAction: { _, f in - switchToVisiblityFilter(.displayed) - }))) - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Hidden, icon: { theme in - return filter.contains(.hidden) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil - }, action: { _, f in - toggleFilter(.hidden) - }, longPressAction: { _, f in - switchToVisiblityFilter(.hidden) - }))) - } - - return ContextController.Items(content: .list(items)) - } - - let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: items, gesture: gesture) - contextController.passthroughTouchEvent = { [weak self] sourceView, point in - guard let strongSelf = self else { - return .ignore - } - - let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil) - guard let localResult = strongSelf.hitTest(localPoint, with: nil) else { - return .dismiss(consume: true, result: nil) - } - - var testView: UIView? = localResult - while true { - if let testViewValue = testView { - if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton { - node.isUserInteractionEnabled = false - DispatchQueue.main.async { - node.isUserInteractionEnabled = true - } - return .dismiss(consume: false, result: nil) - } else { - testView = testViewValue.superview - } - } else { - break - } - } - - return .dismiss(consume: true, result: nil) - } - self.mediaGalleryContextMenu = contextController - controller.presentInGlobalOverlay(contextController) - } - - private func displayMediaGalleryContextMenu(source: ContextReferenceContentNode, gesture: ContextGesture?) { - let peerId = self.peerId - - var isBotPreviewOrStories = false - if let currentPaneKey = self.paneContainerNode.currentPaneKey { - if case .botPreview = currentPaneKey { - isBotPreviewOrStories = true - } else if case .stories = currentPaneKey { - isBotPreviewOrStories = true - } - } - - if isBotPreviewOrStories { - guard let controller = self.controller else { - return - } - guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoStoryPaneNode else { - return - } - - if case .botPreview = pane.scope { - guard let data = self.data, let user = data.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) else { - return - } - - var items: [ContextMenuItem] = [] - - let strings = self.presentationData.strings - - var ignoreNextActions = false - - if pane.canAddMoreBotPreviews() { - items.append(.action(ContextMenuActionItem(text: strings.BotPreviews_MenuAddPreview, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - if let self { - self.headerNode.navigationButtonContainer.performAction?(.postStory, nil, nil) - } - }))) - } - - if pane.canReorder() { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) - }, action: { [weak pane] _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - if let pane { - pane.beginReordering() - } - }))) - } - - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - if let self { - self.toggleStorySelection(ids: [], isSelected: true) - } - }))) - - if let language = pane.currentBotPreviewLanguage { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuDeleteLanguage(language.name).string, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) - }, action: { [weak pane] _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - if let pane { - pane.presentDeleteBotPreviewLanguage() - } - }))) - } - - let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - contextController.passthroughTouchEvent = { [weak self] sourceView, point in - guard let strongSelf = self else { - return .ignore - } - - let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil) - guard let localResult = strongSelf.hitTest(localPoint, with: nil) else { - return .dismiss(consume: true, result: nil) - } - - var testView: UIView? = localResult - while true { - if let testViewValue = testView { - if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton { - node.isUserInteractionEnabled = false - DispatchQueue.main.async { - node.isUserInteractionEnabled = true - } - return .dismiss(consume: false, result: nil) - } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode { - node.brieflyDisableTouchActions() - return .dismiss(consume: false, result: nil) - } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoStoryPaneNode { - node.brieflyDisableTouchActions() - return .dismiss(consume: false, result: nil) - } else { - testView = testViewValue.superview - } - } else { - break - } - } - - return .dismiss(consume: true, result: nil) - } - self.mediaGalleryContextMenu = contextController - controller.presentInGlobalOverlay(contextController) - } else if case .peer = pane.scope { - guard let data = self.data, let user = data.peer as? TelegramUser else { - return - } - let _ = user - - var items: [ContextMenuItem] = [] - - let strings = self.presentationData.strings - - var ignoreNextActions = false - - items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_MenuAddStories, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddStoryIcon"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - if let self { - self.headerNode.navigationButtonContainer.performAction?(.postStory, nil, nil) - } - }))) - - if let _ = pane.currentStoryFolder { - items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuShare, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { [weak pane] _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - if let pane { - pane.shareCurrentFolder() - } - }))) - } - - if pane.canReorder() { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) - }, action: { [weak pane] _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - if let pane { - pane.beginReordering() - } - }))) - } - - if let folder = pane.currentStoryFolder { - let _ = folder - - items.append(.action(ContextMenuActionItem(text: strings.Stories_MenuDeleteAlbum, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) - }, action: { [weak pane] _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - if let pane { - pane.presentDeleteCurrentStoryFolder() - } - }))) - } - - if let language = pane.currentBotPreviewLanguage { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuDeleteLanguage(language.name).string, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) - }, action: { [weak pane] _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - if let pane { - pane.presentDeleteBotPreviewLanguage() - } - }))) - } - - let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - contextController.passthroughTouchEvent = { [weak self] sourceView, point in - guard let strongSelf = self else { - return .ignore - } - - let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil) - guard let localResult = strongSelf.hitTest(localPoint, with: nil) else { - return .dismiss(consume: true, result: nil) - } - - var testView: UIView? = localResult - while true { - if let testViewValue = testView { - if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton { - node.isUserInteractionEnabled = false - DispatchQueue.main.async { - node.isUserInteractionEnabled = true - } - return .dismiss(consume: false, result: nil) - } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode { - node.brieflyDisableTouchActions() - return .dismiss(consume: false, result: nil) - } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoStoryPaneNode { - node.brieflyDisableTouchActions() - return .dismiss(consume: false, result: nil) - } else { - testView = testViewValue.superview - } - } else { - break - } - } - - return .dismiss(consume: true, result: nil) - } - self.mediaGalleryContextMenu = contextController - controller.presentInGlobalOverlay(contextController) - } - } else { - let _ = (self.context.engine.data.get(EngineDataMap([ - TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photo), - TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: self.chatLocation.threadId, tag: .video) - ])) - |> deliverOnMainQueue).startStandalone(next: { [weak self] messageCounts in - guard let strongSelf = self else { - return - } - - var mediaCount: [MessageTags: Int32] = [:] - for (key, count) in messageCounts { - mediaCount[key.tag] = count.flatMap(Int32.init) ?? 0 - } - - let photoCount: Int32 = mediaCount[.photo] ?? 0 - let videoCount: Int32 = mediaCount[.video] ?? 0 - - guard let controller = strongSelf.controller else { - return - } - guard let pane = strongSelf.paneContainerNode.currentPane?.node as? PeerInfoVisualMediaPaneNode else { - return - } - - var items: [ContextMenuItem] = [] - - let strings = strongSelf.presentationData.strings - - var recurseGenerateAction: ((Bool) -> ContextMenuActionItem)? - let generateAction: (Bool) -> ContextMenuActionItem = { [weak pane] isZoomIn in - let nextZoomLevel = isZoomIn ? pane?.availableZoomLevels().increment : pane?.availableZoomLevels().decrement - let canZoom: Bool = nextZoomLevel != nil - - return ContextMenuActionItem(id: isZoomIn ? 0 : 1, text: isZoomIn ? strings.SharedMedia_ZoomIn : strings.SharedMedia_ZoomOut, textColor: canZoom ? .primary : .disabled, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: isZoomIn ? "Chat/Context Menu/ZoomIn" : "Chat/Context Menu/ZoomOut"), color: canZoom ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)) - }, action: canZoom ? { action in - guard let pane = pane, let zoomLevel = isZoomIn ? pane.availableZoomLevels().increment : pane.availableZoomLevels().decrement else { - return - } - pane.updateZoomLevel(level: zoomLevel) - if let recurseGenerateAction = recurseGenerateAction { - action.updateAction(0, recurseGenerateAction(true)) - action.updateAction(1, recurseGenerateAction(false)) - } - } : nil) - } - recurseGenerateAction = { isZoomIn in - return generateAction(isZoomIn) - } - - items.append(.action(generateAction(true))) - items.append(.action(generateAction(false))) - - var ignoreNextActions = false - if strongSelf.chatLocation.threadId == nil { - items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowCalendar, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor) - }, action: { _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - self?.openMediaCalendar() - }))) - } - - if photoCount != 0 && videoCount != 0 { - items.append(.separator) - - let showPhotos: Bool - switch pane.contentType { - case .photo, .photoOrVideo: - showPhotos = true - default: - showPhotos = false - } - let showVideos: Bool - switch pane.contentType { - case .video, .photoOrVideo: - showVideos = true - default: - showVideos = false - } - - items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowPhotos, icon: { theme in - if !showPhotos { - return nil - } - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) - }, action: { [weak pane] _, a in - a(.default) - - guard let pane = pane else { - return - } - let updatedContentType: PeerInfoVisualMediaPaneNode.ContentType - switch pane.contentType { - case .photoOrVideo: - updatedContentType = .video - case .photo: - updatedContentType = .photo - case .video: - updatedContentType = .photoOrVideo - default: - updatedContentType = pane.contentType - } - pane.updateContentType(contentType: updatedContentType) - }))) - items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowVideos, icon: { theme in - if !showVideos { - return nil - } - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) - }, action: { [weak pane] _, a in - a(.default) - - guard let pane = pane else { - return - } - let updatedContentType: PeerInfoVisualMediaPaneNode.ContentType - switch pane.contentType { - case .photoOrVideo: - updatedContentType = .photo - case .photo: - updatedContentType = .photoOrVideo - case .video: - updatedContentType = .video - default: - updatedContentType = pane.contentType - } - pane.updateContentType(contentType: updatedContentType) - }))) - } - - let contextController = ContextController(presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - contextController.passthroughTouchEvent = { sourceView, point in - guard let strongSelf = self else { - return .ignore - } - - let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil) - guard let localResult = strongSelf.hitTest(localPoint, with: nil) else { - return .dismiss(consume: true, result: nil) - } - - var testView: UIView? = localResult - while true { - if let testViewValue = testView { - if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton { - node.isUserInteractionEnabled = false - DispatchQueue.main.async { - node.isUserInteractionEnabled = true - } - return .dismiss(consume: false, result: nil) - } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode { - node.brieflyDisableTouchActions() - return .dismiss(consume: false, result: nil) - } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoStoryPaneNode { - node.brieflyDisableTouchActions() - return .dismiss(consume: false, result: nil) - } else { - testView = testViewValue.superview - } - } else { - break - } - } - - return .dismiss(consume: true, result: nil) - } - strongSelf.mediaGalleryContextMenu = contextController - controller.presentInGlobalOverlay(contextController) - }) - } - } - - private func openMediaCalendar() { + func openMediaCalendar() { var initialTimestamp = Int32(Date().timeIntervalSince1970) guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoVisualMediaPaneNode, let timestamp = pane.currentTopTimestamp(), let calendarSource = pane.calendarSource else { @@ -12514,7 +5178,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData - self.backgroundColor = self.presentationData.theme.list.blocksBackgroundColor + self.updateBackgroundColor() self.updateNavigationExpansionPresentation(isExpanded: self.headerNode.isAvatarExpanded, animated: false) @@ -12586,8 +5250,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } let headerInset = sectionInset - let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, screenData: self.data, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: transition.isAnimated && self.headerNode.navigationTransition == nil) - let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: headerHeight)) + let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, screenData: self.data, isSearching: self.searchDisplayController != nil, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: transition.isAnimated && self.headerNode.navigationTransition == nil) + + let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: layout.size.height)) if additive { transition.updateFrameAdditive(node: self.headerNode, frame: headerFrame) } else { @@ -12623,7 +5288,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } else { sectionNode = PeerInfoScreenItemSectionContainerNode() self.regularSections[sectionId] = sectionNode - self.scrollNode.addSubnode(sectionNode) + self.scrollNode.insertSubnode(sectionNode, belowSubnode: self.paneContainerNode) wasAdded = true } @@ -12695,7 +5360,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro wasAdded = true sectionNode = PeerInfoScreenItemSectionContainerNode() self.editingSections[sectionId] = sectionNode - self.scrollNode.addSubnode(sectionNode) + self.scrollNode.insertSubnode(sectionNode, belowSubnode: self.paneContainerNode) } let sectionWidth = layout.size.width - insets.left - insets.right @@ -12737,12 +5402,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro restoreContentOffset = self.scrollNode.view.contentOffset } + if !self.isMediaOnly { + contentHeight -= 18.0 + } let paneContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: paneContainerSize) if self.state.isEditing || (self.data?.availablePanes ?? []).isEmpty { transition.updateAlpha(node: self.paneContainerNode, alpha: 0.0) + ComponentTransition(transition).setAlpha(view: self.paneContainerNode.headerContainer, alpha: 0.0) } else { contentHeight += layout.size.height - navigationHeight transition.updateAlpha(node: self.paneContainerNode, alpha: 1.0) + ComponentTransition(transition).setAlpha(view: self.paneContainerNode.headerContainer, alpha: 1.0) } if let selectedMessageIds = self.state.selectedMessageIds { @@ -12909,6 +5579,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } else { transition.updateFrame(node: self.paneContainerNode, frame: paneContainerFrame) } + if additive { + transition.updateFrameAdditive(view: self.paneContainerNode.headerContainer, frame: paneContainerFrame) + } else { + transition.updateFrame(view: self.paneContainerNode.headerContainer, frame: paneContainerFrame) + } self.ignoreScrolling = false self.updateNavigation(transition: transition, additive: additive, animateHeader: self.controller?.didAppear ?? false) @@ -12929,6 +5604,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro |> take(1)) } } + + private func updateBackgroundColor() { + let color: UIColor + if self.paneContainerNode.currentPaneKey == .gifts { + color = self.presentationData.theme.list.blocksBackgroundColor + } else { + color = self.presentationData.theme.list.blocksBackgroundColor.mixedWith(self.presentationData.theme.list.plainBackgroundColor, alpha: self.effectiveAreaExpansionFraction) + } + self.backgroundColor = color + } private var hasQrButton = false fileprivate func updateNavigation(transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) { @@ -12971,7 +5656,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } let headerInset = sectionInset - let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, screenData: self.data, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: animateHeader && self.headerNode.navigationTransition == nil) + let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, screenData: self.data, isSearching: self.searchDisplayController != nil, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: animateHeader && self.headerNode.navigationTransition == nil) } let paneAreaExpansionDistance: CGFloat = 32.0 @@ -12979,15 +5664,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if self.state.isEditing { effectiveAreaExpansionFraction = 0.0 } else if self.isSettings { - var paneAreaExpansionDelta = (self.headerNode.frame.maxY - navigationHeight) - self.scrollNode.view.contentOffset.y - paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance)) - effectiveAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance + effectiveAreaExpansionFraction = 0.0 } else { var paneAreaExpansionDelta = (self.paneContainerNode.frame.minY - navigationHeight) - self.scrollNode.view.contentOffset.y paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance)) effectiveAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance } + self.effectiveAreaExpansionFraction = effectiveAreaExpansionFraction + + self.updateBackgroundColor() + let visibleHeight = self.scrollNode.view.contentOffset.y + self.scrollNode.view.bounds.height - self.paneContainerNode.frame.minY var bottomInset = layout.intrinsicInsets.bottom @@ -13000,11 +5687,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro disableTabSwitching = true } - let navigationBarHeight: CGFloat = !self.isSettings && layout.isModalOverlay ? 56.0 : 44.0 - self.paneContainerNode.update(size: self.paneContainerNode.bounds.size, sideInset: layout.safeInsets.left, bottomInset: bottomInset, deviceMetrics: layout.deviceMetrics, visibleHeight: visibleHeight, expansionFraction: effectiveAreaExpansionFraction, presentationData: self.presentationData, data: self.data, areTabsHidden: self.headerNode.customNavigationContentNode != nil, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: transition) + let navigationBarHeight: CGFloat = !self.isSettings && layout.isModalOverlay ? 68.0 : 60.0 + let paneContainerTopInset = navigationBarHeight + (layout.statusBarHeight ?? 0.0) + self.paneContainerNode.update(size: self.paneContainerNode.bounds.size, sideInset: layout.safeInsets.left, topInset: paneContainerTopInset, bottomInset: bottomInset, deviceMetrics: layout.deviceMetrics, visibleHeight: visibleHeight, expansionFraction: effectiveAreaExpansionFraction, presentationData: self.presentationData, data: self.data, areTabsHidden: self.headerNode.customNavigationContentNode != nil, disableTabSwitching: disableTabSwitching, navigationHeight: navigationHeight, transition: transition) transition.updateFrame(node: self.headerNode.navigationButtonContainer, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.statusBarHeight ?? 0.0), size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight))) - + var searchBarContainerY: CGFloat = layout.statusBarHeight ?? 0.0 + searchBarContainerY += floor((navigationBarHeight - 44.0) * 0.5) + 2.0 + transition.updateFrame(node: self.headerNode.searchBarContainer, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: searchBarContainerY), size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight))) + transition.updateFrame(node: self.headerNode.searchContainer, frame: CGRect(origin: CGPoint(), size: layout.size)) var leftNavigationButtons: [PeerInfoHeaderNavigationButtonSpec] = [] var rightNavigationButtons: [PeerInfoHeaderNavigationButtonSpec] = [] @@ -13015,7 +5706,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if self.isSettings { leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) - rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) } else if self.isMyProfile { rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) } else if peerInfoCanEdit(peer: self.data?.peer, chatLocation: self.chatLocation, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) { @@ -13078,6 +5768,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro switch previousItem { case .close, .item: leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .back, isForExpandedView: false)) + leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .back, isForExpandedView: true)) } } self.headerNode.navigationButtonContainer.update(size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight), presentationData: self.presentationData, leftButtons: leftNavigationButtons, rightButtons: rightNavigationButtons, expandFraction: effectiveAreaExpansionFraction, shouldAnimateIn: animateHeader, transition: transition) @@ -13085,7 +5776,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + func cancelContextGestures(view: UIView) { + if let gestureRecognizers = view.gestureRecognizers { + for gesture in gestureRecognizers { + if let gesture = gesture as? ContextGesture { + gesture.cancel() + } + } + } + for subview in view.subviews { + cancelContextGestures(view: subview) + } + } + cancelContextGestures(view: scrollView) + self.canAddVelocity = true self.canOpenAvatarByDragging = self.headerNode.isAvatarExpanded self.paneContainerNode.currentPane?.node.cancelPreviewGestures() @@ -13101,6 +5806,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard !self.ignoreScrolling else { return } + + self.headerNode.headerEdgeEffectContainer.center = CGPoint(x: 0.0, y: scrollView.contentOffset.y) if !self.state.isEditing { if self.canAddVelocity { @@ -13454,7 +6161,7 @@ public enum PeerInfoSwitchToGiftsTarget { public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortcutResponder { let context: AccountContext - fileprivate let updatedPresentationData: (initial: PresentationData, signal: Signal)? + let updatedPresentationData: (initial: PresentationData, signal: Signal)? public let peerId: PeerId private let avatarInitiallyExpanded: Bool private let isOpenedFromChat: Bool @@ -13466,8 +6173,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc private let hintGroupInCommon: PeerId? private weak var requestsContext: PeerInvitationImportersContext? private weak var profileGiftsContext: ProfileGiftsContext? - fileprivate let starsContext: StarsContext? - fileprivate let tonContext: StarsContext? + let starsContext: StarsContext? + let tonContext: StarsContext? private let switchToRecommendedChannels: Bool private let switchToGiftsTarget: PeerInfoSwitchToGiftsTarget? private let switchToGroupsInCommon: Bool @@ -13490,6 +6197,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc private var tabBarItemDisposable: Disposable? + var avatarPickerHolder: Any? + var controllerNode: PeerInfoScreenNode { return self.displayNode as! PeerInfoScreenNode } @@ -13604,6 +6313,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc let baseNavigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) super.init(navigationBarPresentationData: NavigationBarPresentationData( theme: NavigationBarTheme( + overallDarkAppearance: true, buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, @@ -13615,6 +6325,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc badgeTextColor: baseNavigationBarPresentationData.theme.badgeTextColor ), strings: baseNavigationBarPresentationData.strings)) + self._hasGlassStyle = true + self.navigationBar?.enableAutomaticBackButton = false if isSettings || isMyProfile { @@ -13786,46 +6498,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc } if self.chatLocation.peerId != nil { - /*self.navigationBar?.shouldTransitionInline = { [weak self] in - guard let strongSelf = self else { - return false - } - if strongSelf.navigationItem.leftBarButtonItem != nil { - return false - } - if strongSelf.controllerNode.scrollNode.view.contentOffset.y > .ulpOfOne { - return false - } - if strongSelf.controllerNode.headerNode.isAvatarExpanded { - return false - } - return false - }*/ - self.navigationBar?.makeCustomTransitionNode = { [weak self] other, isInteractive in - guard let strongSelf = self else { - return nil - } - if strongSelf.navigationItem.leftBarButtonItem != nil { - return nil - } - if other.item?.leftBarButtonItem != nil { - return nil - } - if strongSelf.controllerNode.scrollNode.view.contentOffset.y > .ulpOfOne { - return nil - } - if strongSelf.controllerNode.initialExpandPanes { - return nil - } - if isInteractive && strongSelf.controllerNode.headerNode.isAvatarExpanded { - return nil - } - if let allowsCustomTransition = other.allowsCustomTransition, !allowsCustomTransition() { - return nil - } - if let tag = other.userInfo as? PeerInfoNavigationSourceTag, (tag.peerId == peerId || tag.threadId == peerId.toInt64()) { - return PeerInfoNavigationTransitionNode(screenNode: strongSelf.controllerNode, presentationData: strongSelf.presentationData, headerNode: strongSelf.controllerNode.headerNode) - } + self.navigationBar?.makeCustomTransitionNode = { _, _ in return nil } } @@ -13923,6 +6596,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc } self._readyProxy.set(.single(true)) }) + + self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .immediate) } required init(coder aDecoder: NSCoder) { @@ -14171,9 +6846,9 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc chatNavigationStack = summary.peerNavigationItems.filter({ $0 != ChatNavigationStackItem(peerId: self.peerId, threadId: self.chatLocation.threadId) }) } - if !chatNavigationStack.isEmpty { - self.navigationBar?.backButtonNode.isGestureEnabled = true - self.navigationBar?.backButtonNode.activated = { [weak self] gesture, _ in + if !chatNavigationStack.isEmpty, let backButtonNode = self.navigationBar?.backButtonNode as? ContextControllerSourceNode { + backButtonNode.isGestureEnabled = true + backButtonNode.activated = { [weak self] gesture, _ in guard let strongSelf = self, let backButtonNode = strongSelf.navigationBar?.backButtonNode, let navigationController = strongSelf.navigationController as? NavigationController else { gesture.cancel() return @@ -14294,6 +6969,14 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc self.controllerNode.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) } + override public func tabBarActivateSearch() { + self.controllerNode.activateSearch() + } + + override public func tabBarDeactivateSearch() { + self.controllerNode.deactivateSearch() + } + public static func openSavedMessagesMoreMenu(context: AccountContext, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> deliverOnMainQueue).startStandalone(next: { peer in @@ -14383,7 +7066,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc } } -private final class SettingsTabBarContextReferenceContentSource: ContextReferenceContentSource { +final class SettingsTabBarContextReferenceContentSource: ContextReferenceContentSource { let keepInPlace: Bool = true private let controller: ViewController @@ -14403,7 +7086,7 @@ private final class SettingsTabBarContextReferenceContentSource: ContextReferenc } } -private func getUserPeer(engine: TelegramEngine, peerId: EnginePeer.Id) -> Signal { +func getUserPeer(engine: TelegramEngine, peerId: EnginePeer.Id) -> Signal { return engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> mapToSignal { peer -> Signal in guard let peer = peer else { @@ -14417,349 +7100,7 @@ private func getUserPeer(engine: TelegramEngine, peerId: EnginePeer.Id) -> Signa } } -private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavigationTransitionNode { - private let screenNode: PeerInfoScreenNode - private let presentationData: PresentationData - - private var topNavigationBar: NavigationBar? - private var bottomNavigationBar: NavigationBar? - private var reverseFraction: Bool = false - - private let headerNode: PeerInfoHeaderNode - - private var previousBackButtonArrow: UIView? - private var previousBackButton: UIView? - private var currentBackButtonArrow: ASDisplayNode? - private var previousBackButtonBadge: ASDisplayNode? - private var currentBackButton: ASDisplayNode? - - private var previousRightButton: CALayer? - - private var previousContentNode: ASDisplayNode? - private var previousContentNodeFrame: CGRect? - private var previousContentNodeAlpha: CGFloat = 1.0 - - private var previousSecondaryContentNode: ASDisplayNode? - private var previousSecondaryContentNodeFrame: CGRect? - private var previousSecondaryContentNodeAlpha: CGFloat = 1.0 - - private var previousTitleNode: (ASDisplayNode, PortalView)? - private var previousStatusNode: (ASDisplayNode, ASDisplayNode)? - - private var previousAvatarView: UIView? - - private var didSetup: Bool = false - - init(screenNode: PeerInfoScreenNode, presentationData: PresentationData, headerNode: PeerInfoHeaderNode) { - self.screenNode = screenNode - self.presentationData = presentationData - self.headerNode = headerNode - - super.init() - - self.addSubnode(headerNode) - } - - func setup(topNavigationBar: NavigationBar, bottomNavigationBar: NavigationBar) { - if let _ = bottomNavigationBar.userInfo as? PeerInfoNavigationSourceTag { - self.topNavigationBar = topNavigationBar - self.bottomNavigationBar = bottomNavigationBar - } else { - self.topNavigationBar = bottomNavigationBar - self.bottomNavigationBar = topNavigationBar - self.reverseFraction = true - } - - topNavigationBar.isHidden = true - bottomNavigationBar.isHidden = true - - if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar { - self.addSubnode(bottomNavigationBar.additionalContentNode) - - if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View { - if let previousBackButtonArrow = headerView.makeTransitionBackArrowView(accentColor: self.presentationData.theme.rootController.navigationBar.accentTextColor) { - self.previousBackButtonArrow = previousBackButtonArrow - self.view.addSubview(previousBackButtonArrow) - } - if let previousBackButton = headerView.makeTransitionBackButtonView(accentColor: self.presentationData.theme.rootController.navigationBar.accentTextColor) { - self.previousBackButton = previousBackButton - self.view.addSubview(previousBackButton) - } - } else { - if let previousBackButtonArrow = bottomNavigationBar.makeTransitionBackArrowView(accentColor: self.presentationData.theme.rootController.navigationBar.accentTextColor) { - self.previousBackButtonArrow = previousBackButtonArrow - self.view.addSubview(previousBackButtonArrow) - } - if let previousBackButton = bottomNavigationBar.makeTransitionBackButtonView(accentColor: self.presentationData.theme.rootController.navigationBar.accentTextColor) { - self.previousBackButton = previousBackButton - self.view.addSubview(previousBackButton) - } - } - - if let currentBackButtonArrow = topNavigationBar.makeTransitionBackArrowNode(accentColor: .white) { - self.currentBackButtonArrow = currentBackButtonArrow - //self.addSubnode(currentBackButtonArrow) - } - - if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View { - let _ = headerView - } else { - if let previousBackButtonBadge = bottomNavigationBar.makeTransitionBadgeNode() { - self.previousBackButtonBadge = previousBackButtonBadge - self.addSubnode(previousBackButtonBadge) - } - } - - if let currentBackButton = topNavigationBar.makeTransitionBackButtonNode(accentColor: .white) { - self.currentBackButton = currentBackButton - //self.addSubnode(currentBackButton) - } - - if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View { - if let previousRightButton = headerView.rightButtonView?.layer.snapshotContentTree() { - self.previousRightButton = previousRightButton - self.view.layer.addSublayer(previousRightButton) - } - } else { - if let avatarNavigationNode = bottomNavigationBar.rightButtonNode.singleCustomNode as? ChatAvatarNavigationNode, let previousAvatarView = avatarNavigationNode.view.snapshotContentTree() { - self.previousAvatarView = previousAvatarView - self.view.addSubview(previousAvatarView) - } else if let previousRightButton = bottomNavigationBar.rightButtonNode.view.layer.snapshotContentTree() { - self.previousRightButton = previousRightButton - self.view.layer.addSublayer(previousRightButton) - } - } - - if let contentNode = bottomNavigationBar.contentNode { - self.previousContentNode = contentNode - self.previousContentNodeFrame = contentNode.view.convert(contentNode.view.bounds, to: bottomNavigationBar.view) - self.previousContentNodeAlpha = contentNode.alpha - self.addSubnode(contentNode) - } - - if let secondaryContentNode = bottomNavigationBar.secondaryContentNode { - self.previousSecondaryContentNode = secondaryContentNode - self.previousSecondaryContentNodeFrame = secondaryContentNode.view.convert(secondaryContentNode.view.bounds, to: bottomNavigationBar.view) - self.previousSecondaryContentNodeAlpha = secondaryContentNode.alpha - self.addSubnode(secondaryContentNode) - } - - var previousTitleView: UIView? - if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View { - if let componentView = headerView.titleContentView as? ChatTitleComponent.View { - previousTitleView = componentView.contentView - } - } else { - previousTitleView = bottomNavigationBar.titleView - } - - if let previousTitleView = previousTitleView as? ChatTitleView, let previousTitleNode = PortalView(matchPosition: false) { - previousTitleNode.view.frame = previousTitleView.titleContainerView.frame - previousTitleView.titleContainerView.addPortal(view: previousTitleNode) - let previousTitleContainerNode = ASDisplayNode() - previousTitleContainerNode.view.addSubview(previousTitleNode.view) - previousTitleNode.view.frame = previousTitleNode.view.frame.offsetBy(dx: -previousTitleNode.view.frame.width / 2.0, dy: -previousTitleNode.view.frame.height / 2.0) - self.previousTitleNode = (previousTitleContainerNode, previousTitleNode) - self.addSubnode(previousTitleContainerNode) - - let previousStatusNode = previousTitleView.activityNode.makeCopy() - let previousStatusContainerNode = ASDisplayNode() - previousStatusContainerNode.addSubnode(previousStatusNode) - previousStatusNode.frame = previousStatusNode.frame.offsetBy(dx: -previousStatusNode.frame.width / 2.0, dy: -previousStatusNode.frame.height / 2.0) - self.previousStatusNode = (previousStatusContainerNode, previousStatusNode) - self.addSubnode(previousStatusContainerNode) - } - } - } - - func update(containerSize: CGSize, fraction: CGFloat, transition: ContainedViewLayoutTransition) { - guard let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar else { - return - } - - let fraction = self.reverseFraction ? (1.0 - fraction) : fraction - - if let previousBackButtonArrow = self.previousBackButtonArrow { - if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View { - if let backArrowView = headerView.backArrowView { - let previousBackButtonArrowFrame = backArrowView.convert(backArrowView.bounds, to: bottomNavigationBar.view) - previousBackButtonArrow.frame = previousBackButtonArrowFrame - transition.updateAlpha(layer: previousBackButtonArrow.layer, alpha: fraction) - } - } else { - let previousBackButtonArrowFrame = bottomNavigationBar.backButtonArrow.view.convert(bottomNavigationBar.backButtonArrow.view.bounds, to: bottomNavigationBar.view) - previousBackButtonArrow.frame = previousBackButtonArrowFrame - transition.updateAlpha(layer: previousBackButtonArrow.layer, alpha: fraction) - } - } - - if let previousBackButton = self.previousBackButton { - if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View { - if let backButtonTitleView = headerView.backButtonTitleView { - let previousBackButtonFrame = backButtonTitleView.convert(backButtonTitleView.bounds, to: bottomNavigationBar.view) - previousBackButton.frame = previousBackButtonFrame - transition.updateAlpha(layer: previousBackButton.layer, alpha: fraction) - } - } else { - let previousBackButtonFrame = bottomNavigationBar.backButtonNode.view.convert(bottomNavigationBar.backButtonNode.view.bounds, to: bottomNavigationBar.view) - previousBackButton.frame = previousBackButtonFrame - transition.updateAlpha(layer: previousBackButton.layer, alpha: fraction) - } - } - - if let previousRightButton = self.previousRightButton { - if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View { - if let rightButtonView = headerView.rightButtonView { - let previousRightButtonFrame = rightButtonView.convert(rightButtonView.bounds, to: bottomNavigationBar.view) - previousRightButton.frame = previousRightButtonFrame - transition.updateAlpha(layer: previousRightButton, alpha: fraction) - } - } else { - let previousRightButtonFrame = bottomNavigationBar.rightButtonNode.view.convert(bottomNavigationBar.rightButtonNode.view.bounds, to: bottomNavigationBar.view) - previousRightButton.frame = previousRightButtonFrame - transition.updateAlpha(layer: previousRightButton, alpha: fraction) - } - } - - if let currentBackButtonArrow = self.currentBackButtonArrow { - let currentBackButtonArrowFrame = topNavigationBar.backButtonArrow.view.convert(topNavigationBar.backButtonArrow.view.bounds, to: topNavigationBar.view) - currentBackButtonArrow.frame = currentBackButtonArrowFrame - - transition.updateAlpha(node: currentBackButtonArrow, alpha: 1.0 - fraction) - if let previousBackButtonArrow = self.previousBackButtonArrow { - transition.updateAlpha(layer: previousBackButtonArrow.layer, alpha: fraction) - } - } - - if let previousBackButtonBadge = self.previousBackButtonBadge { - let previousBackButtonBadgeFrame = bottomNavigationBar.badgeNode.view.convert(bottomNavigationBar.badgeNode.view.bounds, to: bottomNavigationBar.view) - previousBackButtonBadge.frame = previousBackButtonBadgeFrame - - transition.updateAlpha(node: previousBackButtonBadge, alpha: fraction) - } - - if let currentBackButton = self.currentBackButton { - transition.updateAlpha(node: currentBackButton, alpha: (1.0 - fraction)) - } - - var previousTitleView: UIView? - if let headerView = bottomNavigationBar.customHeaderContentView as? ChatListHeaderComponent.View { - if let componentView = headerView.titleContentView as? ChatTitleComponent.View { - previousTitleView = componentView.contentView - } - } else { - previousTitleView = bottomNavigationBar.titleView - } - - if let previousTitleView = previousTitleView as? ChatTitleView, let (previousTitleContainerNode, previousTitleNode) = self.previousTitleNode, let (previousStatusContainerNode, previousStatusNode) = self.previousStatusNode { - let previousTitleFrame = previousTitleView.titleContainerView.convert(previousTitleView.titleContainerView.bounds, to: bottomNavigationBar.view) - let previousStatusFrame = previousTitleView.activityNode.view.convert(previousTitleView.activityNode.bounds, to: bottomNavigationBar.view) - - self.headerNode.navigationTransition = PeerInfoHeaderNavigationTransition(sourceNavigationBar: bottomNavigationBar, sourceTitleView: previousTitleView, sourceTitleFrame: previousTitleFrame, sourceSubtitleFrame: previousStatusFrame, previousAvatarView: self.previousAvatarView, fraction: fraction) - var topHeight = topNavigationBar.backgroundNode.bounds.height - - if let iconView = previousTitleView.titleCredibilityIconView.componentView { - transition.updateFrame(view: iconView, frame: iconView.bounds.offsetBy(dx: (1.0 - fraction) * 8.0, dy: 0.0)) - } - - if let (layout, _) = self.screenNode.validLayout { - let sectionInset: CGFloat - if layout.size.width >= 375.0 { - sectionInset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) - } else { - sectionInset = 0.0 - } - let headerInset = sectionInset - - topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.savedMessagesPeer ?? self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadData: self.screenNode.data?.threadData, peerNotificationSettings: self.screenNode.data?.peerNotificationSettings, threadNotificationSettings: self.screenNode.data?.threadNotificationSettings, globalNotificationSettings: self.screenNode.data?.globalNotificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, profileGiftsContext: self.screenNode.data?.profileGiftsContext, screenData: self.screenNode.data, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: false, animateHeader: false) - } - - let titleScale = (fraction * previousTitleNode.view.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.view.bounds.height - let subtitleScale = max(0.01, min(10.0, (fraction * previousStatusNode.bounds.height + (1.0 - fraction) * self.headerNode.subtitleNodeRawContainer.bounds.height) / previousStatusNode.bounds.height)) - - transition.updateFrame(node: previousTitleContainerNode, frame: CGRect(origin: self.headerNode.titleNodeRawContainer.frame.center, size: CGSize())) - transition.updateFrame(view: previousTitleNode.view, frame: CGRect(origin: CGPoint(x: -previousTitleFrame.width / 2.0, y: -previousTitleFrame.height / 2.0), size: previousTitleFrame.size)) - transition.updateFrame(node: previousStatusContainerNode, frame: CGRect(origin: self.headerNode.subtitleNodeRawContainer.frame.center, size: CGSize())) - transition.updateFrame(node: previousStatusNode, frame: CGRect(origin: CGPoint(x: -previousStatusFrame.size.width / 2.0, y: -previousStatusFrame.size.height / 2.0), size: previousStatusFrame.size)) - - transition.updateSublayerTransformScale(node: previousTitleContainerNode, scale: titleScale) - transition.updateSublayerTransformScale(node: previousStatusContainerNode, scale: subtitleScale) - - transition.updateAlpha(node: self.headerNode.titleNode, alpha: (1.0 - fraction)) - transition.updateAlpha(layer: previousTitleNode.view.layer, alpha: fraction) - transition.updateAlpha(node: self.headerNode.subtitleNode, alpha: (1.0 - fraction)) - transition.updateAlpha(node: previousStatusNode, alpha: fraction) - - transition.updateAlpha(node: self.headerNode.navigationButtonContainer, alpha: (1.0 - fraction)) - - if case .animated = transition, (bottomNavigationBar.additionalContentNode.alpha.isZero || bottomNavigationBar.additionalContentNode.alpha == 1.0) { - bottomNavigationBar.additionalContentNode.alpha = fraction - if fraction.isZero { - bottomNavigationBar.additionalContentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) - } else { - transition.updateAlpha(node: bottomNavigationBar.additionalContentNode, alpha: fraction) - } - } else { - transition.updateAlpha(node: bottomNavigationBar.additionalContentNode, alpha: fraction) - } - - let bottomHeight = bottomNavigationBar.backgroundNode.bounds.height - - transition.updateSublayerTransformOffset(layer: bottomNavigationBar.additionalContentNode.layer, offset: CGPoint(x: 0.0, y: (1.0 - fraction) * (topHeight - bottomHeight))) - - if let previousContentNode = self.previousContentNode, let previousContentNodeFrame = self.previousContentNodeFrame { - var updatedPreviousContentNodeFrame = bottomNavigationBar.view.convert(previousContentNodeFrame, to: bottomNavigationBar.view) - updatedPreviousContentNodeFrame.origin.y += (1.0 - fraction) * (topHeight - bottomHeight) - transition.updateFrame(node: previousContentNode, frame: updatedPreviousContentNodeFrame) - transition.updateAlpha(node: previousContentNode, alpha: fraction) - } - - if let previousSecondaryContentNode = self.previousSecondaryContentNode, let previousSecondaryContentNodeFrame = self.previousSecondaryContentNodeFrame { - var updatedPreviousSecondaryContentNodeFrame = bottomNavigationBar.view.convert(previousSecondaryContentNodeFrame, to: bottomNavigationBar.view) - updatedPreviousSecondaryContentNodeFrame.origin.y += (1.0 - fraction) * (topHeight - bottomHeight) - transition.updateFrame(node: previousSecondaryContentNode, frame: updatedPreviousSecondaryContentNodeFrame) - transition.updateAlpha(node: previousSecondaryContentNode, alpha: fraction) - } - } - } - - func restore() { - guard let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar else { - return - } - - topNavigationBar.additionalContentNode.alpha = 1.0 - ContainedViewLayoutTransition.immediate.updateSublayerTransformOffset(layer: topNavigationBar.additionalContentNode.layer, offset: CGPoint()) - topNavigationBar.reattachAdditionalContentNode() - - bottomNavigationBar.additionalContentNode.alpha = 1.0 - ContainedViewLayoutTransition.immediate.updateSublayerTransformOffset(layer: bottomNavigationBar.additionalContentNode.layer, offset: CGPoint()) - bottomNavigationBar.reattachAdditionalContentNode() - - topNavigationBar.isHidden = false - bottomNavigationBar.isHidden = false - self.headerNode.navigationTransition = nil - self.screenNode.insertSubnode(self.headerNode, aboveSubnode: self.screenNode.scrollNode) - - if let previousContentNode = self.previousContentNode, let previousContentNodeFrame = self.previousContentNodeFrame { - previousContentNode.frame = previousContentNodeFrame - previousContentNode.alpha = self.previousContentNodeAlpha - bottomNavigationBar.insertSubnode(previousContentNode, belowSubnode: bottomNavigationBar.stripeNode) - } - - if let previousSecondaryContentNode = self.previousSecondaryContentNode, let previousSecondaryContentNodeFrame = self.previousSecondaryContentNodeFrame { - previousSecondaryContentNode.frame = previousSecondaryContentNodeFrame - previousSecondaryContentNode.alpha = self.previousSecondaryContentNodeAlpha - bottomNavigationBar.clippingNode.addSubnode(previousSecondaryContentNode) - } - - if let previousTitleView = bottomNavigationBar.titleView as? ChatTitleView, let iconView = previousTitleView.titleCredibilityIconView.componentView { - iconView.frame = iconView.bounds - } - } -} - -private final class ContextControllerContentSourceImpl: ContextControllerContentSource { +final class ContextControllerContentSourceImpl: ContextControllerContentSource { let controller: ViewController weak var sourceNode: ASDisplayNode? let sourceRect: CGRect @@ -14793,7 +7134,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } } -private final class MessageContextExtractedContentSource: ContextExtractedContentSource { +final class MessageContextExtractedContentSource: ContextExtractedContentSource { let keepInPlace: Bool = false let ignoreContentTouches: Bool = true let blurBackground: Bool = true @@ -14813,7 +7154,7 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten } } -private final class PeerInfoContextExtractedContentSource: ContextExtractedContentSource { +final class PeerInfoContextExtractedContentSource: ContextExtractedContentSource { var keepInPlace: Bool = false let ignoreContentTouches: Bool = true let blurBackground: Bool = true @@ -14835,7 +7176,7 @@ private final class PeerInfoContextExtractedContentSource: ContextExtractedConte } } -private final class PeerInfoContextReferenceContentSource: ContextReferenceContentSource { +final class PeerInfoContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController private let sourceNode: ContextReferenceContentNode @@ -14849,214 +7190,6 @@ private final class PeerInfoContextReferenceContentSource: ContextReferenceConte } } -public func presentAddMembersImpl(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) { - let members: Promise<[PeerId]> = Promise() - if groupPeer.id.namespace == Namespaces.Peer.CloudChannel { - /*var membersDisposable: Disposable? - let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { listState in - members.set(.single(listState.list.map {$0.peer.id})) - membersDisposable?.dispose() - }) - membersDisposable = disposable*/ - members.set(.single([])) - } else { - members.set(.single([])) - } - - let _ = (members.get() - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak parentController] recentIds in - var createInviteLinkImpl: (() -> Void)? - var confirmationImpl: ((PeerId) -> Signal)? - let _ = confirmationImpl - var options: [ContactListAdditionalOption] = [] - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - - var canCreateInviteLink = false - if let group = groupPeer as? TelegramGroup { - switch group.role { - case .creator: - canCreateInviteLink = true - case let .admin(rights, _): - canCreateInviteLink = rights.rights.contains(.canInviteUsers) - default: - break - } - } else if let channel = groupPeer as? TelegramChannel, (channel.addressName?.isEmpty ?? true) { - if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) { - canCreateInviteLink = true - } - } - - if canCreateInviteLink { - options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: { - createInviteLinkImpl?() - }, clearHighlightAutomatically: true)) - } - - let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: .single(options), filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true, isGroupInvitation: true)) - contactsController.navigationPresentation = .modal - - confirmationImpl = { [weak contactsController] peerId in - return context.account.postbox.loadedPeerWithId(peerId) - |> deliverOnMainQueue - |> mapToSignal { peer in - let result = ValuePromise() - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - if let contactsController = contactsController { - let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: { - result.set(false) - }), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { - result.set(true) - }) - ]) - contactsController.present(alertController, in: .window(.root)) - } - - return result.get() - } - } - - let addMembers: ([ContactListPeerId]) -> Signal<[(PeerId, AddChannelMemberError)], NoError> = { members -> Signal<[(PeerId, AddChannelMemberError)], NoError> in - let memberIds = members.compactMap { contact -> PeerId? in - switch contact { - case let .peer(peerId): - return peerId - default: - return nil - } - } - return context.account.postbox.multiplePeersView(memberIds) - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { view -> Signal<[(PeerId, AddChannelMemberError)], NoError> in - if groupPeer.id.namespace == Namespaces.Peer.CloudChannel { - if memberIds.count == 1 { - return context.peerChannelMemberCategoriesContextsManager.addMember(engine: context.engine, peerId: groupPeer.id, memberId: memberIds[0]) - |> map { _ -> [(PeerId, AddChannelMemberError)] in - } - |> then(Signal<[(PeerId, AddChannelMemberError)], AddChannelMemberError>.single([])) - |> `catch` { error -> Signal<[(PeerId, AddChannelMemberError)], NoError> in - return .single([(memberIds[0], error)]) - } - } else { - return context.peerChannelMemberCategoriesContextsManager.addMembersAllowPartial(engine: context.engine, peerId: groupPeer.id, memberIds: memberIds) - } - } else { - var signals: [Signal<(PeerId, AddChannelMemberError)?, NoError>] = [] - for memberId in memberIds { - let signal: Signal<(PeerId, AddChannelMemberError)?, NoError> = context.engine.peers.addGroupMember(peerId: groupPeer.id, memberId: memberId) - |> mapError { error -> AddChannelMemberError in - switch error { - case .generic: - return .generic - case .groupFull: - return .limitExceeded - case let .privacy(privacy): - return .restricted(privacy?.forbiddenPeers.first) - case .notMutualContact: - return .notMutualContact - case .tooManyChannels: - return .generic - } - } - |> ignoreValues - |> map { _ -> (PeerId, AddChannelMemberError)? in - } - |> then(Signal<(PeerId, AddChannelMemberError)?, AddChannelMemberError>.single(nil)) - |> `catch` { error -> Signal<(PeerId, AddChannelMemberError)?, NoError> in - return .single((memberId, error)) - } - signals.append(signal) - } - return combineLatest(signals) - |> map { values -> [(PeerId, AddChannelMemberError)] in - return values.compactMap { $0 } - } - } - } - } - - createInviteLinkImpl = { [weak contactsController] in - contactsController?.view.window?.endEditing(true) - contactsController?.present(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: groupPeer.id), initialInvite: nil, parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root)) - } - - parentController?.push(contactsController) - do { - selectAddMemberDisposable.set(( - combineLatest(queue: .mainQueue(), - context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: groupPeer.id)), - contactsController.result - ) - |> deliverOnMainQueue).start(next: { [weak contactsController] exportedInvitation, result in - var peers: [ContactListPeerId] = [] - if case let .result(peerIdsValue, _) = result { - peers = peerIdsValue - } - - contactsController?.displayProgress = true - addMemberDisposable.set((addMembers(peers) - |> deliverOnMainQueue).start(next: { failedPeerIds in - if failedPeerIds.isEmpty { - contactsController?.dismiss() - - let mappedPeerIds: [EnginePeer.Id] = peers.compactMap { peer -> EnginePeer.Id? in - switch peer { - case let .peer(id): - return id - default: - return nil - } - } - if !mappedPeerIds.isEmpty { - let _ = (context.engine.data.get(EngineDataMap(mappedPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))) - |> deliverOnMainQueue).startStandalone(next: { maybePeers in - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let peers = maybePeers.compactMap { $0.value } - - let text: String - if peers.count == 1 { - text = presentationData.strings.PeerInfo_NotificationMemberAdded(peers[0].displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string - } else { - text = presentationData.strings.PeerInfo_NotificationMultipleMembersAdded(Int32(peers.count)) - } - parentController?.present(UndoOverlayController(presentationData: presentationData, content: .peers(context: context, peers: peers, title: nil, text: text, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - }) - } - } else { - let failedPeers = failedPeerIds.compactMap { _, error -> TelegramForbiddenInvitePeer? in - if case let .restricted(peer) = error { - return peer - } else { - return nil - } - } - - if !failedPeers.isEmpty, let contactsController, let navigationController = contactsController.navigationController as? NavigationController { - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(where: { $0 === contactsController }) { - let inviteScreen = SendInviteLinkScreen(context: context, subject: .chat(peer: EnginePeer(groupPeer), link: exportedInvitation?.link), peers: failedPeers) - viewControllers.remove(at: index) - viewControllers.append(inviteScreen) - navigationController.setViewControllers(viewControllers, animated: true) - } - } else { - contactsController?.dismiss() - } - } - })) - })) - contactsController.dismissed = { - selectAddMemberDisposable.set(nil) - addMemberDisposable.set(nil) - } - } - }) -} - struct ClearPeerHistory { enum ClearType { case savedMessages @@ -15126,235 +7259,3 @@ struct ClearPeerHistory { } } } - -private final class AccountPeerContextItem: ContextMenuCustomItem { - let context: AccountContext - let account: Account - let peer: EnginePeer - let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void - - init(context: AccountContext, account: Account, peer: EnginePeer, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) { - self.context = context - self.account = account - self.peer = peer - self.action = action - } - - public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode { - return AccountPeerContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected) - } -} - -private final class AccountPeerContextItemNode: ASDisplayNode, ContextMenuCustomNode { - private let item: AccountPeerContextItem - private let presentationData: PresentationData - private let getController: () -> ContextControllerProtocol? - private let actionSelected: (ContextMenuActionResult) -> Void - - private let highlightedBackgroundNode: ASDisplayNode - private let buttonNode: HighlightTrackingButtonNode - private let textNode: ImmediateTextNode - private let avatarNode: AvatarNode - private let emojiStatusView: ComponentView - - init(presentationData: PresentationData, item: AccountPeerContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) { - self.item = item - self.presentationData = presentationData - self.getController = getController - self.actionSelected = actionSelected - - let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 17.0 / 17.0) - - self.highlightedBackgroundNode = ASDisplayNode() - self.highlightedBackgroundNode.isAccessibilityElement = false - self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor - self.highlightedBackgroundNode.alpha = 0.0 - - self.textNode = ImmediateTextNode() - self.textNode.isAccessibilityElement = false - self.textNode.isUserInteractionEnabled = false - self.textNode.displaysAsynchronously = false - let peerTitle = item.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - self.textNode.attributedText = NSAttributedString(string: peerTitle, font: textFont, textColor: presentationData.theme.contextMenu.primaryColor) - self.textNode.maximumNumberOfLines = 1 - - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0)) - - self.emojiStatusView = ComponentView() - - self.buttonNode = HighlightTrackingButtonNode() - self.buttonNode.isAccessibilityElement = true - self.buttonNode.accessibilityLabel = peerTitle - - super.init() - - self.addSubnode(self.highlightedBackgroundNode) - self.addSubnode(self.textNode) - self.addSubnode(self.avatarNode) - self.addSubnode(self.buttonNode) - - self.buttonNode.highligthedChanged = { [weak self] highligted in - guard let strongSelf = self else { - return - } - if highligted { - strongSelf.highlightedBackgroundNode.alpha = 1.0 - } else { - strongSelf.highlightedBackgroundNode.alpha = 0.0 - strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - } - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - } - - func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) { - let sideInset: CGFloat = 16.0 - let iconSideInset: CGFloat = 12.0 - let verticalInset: CGFloat = 12.0 - - let iconSize = CGSize(width: 28.0, height: 28.0) - - let standardIconWidth: CGFloat = 32.0 - var rightTextInset: CGFloat = sideInset - if !iconSize.width.isZero { - rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset - 12.0 - } - - self.avatarNode.setPeer(context: self.item.context, account: self.item.account, theme: self.presentationData.theme, peer: self.item.peer) - - if self.item.peer.emojiStatus != nil { - rightTextInset += 32.0 - } - - let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude)) - - return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + textSize.height), { size, transition in - let verticalOrigin = floor((size.height - textSize.height) / 2.0) - let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize) - transition.updateFrameAdditive(node: self.textNode, frame: textFrame) - - var iconContent: EmojiStatusComponent.Content? - if case let .user(user) = self.item.peer { - if let emojiStatus = user.emojiStatus { - iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever) - } else if user.isPremium { - iconContent = .premium(color: self.presentationData.theme.list.itemAccentColor) - } - } else if case let .channel(channel) = self.item.peer { - if let emojiStatus = channel.emojiStatus { - iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever) - } - } - if let iconContent { - let emojiStatusSize = self.emojiStatusView.update( - transition: .immediate, - component: AnyComponent(EmojiStatusComponent( - context: self.item.context, - animationCache: self.item.context.animationCache, - animationRenderer: self.item.context.animationRenderer, - content: iconContent, - isVisibleForAnimations: true, - action: nil - )), - environment: {}, - containerSize: CGSize(width: 24.0, height: 24.0) - ) - if let view = self.emojiStatusView.view { - if view.superview == nil { - self.view.addSubview(view) - } - transition.updateFrame(view: view, frame: CGRect(origin: CGPoint(x: textFrame.maxX + 2.0, y: textFrame.minY + floor((textFrame.height - emojiStatusSize.height) / 2.0)), size: emojiStatusSize)) - } - } - - transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)) - - transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) - transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) - }) - } - - func updateTheme(presentationData: PresentationData) { - self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor - - if let attributedText = self.textNode.attributedText { - let updatedAttributedText = NSMutableAttributedString(attributedString: attributedText) - updatedAttributedText.addAttribute(.foregroundColor, value: presentationData.theme.contextMenu.primaryColor.cgColor, range: NSRange(location: 0, length: updatedAttributedText.length)) - self.textNode.attributedText = updatedAttributedText - } - } - - @objc private func buttonPressed() { - self.performAction() - } - - func canBeHighlighted() -> Bool { - return true - } - - func setIsHighlighted(_ value: Bool) { - if value { - self.highlightedBackgroundNode.alpha = 1.0 - } else { - self.highlightedBackgroundNode.alpha = 0.0 - } - } - - func updateIsHighlighted(isHighlighted: Bool) { - self.setIsHighlighted(isHighlighted) - } - - func performAction() { - guard let controller = self.getController() else { - return - } - self.item.action(controller, { [weak self] result in - self?.actionSelected(result) - }) - } -} - -private final class PeerInfoControllerContextReferenceContentSource: ContextReferenceContentSource { - let controller: ViewController - let sourceView: UIView - let insets: UIEdgeInsets - let contentInsets: UIEdgeInsets - - init(controller: ViewController, sourceView: UIView, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets()) { - self.controller = controller - self.sourceView = sourceView - self.insets = insets - self.contentInsets = contentInsets - } - - func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets) - } -} - -private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { - private let controller: ViewController - private let sourceView: UIView - - init(controller: ViewController, sourceView: UIView) { - self.controller = controller - self.sourceView = sourceView - } - - func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) - } -} - -private func cancelContextGestures(view: UIView) { - if let gestureRecognizers = view.gestureRecognizers { - for gesture in gestureRecognizers { - if let gesture = gesture as? ContextGesture { - gesture.cancel() - } - } - } - for subview in view.subviews { - cancelContextGestures(view: subview) - } -} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift index 88d95df3..4705dc95 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift @@ -103,9 +103,14 @@ extension PeerInfoScreenImpl { let parentController = (self.context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController as? ViewController var dismissImpl: (() -> Void)? - let mainController = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in + let (mainController, pickerHolder) = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in self?.openAvatarRemoval(mode: mode, peer: peer, item: item) - }, completion: { result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in + }, completion: { [weak self] result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in + guard let self else { + return + } + self.avatarPickerHolder = nil + var resultImage: UIImage? let uploadStatusPromise = Promise(.progress(0.0)) @@ -155,7 +160,6 @@ extension PeerInfoScreenImpl { commit() } parentController?.push(controller) - //isFromEditor = true return } @@ -232,31 +236,37 @@ extension PeerInfoScreenImpl { self.parentController?.pushViewController(editorController) } }, dismissed: { - }) - dismissImpl = { [weak self, weak mainController] in - if let mainController, let navigationController = mainController.navigationController { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { c in - return !(c is CameraScreen) && c !== mainController + self.avatarPickerHolder = pickerHolder + if let mainController { + dismissImpl = { [weak self, weak mainController] in + if let mainController, let navigationController = mainController.navigationController { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { c in + return !(c is CameraScreen) && c !== mainController + } + navigationController.setViewControllers(viewControllers, animated: false) } - navigationController.setViewControllers(viewControllers, animated: false) - } - if let self, let navigationController = self.parentController, let mainController { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { c in - return !(c is CameraScreen) && c !== mainController + if let self, let navigationController = self.parentController, let mainController { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { c in + return !(c is CameraScreen) && c !== mainController + } + navigationController.setViewControllers(viewControllers, animated: false) + } + + } + if mainController is ActionSheetController { + self.present(mainController, in: .window(.root)) + } else { + mainController.navigationPresentation = .flatModal + mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + if self.navigationController != nil { + self.push(mainController) + } else { + self.parentController?.pushViewController(mainController) } - navigationController.setViewControllers(viewControllers, animated: false) } - - } - mainController.navigationPresentation = .flatModal - mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - if self.navigationController != nil { - self.push(mainController) - } else { - self.parentController?.pushViewController(mainController) } }) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenBusinessActions.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenBusinessActions.swift new file mode 100644 index 00000000..fe797392 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenBusinessActions.swift @@ -0,0 +1,182 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import AsyncDisplayKit +import ContextUI +import UndoUI + +extension PeerInfoScreenNode { + func openWorkingHoursContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { + guard let sourceNode = node as? ContextExtractedContentContainingNode else { + return + } + guard let cachedData = self.data?.cachedData else { + return + } + + var businessHours: TelegramBusinessHours? + if let cachedData = cachedData as? CachedUserData { + businessHours = cachedData.businessHours + } + + guard let businessHours else { + return + } + + let copyAction = { [weak self] in + guard let self else { + return + } + UIPasteboard.general.string = businessHoursTextToCopy(businessHours: businessHours, presentationData: self.presentationData, displayLocalTimezone: false) + + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: self.presentationData.strings.MyProfile_ToastHoursCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + + var items: [ContextMenuItem] = [] + + if self.isMyProfile { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + let businessHoursSetupScreen = self.context.sharedContext.makeBusinessHoursSetupScreen(context: self.context, initialValue: businessHours, completion: { _ in }) + self.controller?.push(businessHoursSetupScreen) + } + }))) + } + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + copyAction() + } + }))) + + if self.isMyProfile { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursActionRemove, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ + in + guard let self else { + return + } + + var subItems: [ContextMenuItem] = [] + let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil + subItems.append(.action(ContextMenuActionItem( + text: self.presentationData.strings.MyProfile_HoursRemoveConfirmation_Title, + textLayout: .multiline, + textFont: .small, + icon: { _ in nil }, + action: noAction + ))) + subItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_HoursRemoveConfirmation_Action, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + let _ = self.context.engine.accountData.updateAccountBusinessHours(businessHours: nil).startStandalone() + } + }))) + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + }))) + } + + let actions = ContextController.Items(content: .list(items)) + + let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) + self.controller?.present(contextController, in: .window(.root)) + } + + func openBusinessLocationContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { + guard let sourceNode = node as? ContextExtractedContentContainingNode else { + return + } + guard let cachedData = self.data?.cachedData else { + return + } + + var businessLocation: TelegramBusinessLocation? + if let cachedData = cachedData as? CachedUserData { + businessLocation = cachedData.businessLocation + } + + guard let businessLocation else { + return + } + + let copyAction = { [weak self] in + guard let self else { + return + } + UIPasteboard.general.string = businessLocation.address + + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: self.presentationData.strings.MyProfile_ToastLocationCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + + var items: [ContextMenuItem] = [] + + if businessLocation.coordinates != nil { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionOpen, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LocationSmall"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss(completion: { + guard let self else { + return + } + self.interaction.openLocation() + }) + }))) + } + + if !businessLocation.address.isEmpty { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + copyAction() + } + }))) + } + + if self.isMyProfile { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + let businessLocationSetupScreen = self.context.sharedContext.makeBusinessLocationSetupScreen(context: self.context, initialValue: businessLocation, completion: { _ in }) + self.controller?.push(businessLocationSetupScreen) + } + }))) + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationActionRemove, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in + guard let self else { + return + } + + var subItems: [ContextMenuItem] = [] + let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil + subItems.append(.action(ContextMenuActionItem( + text: self.presentationData.strings.MyProfile_LocationRemoveConfirmation_Title, + textLayout: .multiline, + textFont: .small, + icon: { _ in nil }, + action: noAction + ))) + subItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_LocationRemoveConfirmation_Action, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + let _ = self.context.engine.accountData.updateAccountBusinessLocation(businessLocation: nil).startStandalone() + } + }))) + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + }))) + } + + let actions = ContextController.Items(content: .list(items)) + + let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) + self.controller?.present(contextController, in: .window(.root)) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift new file mode 100644 index 00000000..c3b58b33 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift @@ -0,0 +1,361 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import AsyncDisplayKit +import ContextUI +import CreateExternalMediaStreamScreen +import OverlayStatusController +import TelegramPresentationData +import PresentationDataUtils +import TelegramCallsUI +import AvatarNode + +extension PeerInfoScreenNode { + func requestCall(isVideo: Bool, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) { + guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { + return + } + let peerId = self.peerId + let requestCall: (PeerId?, EngineGroupCallDescription?) -> Void = { [weak self] defaultJoinAsPeerId, activeCall in + if let activeCall = activeCall { + self?.context.joinGroupCall(peerId: peerId, invite: nil, requestJoinAsPeerId: { completion in + if let defaultJoinAsPeerId = defaultJoinAsPeerId { + result?(.dismissWithoutContent) + completion(defaultJoinAsPeerId) + } else { + self?.openVoiceChatDisplayAsPeerSelection(completion: { joinAsPeerId in + completion(joinAsPeerId) + }, gesture: gesture, contextController: contextController, result: result, backAction: backAction) + } + }, activeCall: activeCall) + } else { + self?.openVoiceChatOptions(defaultJoinAsPeerId: defaultJoinAsPeerId, gesture: gesture, contextController: contextController) + } + } + + if let cachedChannelData = self.data?.cachedData as? CachedChannelData { + requestCall(cachedChannelData.callJoinPeerId, cachedChannelData.activeCall.flatMap(EngineGroupCallDescription.init)) + return + } else if let cachedGroupData = self.data?.cachedData as? CachedGroupData { + requestCall(cachedGroupData.callJoinPeerId, cachedGroupData.activeCall.flatMap(EngineGroupCallDescription.init)) + return + } + + guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else { + return + } + if cachedUserData.callsPrivate { + self.controller?.push(self.context.sharedContext.makeSendInviteLinkScreen(context: self.context, subject: .groupCall(.create), peers: [TelegramForbiddenInvitePeer( + peer: EnginePeer(peer), + canInviteWithPremium: false, + premiumRequiredToContact: false + )], theme: self.presentationData.theme)) + return + } + + self.context.requestCall(peerId: peer.id, isVideo: isVideo, completion: {}) + } + + func scheduleGroupCall() { + guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { + return + } + self.context.scheduleGroupCall(peerId: self.peerId, parentController: controller) + } + + func createExternalStream(credentialsPromise: Promise?) { + guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { + return + } + self.controller?.push(CreateExternalMediaStreamScreen(context: self.context, peerId: self.peerId, credentialsPromise: credentialsPromise, mode: .create(liveStream: false))) + } + + func createAndJoinGroupCall(peerId: PeerId, joinAsPeerId: PeerId?) { + guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { + return + } + if let _ = self.context.sharedContext.callManager { + let startCall: (Bool) -> Void = { [weak self] endCurrentIfAny in + guard let strongSelf = self else { + return + } + + var cancelImpl: (() -> Void)? + let presentationData = strongSelf.presentationData + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.controller?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + let createSignal = strongSelf.context.engine.calls.createGroupCall(peerId: peerId, title: nil, scheduleDate: nil, isExternalStream: false) + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { [weak self] in + self?.activeActionDisposable.set(nil) + } + strongSelf.activeActionDisposable.set((createSignal + |> deliverOnMainQueue).start(next: { [weak self] info in + guard let strongSelf = self else { + return + } + strongSelf.context.joinGroupCall(peerId: peerId, invite: nil, requestJoinAsPeerId: { result in + result(joinAsPeerId) + }, activeCall: EngineGroupCallDescription(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: info.isStream)) + }, error: { [weak self] error in + guard let strongSelf = self else { + return + } + strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil) + + let text: String + switch error { + case .generic, .scheduledTooLate: + text = strongSelf.presentationData.strings.Login_UnknownError + case .anonymousNotAllowed: + if let channel = strongSelf.data?.peer as? TelegramChannel, case .broadcast = channel.info { + text = strongSelf.presentationData.strings.LiveStream_AnonymousDisabledAlertText + } else { + text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText + } + } + strongSelf.controller?.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + })) + } + + startCall(true) + } + } + + func openVoiceChatDisplayAsPeerSelection(completion: @escaping (PeerId) -> Void, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) { + let dismissOnSelection = contextController == nil + let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(context.account.peerId) + |> map { peer in + return [FoundPeer(peer: peer, subscribers: nil)] + } + let _ = (combineLatest(queue: Queue.mainQueue(), currentAccountPeer, self.displayAsPeersPromise.get() |> take(1)) + |> map { currentAccountPeer, availablePeers -> [FoundPeer] in + var result = currentAccountPeer + result.append(contentsOf: availablePeers) + return result + }).startStandalone(next: { [weak self] peers in + guard let strongSelf = self else { + return + } + if peers.count == 1, let peer = peers.first { + result?(.dismissWithoutContent) + completion(peer.peer.id) + } else { + var items: [ContextMenuItem] = [] + + var isGroup = false + for peer in peers { + if peer.peer is TelegramGroup { + isGroup = true + break + } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + isGroup = true + break + } + } + + items.append(.custom(VoiceChatInfoContextItem(text: isGroup ? strongSelf.presentationData.strings.VoiceChat_DisplayAsInfoGroup : strongSelf.presentationData.strings.VoiceChat_DisplayAsInfo, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Accounts"), color: theme.actionSheet.primaryTextColor) + }), true)) + + for peer in peers { + var subtitle: String? + if peer.peer.id.namespace == Namespaces.Peer.CloudUser { + subtitle = strongSelf.presentationData.strings.VoiceChat_PersonalAccount + } else if let subscribers = peer.subscribers { + if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { + subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) + } else { + subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers) + } + } + + let avatarSize = CGSize(width: 28.0, height: 28.0) + let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize) + items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { _, f in + if dismissOnSelection { + f(.dismissWithoutContent) + } + completion(peer.peer.id) + }))) + + if peer.peer.id.namespace == Namespaces.Peer.CloudUser { + items.append(.separator) + } + } + if backAction != nil { + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) + }, iconPosition: .left, action: { (c, _) in + if let c, let backAction = backAction { + backAction(c) + } + }))) + } + + if let contextController = contextController { + contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) + } else { + strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat) + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + + if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller { + let contextController = ContextController(presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + contextController.dismissed = { [weak self] in + if let strongSelf = self { + strongSelf.state = strongSelf.state.withHighlightedButton(nil) + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + } + } + controller.presentInGlobalOverlay(contextController) + } + } + } + }) + } + + func openVoiceChatOptions(defaultJoinAsPeerId: PeerId?, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil) { + guard let chatPeer = self.data?.peer else { + return + } + let context = self.context + let peerId = self.peerId + let defaultJoinAsPeerId = defaultJoinAsPeerId ?? self.context.account.peerId + let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + |> map { peer in + return [FoundPeer(peer: peer, subscribers: nil)] + } + let _ = (combineLatest(queue: Queue.mainQueue(), currentAccountPeer, self.displayAsPeersPromise.get() |> take(1)) + |> map { currentAccountPeer, availablePeers -> [FoundPeer] in + var result = currentAccountPeer + result.append(contentsOf: availablePeers) + return result + }).startStandalone(next: { [weak self] peers in + guard let strongSelf = self else { + return + } + + var items: [ContextMenuItem] = [] + + if peers.count > 1 { + var selectedPeer: FoundPeer? + for peer in peers { + if peer.peer.id == defaultJoinAsPeerId { + selectedPeer = peer + } + } + if let peer = selectedPeer { + let avatarSize = CGSize(width: 28.0, height: 28.0) + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { c, f in + guard let strongSelf = self else { + return + } + + strongSelf.openVoiceChatDisplayAsPeerSelection(completion: { joinAsPeerId in + let _ = context.engine.calls.updateGroupCallJoinAsPeer(peerId: peerId, joinAs: joinAsPeerId).startStandalone() + self?.openVoiceChatOptions(defaultJoinAsPeerId: joinAsPeerId, gesture: nil, contextController: c) + }, gesture: gesture, contextController: c, result: f, backAction: { [weak self] c in + self?.openVoiceChatOptions(defaultJoinAsPeerId: defaultJoinAsPeerId, gesture: nil, contextController: c) + }) + + }))) + items.append(.separator) + } + } + + let createVoiceChatTitle: String + let scheduleVoiceChatTitle: String + if let channel = strongSelf.data?.peer as? TelegramChannel, case .broadcast = channel.info { + createVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_CreateLiveStream + scheduleVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_ScheduleLiveStream + } else { + createVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_CreateVoiceChat + scheduleVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_ScheduleVoiceChat + } + + items.append(.action(ContextMenuActionItem(text: createVoiceChatTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VoiceChat"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.dismissWithoutContent) + + self?.createAndJoinGroupCall(peerId: peerId, joinAsPeerId: defaultJoinAsPeerId) + }))) + + items.append(.action(ContextMenuActionItem(text: scheduleVoiceChatTitle, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Schedule"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.dismissWithoutContent) + + self?.scheduleGroupCall() + }))) + + var credentialsPromise: Promise? + var canCreateStream = false + switch chatPeer { + case let group as TelegramGroup: + if case .creator = group.role { + canCreateStream = true + } + case let channel as TelegramChannel: + if channel.hasPermission(.manageCalls) { + canCreateStream = true + credentialsPromise = Promise() + credentialsPromise?.set(context.engine.calls.getGroupCallStreamCredentials(peerId: peerId, isLiveStream: false, revokePreviousCredentials: false) |> `catch` { _ -> Signal in return .never() }) + } + default: + break + } + + if canCreateStream { + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChannelInfo_CreateExternalStream, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VoiceChat"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.dismissWithoutContent) + + self?.createExternalStream(credentialsPromise: credentialsPromise) + }))) + } + + if let contextController = contextController { + contextController.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) + } else { + strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat) + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + + if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller { + let contextController = ContextController(presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + contextController.dismissed = { [weak self] in + if let strongSelf = self { + strongSelf.state = strongSelf.state.withHighlightedButton(nil) + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + } + } + controller.presentInGlobalOverlay(contextController) + } + } + }) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayGiftsContextMenu.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayGiftsContextMenu.swift new file mode 100644 index 00000000..6d1d859c --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayGiftsContextMenu.swift @@ -0,0 +1,258 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import ContextUI +import PeerInfoVisualMediaPaneNode + +extension PeerInfoScreenNode { + func displayGiftsContextMenu(source: ContextReferenceContentNode, gesture: ContextGesture?) { + guard let currentPaneKey = self.paneContainerNode.currentPaneKey, case .gifts = currentPaneKey else { + return + } + guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoGiftsPaneNode else { + return + } + guard let controller = self.controller else { + return + } + guard let data = self.data else { + return + } + + let giftsContext = pane.giftsContext + + var hasVisibility = false + if let channel = data.peer as? TelegramChannel, channel.hasPermission(.sendSomething) { + hasVisibility = true + } else if data.peer?.id == self.context.account.peerId { + hasVisibility = true + } + + let isCollection = giftsContext.collectionId != nil + + let strings = self.presentationData.strings + let items: Signal = giftsContext.state + |> map { state in + var hasPinnedGifts = false + for gift in state.gifts { + if gift.pinnedToTop { + hasPinnedGifts = true + break + } + } + return (state.filter, state.sorting, hasPinnedGifts || isCollection) + } + |> distinctUntilChanged(isEqual: { lhs, rhs -> Bool in + let filterEquals = lhs.0 == rhs.0 + let sortingEquals = lhs.1 == rhs.1 + let canReorderEquals = lhs.2 == rhs.2 + return filterEquals && sortingEquals && canReorderEquals + }) + |> map { [weak pane, weak giftsContext] filter, sorting, canReorder -> ContextController.Items in + var items: [ContextMenuItem] = [] + + if hasVisibility { + if let pane, case .all = pane.currentCollection { + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_AddCollection, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/Gifts/AddCollection"), color: theme.contextMenu.primaryColor) + }, action: { [weak pane] _, f in + f(.default) + + if let pane { + pane.createCollection() + } + }))) + } else { + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_AddGifts, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/Gifts/AddGift"), color: theme.contextMenu.primaryColor) + }, action: { [weak pane] _, f in + f(.default) + + if let pane, case let .collection(id) = pane.currentCollection { + pane.addGiftsToCollection(id: id) + } + }))) + } + + if canReorder { + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Reorder, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) + }, action: { [weak pane] _, f in + f(.default) + + if let pane { + pane.beginReordering() + } + }))) + } + + if let pane, case let .collection(id) = pane.currentCollection { + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_DeleteCollection, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak pane] _, f in + f(.default) + + if let pane { + pane.deleteCollection(id: id) + } + }))) + } + } + + if let pane, case let .collection(id) = pane.currentCollection, let addressName = data.peer?.addressName, !addressName.isEmpty { + let shareAction: ContextMenuItem = .action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_ShareCollection, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + self?.openShareLink(url: "https://t.me/\(addressName)/c/\(id)") + })) + if items.isEmpty { + items.append(shareAction) + } else { + items.insert(shareAction, at: 1) + } + } + + if !items.isEmpty { + items.append(.separator) + } + + if let pane, case .all = pane.currentCollection { + items.append(.action(ContextMenuActionItem(text: sorting == .date ? strings.PeerInfo_Gifts_SortByValue : strings.PeerInfo_Gifts_SortByDate, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: sorting == .date ? "Peer Info/SortValue" : "Peer Info/SortDate"), color: theme.contextMenu.primaryColor) + }, action: { [weak giftsContext] _, f in + f(.default) + + giftsContext?.updateSorting(sorting == .date ? .value : .date) + }))) + + items.append(.separator) + } + + let toggleFilter: (ProfileGiftsContext.Filters) -> Void = { [weak giftsContext] value in + var updatedFilter = filter + if updatedFilter.contains(value) { + updatedFilter.remove(value) + } else { + updatedFilter.insert(value) + } + if !updatedFilter.contains(.unlimited) && !updatedFilter.contains(.limitedUpgradable) && !updatedFilter.contains(.limitedNonUpgradable) && !updatedFilter.contains(.unique) { + updatedFilter.insert(.unlimited) + } + if !updatedFilter.contains(.displayed) && !updatedFilter.contains(.hidden) { + if value == .displayed { + updatedFilter.insert(.hidden) + } else { + updatedFilter.insert(.displayed) + } + } + giftsContext?.updateFilter(updatedFilter) + } + + let switchToFilter: (ProfileGiftsContext.Filters) -> Void = { [weak giftsContext] value in + var updatedFilter = filter + updatedFilter.remove(.unlimited) + updatedFilter.remove(.limitedUpgradable) + updatedFilter.remove(.limitedNonUpgradable) + updatedFilter.remove(.unique) + updatedFilter.insert(value) + giftsContext?.updateFilter(updatedFilter) + } + + let switchToVisiblityFilter: (ProfileGiftsContext.Filters) -> Void = { [weak giftsContext] value in + var updatedFilter = filter + updatedFilter.remove(.hidden) + updatedFilter.remove(.displayed) + updatedFilter.insert(value) + giftsContext?.updateFilter(updatedFilter) + } + + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Unlimited, icon: { theme in + return filter.contains(.unlimited) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil + }, action: { _, f in + toggleFilter(.unlimited) + }, longPressAction: { _, f in + switchToFilter(.unlimited) + }))) + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Limited, icon: { theme in + return filter.contains(.limitedNonUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil + }, action: { _, f in + toggleFilter(.limitedNonUpgradable) + }, longPressAction: { _, f in + switchToFilter(.limitedNonUpgradable) + }))) + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Upgradable, icon: { theme in + return filter.contains(.limitedUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil + }, action: { _, f in + toggleFilter(.limitedUpgradable) + }, longPressAction: { _, f in + switchToFilter(.limitedUpgradable) + }))) + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Unique, icon: { theme in + return filter.contains(.unique) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil + }, action: { _, f in + toggleFilter(.unique) + }, longPressAction: { _, f in + switchToFilter(.unique) + }))) + + if hasVisibility { + items.append(.separator) + + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Displayed, icon: { theme in + return filter.contains(.displayed) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil + }, action: { _, f in + toggleFilter(.displayed) + }, longPressAction: { _, f in + switchToVisiblityFilter(.displayed) + }))) + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Hidden, icon: { theme in + return filter.contains(.hidden) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil + }, action: { _, f in + toggleFilter(.hidden) + }, longPressAction: { _, f in + switchToVisiblityFilter(.hidden) + }))) + } + + return ContextController.Items(content: .list(items)) + } + + let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: items, gesture: gesture) + contextController.passthroughTouchEvent = { [weak self] sourceView, point in + guard let strongSelf = self else { + return .ignore + } + + let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil) + guard let localResult = strongSelf.hitTest(localPoint, with: nil) else { + return .dismiss(consume: true, result: nil) + } + + var testView: UIView? = localResult + while true { + if let testViewValue = testView { + if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton { + node.isUserInteractionEnabled = false + DispatchQueue.main.async { + node.isUserInteractionEnabled = true + } + return .dismiss(consume: false, result: nil) + } else { + testView = testViewValue.superview + } + } else { + break + } + } + + return .dismiss(consume: true, result: nil) + } + self.mediaGalleryContextMenu = contextController + controller.presentInGlobalOverlay(contextController) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayMediaGalleryContextMenu.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayMediaGalleryContextMenu.swift new file mode 100644 index 00000000..280fd113 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayMediaGalleryContextMenu.swift @@ -0,0 +1,452 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import ContextUI +import PeerInfoVisualMediaPaneNode + +extension PeerInfoScreenNode { + func displayMediaGalleryContextMenu(source: ContextReferenceContentNode, gesture: ContextGesture?) { + let peerId = self.peerId + + var isBotPreviewOrStories = false + if let currentPaneKey = self.paneContainerNode.currentPaneKey { + if case .botPreview = currentPaneKey { + isBotPreviewOrStories = true + } else if case .stories = currentPaneKey { + isBotPreviewOrStories = true + } + } + + if isBotPreviewOrStories { + guard let controller = self.controller else { + return + } + guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoStoryPaneNode else { + return + } + + if case .botPreview = pane.scope { + guard let data = self.data, let user = data.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) else { + return + } + + var items: [ContextMenuItem] = [] + + let strings = self.presentationData.strings + + var ignoreNextActions = false + + if pane.canAddMoreBotPreviews() { + items.append(.action(ContextMenuActionItem(text: strings.BotPreviews_MenuAddPreview, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + if let self { + self.headerNode.navigationButtonContainer.performAction?(.postStory, nil, nil) + } + }))) + } + + if pane.canReorder() { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) + }, action: { [weak pane] _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + if let pane { + pane.beginReordering() + } + }))) + } + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + if let self { + self.toggleStorySelection(ids: [], isSelected: true) + } + }))) + + if let language = pane.currentBotPreviewLanguage { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuDeleteLanguage(language.name).string, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak pane] _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + if let pane { + pane.presentDeleteBotPreviewLanguage() + } + }))) + } + + let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + contextController.passthroughTouchEvent = { [weak self] sourceView, point in + guard let strongSelf = self else { + return .ignore + } + + let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil) + guard let localResult = strongSelf.hitTest(localPoint, with: nil) else { + return .dismiss(consume: true, result: nil) + } + + var testView: UIView? = localResult + while true { + if let testViewValue = testView { + if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton { + node.isUserInteractionEnabled = false + DispatchQueue.main.async { + node.isUserInteractionEnabled = true + } + return .dismiss(consume: false, result: nil) + } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode { + node.brieflyDisableTouchActions() + return .dismiss(consume: false, result: nil) + } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoStoryPaneNode { + node.brieflyDisableTouchActions() + return .dismiss(consume: false, result: nil) + } else { + testView = testViewValue.superview + } + } else { + break + } + } + + return .dismiss(consume: true, result: nil) + } + self.mediaGalleryContextMenu = contextController + controller.presentInGlobalOverlay(contextController) + } else if case .peer = pane.scope { + guard let data = self.data, let user = data.peer as? TelegramUser else { + return + } + let _ = user + + var items: [ContextMenuItem] = [] + + let strings = self.presentationData.strings + + var ignoreNextActions = false + + items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_MenuAddStories, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddStoryIcon"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + if let self { + self.headerNode.navigationButtonContainer.performAction?(.postStory, nil, nil) + } + }))) + + if let _ = pane.currentStoryFolder { + items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuShare, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) + }, action: { [weak pane] _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + if let pane { + pane.shareCurrentFolder() + } + }))) + } + + if pane.canReorder() { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) + }, action: { [weak pane] _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + if let pane { + pane.beginReordering() + } + }))) + } + + if let folder = pane.currentStoryFolder { + let _ = folder + + items.append(.action(ContextMenuActionItem(text: strings.Stories_MenuDeleteAlbum, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak pane] _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + if let pane { + pane.presentDeleteCurrentStoryFolder() + } + }))) + } + + if let language = pane.currentBotPreviewLanguage { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuDeleteLanguage(language.name).string, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak pane] _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + if let pane { + pane.presentDeleteBotPreviewLanguage() + } + }))) + } + + let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + contextController.passthroughTouchEvent = { [weak self] sourceView, point in + guard let strongSelf = self else { + return .ignore + } + + let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil) + guard let localResult = strongSelf.hitTest(localPoint, with: nil) else { + return .dismiss(consume: true, result: nil) + } + + var testView: UIView? = localResult + while true { + if let testViewValue = testView { + if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton { + node.isUserInteractionEnabled = false + DispatchQueue.main.async { + node.isUserInteractionEnabled = true + } + return .dismiss(consume: false, result: nil) + } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode { + node.brieflyDisableTouchActions() + return .dismiss(consume: false, result: nil) + } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoStoryPaneNode { + node.brieflyDisableTouchActions() + return .dismiss(consume: false, result: nil) + } else { + testView = testViewValue.superview + } + } else { + break + } + } + + return .dismiss(consume: true, result: nil) + } + self.mediaGalleryContextMenu = contextController + controller.presentInGlobalOverlay(contextController) + } + } else { + let _ = (self.context.engine.data.get(EngineDataMap([ + TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photo), + TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: self.chatLocation.threadId, tag: .video) + ])) + |> deliverOnMainQueue).startStandalone(next: { [weak self] messageCounts in + guard let strongSelf = self else { + return + } + + var mediaCount: [MessageTags: Int32] = [:] + for (key, count) in messageCounts { + mediaCount[key.tag] = count.flatMap(Int32.init) ?? 0 + } + + let photoCount: Int32 = mediaCount[.photo] ?? 0 + let videoCount: Int32 = mediaCount[.video] ?? 0 + + guard let controller = strongSelf.controller else { + return + } + guard let pane = strongSelf.paneContainerNode.currentPane?.node as? PeerInfoVisualMediaPaneNode else { + return + } + + var items: [ContextMenuItem] = [] + + let strings = strongSelf.presentationData.strings + + var recurseGenerateAction: ((Bool) -> ContextMenuActionItem)? + let generateAction: (Bool) -> ContextMenuActionItem = { [weak pane] isZoomIn in + let nextZoomLevel = isZoomIn ? pane?.availableZoomLevels().increment : pane?.availableZoomLevels().decrement + let canZoom: Bool = nextZoomLevel != nil + + return ContextMenuActionItem(id: isZoomIn ? 0 : 1, text: isZoomIn ? strings.SharedMedia_ZoomIn : strings.SharedMedia_ZoomOut, textColor: canZoom ? .primary : .disabled, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: isZoomIn ? "Chat/Context Menu/ZoomIn" : "Chat/Context Menu/ZoomOut"), color: canZoom ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)) + }, action: canZoom ? { action in + guard let pane = pane, let zoomLevel = isZoomIn ? pane.availableZoomLevels().increment : pane.availableZoomLevels().decrement else { + return + } + pane.updateZoomLevel(level: zoomLevel) + if let recurseGenerateAction = recurseGenerateAction { + action.updateAction(0, recurseGenerateAction(true)) + action.updateAction(1, recurseGenerateAction(false)) + } + } : nil) + } + recurseGenerateAction = { isZoomIn in + return generateAction(isZoomIn) + } + + items.append(.action(generateAction(true))) + items.append(.action(generateAction(false))) + + var ignoreNextActions = false + if strongSelf.chatLocation.threadId == nil { + items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowCalendar, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor) + }, action: { _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + self?.openMediaCalendar() + }))) + } + + if photoCount != 0 && videoCount != 0 { + items.append(.separator) + + let showPhotos: Bool + switch pane.contentType { + case .photo, .photoOrVideo: + showPhotos = true + default: + showPhotos = false + } + let showVideos: Bool + switch pane.contentType { + case .video, .photoOrVideo: + showVideos = true + default: + showVideos = false + } + + items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowPhotos, icon: { theme in + if !showPhotos { + return nil + } + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, action: { [weak pane] _, a in + a(.default) + + guard let pane = pane else { + return + } + let updatedContentType: PeerInfoVisualMediaPaneNode.ContentType + switch pane.contentType { + case .photoOrVideo: + updatedContentType = .video + case .photo: + updatedContentType = .photo + case .video: + updatedContentType = .photoOrVideo + default: + updatedContentType = pane.contentType + } + pane.updateContentType(contentType: updatedContentType) + }))) + items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowVideos, icon: { theme in + if !showVideos { + return nil + } + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, action: { [weak pane] _, a in + a(.default) + + guard let pane = pane else { + return + } + let updatedContentType: PeerInfoVisualMediaPaneNode.ContentType + switch pane.contentType { + case .photoOrVideo: + updatedContentType = .photo + case .photo: + updatedContentType = .photoOrVideo + case .video: + updatedContentType = .video + default: + updatedContentType = pane.contentType + } + pane.updateContentType(contentType: updatedContentType) + }))) + } + + let contextController = ContextController(presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + contextController.passthroughTouchEvent = { sourceView, point in + guard let strongSelf = self else { + return .ignore + } + + let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil) + guard let localResult = strongSelf.hitTest(localPoint, with: nil) else { + return .dismiss(consume: true, result: nil) + } + + var testView: UIView? = localResult + while true { + if let testViewValue = testView { + if let node = testViewValue.asyncdisplaykit_node as? PeerInfoHeaderNavigationButton { + node.isUserInteractionEnabled = false + DispatchQueue.main.async { + node.isUserInteractionEnabled = true + } + return .dismiss(consume: false, result: nil) + } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode { + node.brieflyDisableTouchActions() + return .dismiss(consume: false, result: nil) + } else if let node = testViewValue.asyncdisplaykit_node as? PeerInfoStoryPaneNode { + node.brieflyDisableTouchActions() + return .dismiss(consume: false, result: nil) + } else { + testView = testViewValue.superview + } + } else { + break + } + } + + return .dismiss(consume: true, result: nil) + } + strongSelf.mediaGalleryContextMenu = contextController + controller.presentInGlobalOverlay(contextController) + }) + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenItem.swift new file mode 100644 index 00000000..f4d1a0af --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenItem.swift @@ -0,0 +1,23 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import AccountContext +import TelegramPresentationData + +protocol PeerInfoScreenItem: AnyObject { + var id: AnyHashable { get } + func node() -> PeerInfoScreenItemNode +} + +class PeerInfoScreenItemNode: ASDisplayNode, AccessibilityFocusableNode { + var bringToFrontForHighlight: (() -> Void)? + + func update(context: AccountContext, width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, hasCorners: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { + preconditionFailure() + } + + override open func accessibilityElementDidBecomeFocused() { +// (self.supernode as? ListView)?.ensureItemNodeVisible(self, animated: false, overflow: 22.0) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenItemSectionContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenItemSectionContainerNode.swift new file mode 100644 index 00000000..7a821b3c --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenItemSectionContainerNode.swift @@ -0,0 +1,148 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import AccountContext +import TelegramPresentationData + +final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode { + private let backgroundNode: ASDisplayNode + private let topSeparatorNode: ASDisplayNode + private let bottomSeparatorNode: ASDisplayNode + private let itemContainerNode: ASDisplayNode + + private var currentItems: [PeerInfoScreenItem] = [] + var itemNodes: [AnyHashable: PeerInfoScreenItemNode] = [:] + + override init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + + self.topSeparatorNode = ASDisplayNode() + self.topSeparatorNode.isLayerBacked = true + + self.bottomSeparatorNode = ASDisplayNode() + self.bottomSeparatorNode.isLayerBacked = true + + self.itemContainerNode = ASDisplayNode() + self.itemContainerNode.clipsToBounds = true + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.itemContainerNode) + self.addSubnode(self.topSeparatorNode) + self.addSubnode(self.bottomSeparatorNode) + } + + func update(context: AccountContext, width: CGFloat, safeInsets: UIEdgeInsets, hasCorners: Bool, presentationData: PresentationData, items: [PeerInfoScreenItem], transition: ContainedViewLayoutTransition) -> CGFloat { + self.backgroundNode.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor + self.topSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + + self.topSeparatorNode.isHidden = hasCorners + self.bottomSeparatorNode.isHidden = hasCorners + + var contentHeight: CGFloat = 0.0 + var contentWithBackgroundHeight: CGFloat = 0.0 + var contentWithBackgroundOffset: CGFloat = 0.0 + + for i in 0 ..< items.count { + let item = items[i] + + let itemNode: PeerInfoScreenItemNode + var wasAdded = false + if let current = self.itemNodes[item.id] { + itemNode = current + } else { + wasAdded = true + itemNode = item.node() + self.itemNodes[item.id] = itemNode + self.itemContainerNode.addSubnode(itemNode) + itemNode.bringToFrontForHighlight = { [weak self, weak itemNode] in + guard let strongSelf = self, let itemNode = itemNode else { + return + } + strongSelf.view.bringSubviewToFront(itemNode.view) + } + } + + let itemTransition: ContainedViewLayoutTransition = wasAdded ? .immediate : transition + + let topItem: PeerInfoScreenItem? + if i == 0 { + topItem = nil + } else if items[i - 1] is PeerInfoScreenHeaderItem { + topItem = nil + } else { + topItem = items[i - 1] + } + + let bottomItem: PeerInfoScreenItem? + if i == items.count - 1 { + bottomItem = nil + } else if items[i + 1] is PeerInfoScreenCommentItem { + bottomItem = nil + } else { + bottomItem = items[i + 1] + } + + let itemHeight = itemNode.update(context: context, width: width, safeInsets: safeInsets, presentationData: presentationData, item: item, topItem: topItem, bottomItem: bottomItem, hasCorners: hasCorners, transition: itemTransition) + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: itemHeight)) + itemTransition.updateFrame(node: itemNode, frame: itemFrame) + if wasAdded { + itemNode.alpha = 0.0 + let alphaTransition: ContainedViewLayoutTransition = transition.isAnimated ? .animated(duration: 0.35, curve: .linear) : .immediate + alphaTransition.updateAlpha(node: itemNode, alpha: 1.0) + } + + if item is PeerInfoScreenCommentItem { + } else { + contentWithBackgroundHeight += itemHeight + } + contentHeight += itemHeight + + if item is PeerInfoScreenHeaderItem { + contentWithBackgroundOffset = contentHeight + } + } + + var removeIds: [AnyHashable] = [] + for (id, _) in self.itemNodes { + if !items.contains(where: { $0.id == id }) { + removeIds.append(id) + } + } + for id in removeIds { + if let itemNode = self.itemNodes.removeValue(forKey: id) { + itemNode.view.superview?.sendSubviewToBack(itemNode.view) + transition.updateAlpha(node: itemNode, alpha: 0.0, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + } + } + + transition.updateFrame(node: self.itemContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight))) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentWithBackgroundOffset), size: CGSize(width: width, height: max(0.0, contentWithBackgroundHeight - contentWithBackgroundOffset)))) + transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentWithBackgroundOffset - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))) + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentWithBackgroundHeight), size: CGSize(width: width, height: UIScreenPixel))) + + if contentHeight.isZero { + transition.updateAlpha(node: self.topSeparatorNode, alpha: 0.0) + transition.updateAlpha(node: self.bottomSeparatorNode, alpha: 0.0) + } else { + transition.updateAlpha(node: self.topSeparatorNode, alpha: 1.0) + transition.updateAlpha(node: self.bottomSeparatorNode, alpha: 1.0) + } + + return contentHeight + } + + func animateErrorIfNeeded() { + for (_, itemNode) in self.itemNodes { + if let itemNode = itemNode as? PeerInfoScreenMultilineInputItemNode { + itemNode.animateErrorIfNeeded() + } + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMessageActions.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMessageActions.swift new file mode 100644 index 00000000..54143e7d --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMessageActions.swift @@ -0,0 +1,282 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import TextFormat +import UndoUI +import ChatInterfaceState + +extension PeerInfoScreenNode { + func deleteMessages(messageIds: Set?) { + if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty { + self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(engine: self.context.engine, accountPeerId: self.context.account.peerId, messageIds: messageIds, keepUpdated: false) + |> deliverOnMainQueue).startStrict(next: { [weak self] actions in + if let strongSelf = self, let peer = strongSelf.data?.peer, !actions.options.isEmpty { + let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) + var items: [ActionSheetItem] = [] + var personalPeerName: String? + var isChannel = false + if let user = peer as? TelegramUser { + personalPeerName = EnginePeer(user).compactDisplayTitle + } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + isChannel = true + } + + if actions.options.contains(.deleteGlobally) { + let globalTitle: String + if isChannel { + globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe + } else if let personalPeerName = personalPeerName { + globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string + } else { + globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone + } + items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) + let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() + } + })) + } + if actions.options.contains(.deleteLocally) { + var localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe + if strongSelf.context.account.peerId == strongSelf.peerId { + if messageIds.count == 1 { + localOptionText = strongSelf.presentationData.strings.Conversation_Moderate_Delete + } else { + localOptionText = strongSelf.presentationData.strings.Conversation_DeleteManyMessages + } + } + items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) + let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).startStandalone() + } + })) + } + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.view.endEditing(true) + strongSelf.controller?.present(actionSheet, in: .window(.root)) + } + })) + } + } + + func forwardMessages(messageIds: Set?) { + if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty { + let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, filter: [.onlyWriteable, .excludeDisabled], hasFilters: true, multipleSelection: true, selectForumThreads: true)) + peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in + guard let strongSelf = self, let strongController = peerSelectionController else { + return + } + strongController.dismiss() + + var result: [EnqueueMessage] = [] + if messageText.string.count > 0 { + let inputText = convertMarkdownToAttributes(messageText) + for text in breakChatInputText(trimChatInputText(inputText)) { + if text.length != 0 { + var attributes: [MessageAttribute] = [] + let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) + if !entities.isEmpty { + attributes.append(TextEntitiesMessageAttribute(entities: entities)) + } + result.append(.message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + } + } + } + + var attributes: [MessageAttribute] = [] + attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions?.hideNames == true, hideCaptions: forwardOptions?.hideCaptions == true)) + + result.append(contentsOf: messageIds.map { messageId -> EnqueueMessage in + return .forward(source: messageId, threadId: nil, grouping: .auto, attributes: attributes, correlationId: nil) + }) + + var displayPeers: [EnginePeer] = [] + for peer in peers { + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: result) + |> deliverOnMainQueue).startStandalone(next: { messageIds in + if let strongSelf = self { + let signals: [Signal] = messageIds.compactMap({ id -> Signal? in + guard let id = id else { + return nil + } + return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) + |> mapToSignal { status, _ -> Signal in + if status != nil { + return .never() + } else { + return .single(true) + } + } + |> take(1) + }) + if strongSelf.shareStatusDisposable == nil { + strongSelf.shareStatusDisposable = MetaDisposable() + } + strongSelf.shareStatusDisposable?.set((combineLatest(signals) + |> deliverOnMainQueue).startStrict()) + } + }) + + if case let .secretChat(secretPeer) = peer { + if let peer = peerMap[secretPeer.regularPeerId] { + displayPeers.append(peer) + } + } else { + displayPeers.append(peer) + } + } + + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + let text: String + var savedMessages = false + if displayPeers.count == 1, let peerId = displayPeers.first?.id, peerId == strongSelf.context.account.peerId { + text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many + savedMessages = true + } else { + if displayPeers.count == 1, let peer = displayPeers.first { + var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + peerName = peerName.replacingOccurrences(of: "**", with: "") + text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string + } else if displayPeers.count == 2, let firstPeer = displayPeers.first, let secondPeer = displayPeers.last { + var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "") + var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "") + text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string + } else if let peer = displayPeers.first { + var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + peerName = peerName.replacingOccurrences(of: "**", with: "") + text = messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(displayPeers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(displayPeers.count - 1)").string + } else { + text = "" + } + } + + strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in + if savedMessages, let self, action == .info { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + guard let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) + }) + } + return false + }), in: .current) + } + peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peer, threadId in + let peerId = peer.id + + if let strongSelf = self, let _ = peerSelectionController { + if peerId == strongSelf.context.account.peerId { + Queue.mainQueue().after(0.88) { + strongSelf.hapticFeedback.success() + } + + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: true, action: { action in + if let self, action == .info { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + guard let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + }) + } + return false + }), in: .current) + + strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) + + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in + return .forward(source: id, threadId: nil, grouping: .auto, attributes: [], correlationId: nil) + }) + |> deliverOnMainQueue).startStandalone(next: { [weak self] messageIds in + if let strongSelf = self { + let signals: [Signal] = messageIds.compactMap({ id -> Signal? in + guard let id = id else { + return nil + } + return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) + |> mapToSignal { status, _ -> Signal in + if status != nil { + return .never() + } else { + return .single(true) + } + } + |> take(1) + }) + strongSelf.activeActionDisposable.set((combineLatest(signals) + |> deliverOnMainQueue).startStrict()) + } + }) + if let peerSelectionController = peerSelectionController { + peerSelectionController.dismiss() + } + } else { + let _ = (ChatInterfaceState.update(engine: strongSelf.context.engine, peerId: peerId, threadId: threadId, { currentState in + return currentState.withUpdatedForwardMessageIds(Array(messageIds)) + }) + |> deliverOnMainQueue).startStandalone(completed: { + if let strongSelf = self { + let proceed: (ChatController) -> Void = { chatController in + strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil) + + if let navigationController = strongSelf.controller?.navigationController as? NavigationController { + var viewControllers = navigationController.viewControllers + if threadId != nil { + viewControllers.insert(chatController, at: viewControllers.count - 2) + } else { + viewControllers.insert(chatController, at: viewControllers.count - 1) + } + navigationController.setViewControllers(viewControllers, animated: false) + + strongSelf.activeActionDisposable.set((chatController.ready.get() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak navigationController] _ in + viewControllers.removeAll(where: { $0 is PeerSelectionController }) + navigationController?.setViewControllers(viewControllers, animated: true) + })) + } + } + + if let threadId = threadId { + let _ = (strongSelf.context.sharedContext.chatControllerForForumThread(context: strongSelf.context, peerId: peerId, threadId: threadId) + |> deliverOnMainQueue).startStandalone(next: { chatController in + proceed(chatController) + }) + } else { + let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: .none, botStart: nil, mode: .standard(.default), params: nil) + proceed(chatController) + } + } + }) + } + } + } + self.controller?.push(peerSelectionController) + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift new file mode 100644 index 00000000..a273ef43 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift @@ -0,0 +1,129 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import AsyncDisplayKit +import TelegramUIPreferences +import ContextUI +import TranslateUI +import UndoUI + +extension PeerInfoScreenNode { + func openBioContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { + let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] sharedData in + guard let self else { + return + } + + let translationSettings: TranslationSettings + if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { + translationSettings = current + } else { + translationSettings = TranslationSettings.defaultSettings + } + + guard let sourceNode = node as? ContextExtractedContentContainingNode else { + return + } + guard let cachedData = self.data?.cachedData else { + return + } + + var bioText: String? + if let cachedData = cachedData as? CachedUserData { + bioText = cachedData.about + } else if let cachedData = cachedData as? CachedChannelData { + bioText = cachedData.about + } else if let cachedData = cachedData as? CachedGroupData { + bioText = cachedData.about + } + + guard let bioText, !bioText.isEmpty else { + return + } + + let copyAction = { [weak self] in + guard let self else { + return + } + UIPasteboard.general.string = bioText + + let toastText: String + if let _ = self.data?.peer as? TelegramUser { + toastText = self.presentationData.strings.MyProfile_ToastBioCopied + } else { + toastText = self.presentationData.strings.ChannelProfile_ToastAboutCopied + } + + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: toastText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + + var items: [ContextMenuItem] = [] + + if self.isMyProfile { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_BioActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) + + for (_, section) in self.editingSections { + for (id, itemNode) in section.itemNodes { + if id == AnyHashable("bio_edit") { + if let itemNode = itemNode as? PeerInfoScreenMultilineInputItemNode { + itemNode.focus() + } + break + } + } + } + } + }))) + } + + let copyText: String + if let _ = self.data?.peer as? TelegramUser { + copyText = self.presentationData.strings.MyProfile_BioActionCopy + } else { + copyText = self.presentationData.strings.ChannelProfile_AboutActionCopy + } + items.append(.action(ContextMenuActionItem(text: copyText, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + copyAction() + } + }))) + + let (canTranslate, language) = canTranslateText(context: self.context, text: bioText, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages) + if canTranslate { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + + let controller = TranslateScreen(context: self.context, text: bioText, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) + controller.pushController = { [weak self] c in + (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true + self?.controller?.push(c) + } + controller.presentController = { [weak self] c in + self?.controller?.present(c, in: .window(.root)) + } + self.controller?.present(controller, in: .window(.root)) + } + }))) + } + + let actions = ContextController.Items(content: .list(items)) + + let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) + self.controller?.present(contextController, in: .window(.root)) + }) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBirthday.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBirthday.swift new file mode 100644 index 00000000..95c79937 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBirthday.swift @@ -0,0 +1,69 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import AsyncDisplayKit +import TelegramStringFormatting +import UndoUI +import ContextUI + +extension PeerInfoScreenNode { + func openBirthdayContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { + guard let sourceNode = node as? ContextExtractedContentContainingNode else { + return + } + guard let cachedData = self.data?.cachedData else { + return + } + + var birthday: TelegramBirthday? + if let cachedData = cachedData as? CachedUserData { + birthday = cachedData.birthday + } + + guard let birthday else { + return + } + + let copyAction = { [weak self] in + guard let self else { + return + } + let presentationData = self.presentationData + let text = stringForCompactBirthday(birthday, strings: presentationData.strings) + + UIPasteboard.general.string = text + + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: self.presentationData.strings.MyProfile_ToastBirthdayCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + + var items: [ContextMenuItem] = [] + + if self.isMyProfile { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_BirthdayActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + + self.state = self.state.withIsEditingBirthDate(true) + self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) + } + }))) + } + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_BirthdayActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + copyAction() + } + }))) + + let actions = ContextController.Items(content: .list(items)) + + let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) + self.controller?.present(contextController, in: .window(.root)) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenChat.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenChat.swift new file mode 100644 index 00000000..824bd0ce --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenChat.swift @@ -0,0 +1,156 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore + +extension PeerInfoScreenNode { + func openChatWithMessageSearch() { + if let navigationController = (self.controller?.navigationController as? NavigationController) { + if case let .replyThread(currentMessage) = self.chatLocation, let current = navigationController.viewControllers.first(where: { controller in + if let controller = controller as? ChatController, case let .replyThread(message) = controller.chatLocation, message.peerId == currentMessage.peerId, message.threadId == currentMessage.threadId { + return true + } + return false + }) as? ChatController { + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(of: current) { + viewControllers.removeSubrange(index + 1 ..< viewControllers.count) + } + navigationController.setViewControllers(viewControllers, animated: true) + current.activateSearch(domain: .everything, query: "") + } else if let peer = self.data?.chatPeer { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, activateMessageSearch: (.everything, ""), peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in + if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { controller in + if controller is PeerInfoScreen { + return false + } + return true + } + navigationController.setViewControllers(viewControllers, animated: false) + } + })) + } + } + } + + func openChatForReporting(title: String, option: Data, message: String?) { + if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) { + if let channel = peer as? TelegramChannel, channel.isForumOrMonoForum { + //let _ = self.context.engine.peers.reportPeer(peerId: peer.id, reason: reason, message: "").startStandalone() + //self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .emoji(name: "PoliceCar", text: self.presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current) + } else { + self.context.sharedContext.navigateToChatController( + NavigateToChatControllerParams( + navigationController: navigationController, + context: self.context, + chatLocation: .peer(EnginePeer(peer)), + keepStack: .default, + reportReason: NavigateToChatControllerParams.ReportReason(title: title, option: option, message: message), + completion: { _ in + } + ) + ) + } + } + } + + func openChatForThemeChange() { + if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, changeColors: true, completion: { _ in + })) + } + } + + func openChatForTranslation() { + if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, changeColors: false, completion: { _ in + })) + } + } + + func openChat(peerId: EnginePeer.Id?) { + if let peerId { + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + guard let self, let peer else { + return + } + guard let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .always)) + }) + return + } + + if let peer = self.data?.peer, let navigationController = self.controller?.navigationController as? NavigationController { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in + if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { controller in + if controller is PeerInfoScreen { + return false + } + return true + } + navigationController.setViewControllers(viewControllers, animated: false) + } + })) + } + } + + func openChatWithClearedHistory(type: InteractiveHistoryClearingType) { + guard let peer = self.data?.chatPeer, let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), setupController: { controller in + controller.beginClearHistory(type: type) + }, completion: { [weak self] _ in + if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { controller in + if controller is PeerInfoScreen { + return false + } + return true + } + + navigationController.setViewControllers(viewControllers, animated: false) + } + })) + } + + func openChannelMessages() { + guard let channel = self.data?.peer as? TelegramChannel, let linkedMonoforumId = channel.linkedMonoforumId else { + return + } + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + guard let self, let peer else { + return + } + if let controller = self.controller, let navigationController = controller.navigationController as? NavigationController { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + } + }) + } + + func openRecentActions() { + guard let peer = self.data?.peer else { + return + } + let controller = self.context.sharedContext.makeChatRecentActionsController(context: self.context, peer: peer, adminPeerId: nil, starsState: self.data?.starsRevenueStatsState) + self.controller?.push(controller) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift new file mode 100644 index 00000000..2bac605c --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift @@ -0,0 +1,141 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import LegacyMediaPickerUI +import ChatHistorySearchContainerNode +import MediaResources +import TelegramUIPreferences + +extension PeerInfoScreenNode { + func openMessage(id: MessageId) -> Bool { + guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else { + return false + } + var foundGalleryMessage: Message? + if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { + if let galleryMessage = searchContentNode.messageForGallery(id) { + self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: [EngineMessage(galleryMessage)]) + foundGalleryMessage = galleryMessage + } + } + if foundGalleryMessage == nil, let galleryMessage = self.paneContainerNode.findLoadedMessage(id: id) { + foundGalleryMessage = galleryMessage + } + + guard let galleryMessage = foundGalleryMessage else { + return false + } + self.view.endEditing(true) + + return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: self.chatLocation, chatFilterTag: nil, chatLocationContextHolder: self.chatLocationContextHolder, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in + self?.view.endEditing(true) + }, present: { [weak self] c, a, _ in + self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true) + }, transitionNode: { [weak self] messageId, media, _ in + guard let strongSelf = self else { + return nil + } + return strongSelf.paneContainerNode.transitionNodeForGallery(messageId: messageId, media: media) + }, addToTransitionSurface: { [weak self] view in + guard let strongSelf = self else { + return + } + strongSelf.paneContainerNode.currentPane?.node.addToTransitionSurface(view: view) + }, openUrl: { [weak self] url in + self?.openUrl(url: url, concealed: false, external: false) + }, openPeer: { [weak self] peer, navigation in + self?.openPeer(peerId: peer.id, navigation: navigation) + }, callPeer: { peerId, isVideo in + }, openConferenceCall: { _ in + }, enqueueMessage: { _ in + }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed, forceExternal in + if let strongSelf = self { + strongSelf.openUrl(url: url, concealed: false, external: forceExternal) + } + }, openUrlIn: { [weak self] url in + if let strongSelf = self { + strongSelf.openUrlIn(url) + } + }, openPeerMention: { [weak self] mention in + if let strongSelf = self { + strongSelf.openPeerMention(mention) + } + }, openPeer: { [weak self] peer in + if let strongSelf = self { + strongSelf.openPeer(peerId: peer.id, navigation: .default) + } + }, openHashtag: { [weak self] peerName, hashtag in + if let strongSelf = self { + strongSelf.openHashtag(hashtag, peerName: peerName) + } + }, openBotCommand: { _ in + }, openAd: { _ in + }, addContact: { [weak self] phoneNumber in + if let strongSelf = self { + strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in + self?.controller?.present(controller, in: .window(.root), with: arguments) + }, pushController: { [weak self] controller in + if let strongSelf = self { + strongSelf.controller?.push(controller) + } + }, completed: {}) + } + }, storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in + guard let strongSelf = self else { + return + } + var storedState: MediaPlaybackStoredState? + if let timestamp = timestamp { + storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: AudioPlaybackRate(playbackRate)) + } + let _ = updateMediaPlaybackStoredStateInteractively(engine: strongSelf.context.engine, messageId: messageId, state: storedState).startStandalone() + }, editMedia: { [weak self] messageId, snapshots, transitionCompletion in + guard let strongSelf = self else { + return + } + + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] message in + guard let strongSelf = self, let message = message else { + return + } + + var mediaReference: AnyMediaReference? + for media in message.media { + if let image = media as? TelegramMediaImage { + mediaReference = AnyMediaReference.standalone(media: image) + } else if let file = media as? TelegramMediaFile { + mediaReference = AnyMediaReference.standalone(media: file) + } + } + + if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { + legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: message.associatedThreadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { + transitionCompletion() + }, getCaptionPanelView: { + return nil + }, sendMessagesWithSignals: { [weak self] signals, _, _, _ in + if let strongSelf = self { + strongSelf.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: strongSelf.context, account: strongSelf.context.account, signals: signals!) + |> deliverOnMainQueue).startStrict(next: { [weak self] messages in + if let strongSelf = self { + let _ = enqueueMessages(account: strongSelf.context.account, peerId: strongSelf.peerId, messages: messages.map { $0.message }).startStandalone() + } + })) + } + }, present: { [weak self] c, a in + self?.controller?.present(c, in: .window(.root), with: a) + }) + } + }) + }, updateCanReadHistory: { _ in + }), centralItemUpdated: { [weak self] messageId in + let _ = self?.paneContainerNode.requestExpandTabs?() + self?.paneContainerNode.currentPane?.node.ensureMessageIsVisible(id: messageId) + })) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenNote.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenNote.swift new file mode 100644 index 00000000..9d2ff549 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenNote.swift @@ -0,0 +1,76 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import AsyncDisplayKit +import ContextUI +import Pasteboard +import UndoUI + +extension PeerInfoScreenNode { + func openNoteContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { + guard let sourceNode = node as? ContextExtractedContentContainingNode else { + return + } + guard let cachedData = self.data?.cachedData else { + return + } + + var noteText: String? + var noteEntities: [MessageTextEntity]? + if let cachedData = cachedData as? CachedUserData { + noteText = cachedData.note?.text + noteEntities = cachedData.note?.entities + } + + guard let noteText, !noteText.isEmpty else { + return + } + + let copyAction = { [weak self] in + guard let self else { + return + } + storeMessageTextInPasteboard(noteText, entities: noteEntities ?? []) + + let toastText = self.presentationData.strings.PeerInfo_ToastNoteCopied + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: toastText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_NoteActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) + + for (_, section) in self.editingSections { + for (id, itemNode) in section.itemNodes { + if id == AnyHashable("note_edit") { + if let itemNode = itemNode as? PeerInfoScreenNoteListItemNode { + itemNode.focus() + } + break + } + } + } + } + }))) + + let copyText = self.presentationData.strings.PeerInfo_NoteActionCopy + items.append(.action(ContextMenuActionItem(text: copyText, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + copyAction() + } + }))) + + let actions = ContextController.Items(content: .list(items)) + + let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) + self.controller?.present(contextController, in: .window(.root)) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift new file mode 100644 index 00000000..b810ae58 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift @@ -0,0 +1,178 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import AsyncDisplayKit +import UndoUI +import TranslateUI +import TelegramStringFormatting +import TelegramUIPreferences + +extension PeerInfoScreenNode { + func openPeerInfoContextMenu(subject: PeerInfoContextSubject, sourceNode: ASDisplayNode, sourceRect: CGRect?) { + guard let data = self.data, let peer = data.peer, let controller = self.controller else { + return + } + let context = self.context + switch subject { + case .birthday: + if let cachedData = data.cachedData as? CachedUserData, let birthday = cachedData.birthday { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let text = stringForCompactBirthday(birthday, strings: presentationData.strings) + + let actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in + UIPasteboard.general.string = text + + self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })] + let contextMenuController = makeContextMenuController(actions: actions) + controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in + if let controller = self?.controller, let sourceNode = sourceNode { + var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) + if let sourceRect = sourceRect { + rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) + } + return (sourceNode, rect, controller.displayNode, controller.view.bounds) + } else { + return nil + } + })) + } + case .bio: + var text: String? + if let cachedData = data.cachedData as? CachedUserData { + text = cachedData.about + } else if let cachedData = data.cachedData as? CachedGroupData { + text = cachedData.about + } else if let cachedData = data.cachedData as? CachedChannelData { + text = cachedData.about + } + if let text = text, !text.isEmpty { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] sharedData in + let translationSettings: TranslationSettings + if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { + translationSettings = current + } else { + translationSettings = TranslationSettings.defaultSettings + } + + var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in + UIPasteboard.general.string = text + + self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })] + + let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages) + if canTranslate { + actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in + + let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) + controller.pushController = { [weak self] c in + (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true + self?.controller?.push(c) + } + controller.presentController = { [weak self] c in + self?.controller?.present(c, in: .window(.root)) + } + self?.controller?.present(controller, in: .window(.root)) + })) + } + + let contextMenuController = makeContextMenuController(actions: actions) + controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in + if let controller = self?.controller, let sourceNode = sourceNode { + var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) + if let sourceRect = sourceRect { + rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) + } + return (sourceNode, rect, controller.displayNode, controller.view.bounds) + } else { + return nil + } + })) + }) + } + case let .phone(phone): + let contextMenuController = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in + UIPasteboard.general.string = phone + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_PhoneCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })]) + controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in + if let controller = self?.controller, let sourceNode = sourceNode { + var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) + if let sourceRect = sourceRect { + rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) + } + return (sourceNode, rect, controller.displayNode, controller.view.bounds) + } else { + return nil + } + })) + case let .link(customLink): + let text: String + let content: UndoOverlayContent + if let customLink = customLink { + text = customLink + content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied) + } else if let addressName = peer.addressName { + if peer is TelegramChannel { + text = "https://t.me/\(addressName)" + content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied) + } else { + text = "@" + addressName + content = .copy(text: self.presentationData.strings.Conversation_UsernameCopied) + } + } else { + text = "https://t.me/@id\(peer.id.id._internalGetInt64Value())" + content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied) + } + + let contextMenuController = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in + UIPasteboard.general.string = text + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })]) + controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in + if let controller = self?.controller, let sourceNode = sourceNode { + var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) + if let sourceRect = sourceRect { + rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) + } + return (sourceNode, rect, controller.displayNode, controller.view.bounds) + } else { + return nil + } + })) + case .businessHours(let text), .businessLocation(let text): + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in + UIPasteboard.general.string = text + + self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })] + + let contextMenuController = makeContextMenuController(actions: actions) + controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in + if let controller = self?.controller, let sourceNode = sourceNode { + var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) + if let sourceRect = sourceRect { + rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) + } + return (sourceNode, rect, controller.displayNode, controller.view.bounds) + } else { + return nil + } + })) + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPhone.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPhone.swift new file mode 100644 index 00000000..8219c2df --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPhone.swift @@ -0,0 +1,179 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import AsyncDisplayKit +import ContextUI +import PhoneNumberFormat +import UndoUI + +extension PeerInfoScreenNode { + func openPhone(value: String, node: ASDisplayNode, gesture: ContextGesture?, progress: Promise?) { + guard let sourceNode = node as? ContextExtractedContentContainingNode else { + return + } + + let formattedPhoneNumber = formatPhoneNumber(context: self.context, number: value) + if gesture == nil, formattedPhoneNumber.hasPrefix("+888") { + let collectibleInfo = Promise() + collectibleInfo.set(self.context.sharedContext.makeCollectibleItemInfoScreenInitialData(context: self.context, peerId: self.peerId, subject: .phoneNumber(value))) + + progress?.set(.single(true)) + let _ = (collectibleInfo.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] initialData in + progress?.set(.single(false)) + + guard let self else { + return + } + if let initialData { + self.view.endEditing(true) + self.controller?.push(self.context.sharedContext.makeCollectibleItemInfoScreen(context: self.context, initialData: initialData)) + } else { + self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: "https://fragment.com/numbers", forceExternal: true, presentationData: self.presentationData, navigationController: nil, dismissInput: {}) + } + }) + + return + } + + let _ = (combineLatest( + getUserPeer(engine: self.context.engine, peerId: self.peerId), + getUserPeer(engine: self.context.engine, peerId: self.context.account.peerId) + ) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer, accountPeer in + guard let strongSelf = self else { + return + } + let presentationData = strongSelf.presentationData + + let telegramCallAction: (Bool) -> Void = { [weak self] isVideo in + guard let strongSelf = self else { + return + } + strongSelf.requestCall(isVideo: isVideo) + } + + let phoneCallAction = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.context.sharedContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(context: strongSelf.context, number: value).replacingOccurrences(of: " ", with: ""))") + } + + let copyAction = { [weak self] in + guard let strongSelf = self else { + return + } + UIPasteboard.general.string = formatPhoneNumber(context: strongSelf.context, number: value) + + strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_PhoneCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + + var accountIsFromUS = false + if let accountPeer, case let .user(user) = accountPeer, let phone = user.phone { + if let (country, _) = lookupCountryIdByNumber(phone, configuration: strongSelf.context.currentCountriesConfiguration.with { $0 }) { + if country.id == "US" { + accountIsFromUS = true + } + } + } + + var isAnonymousNumber = false + var items: [ContextMenuItem] = [] + + if strongSelf.isMyProfile { + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.MyProfile_PhoneActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + self.openSettings(section: .phoneNumber) + } + }))) + } + + if case let .user(peer) = peer, let peerPhoneNumber = peer.phone, formattedPhoneNumber == formatPhoneNumber(context: strongSelf.context, number: peerPhoneNumber) { + if !strongSelf.isMyProfile { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_TelegramCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + telegramCallAction(false) + } + }))) + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_TelegramVideoCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoCall"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + telegramCallAction(true) + } + }))) + } + if !formattedPhoneNumber.hasPrefix("+888") { + if !strongSelf.isMyProfile { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_PhoneCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PhoneCall"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + phoneCallAction() + } + }))) + } + } else { + isAnonymousNumber = true + } + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.MyProfile_PhoneActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + copyAction() + } + }))) + } else { + if !formattedPhoneNumber.hasPrefix("+888") { + if !strongSelf.isMyProfile { + items.append( + .action(ContextMenuActionItem(text: presentationData.strings.UserInfo_PhoneCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PhoneCall"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + phoneCallAction() + } + })) + ) + } + } else { + isAnonymousNumber = true + } + items.append( + .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.MyProfile_PhoneActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + copyAction() + } + })) + ) + } + var actions = ContextController.Items(content: .list(items)) + if isAnonymousNumber && !accountIsFromUS { + let collectibleInfo = Promise() + collectibleInfo.set(strongSelf.context.sharedContext.makeCollectibleItemInfoScreenInitialData(context: strongSelf.context, peerId: strongSelf.peerId, subject: .phoneNumber(value))) + + actions.tip = .animatedEmoji(text: strongSelf.presentationData.strings.UserInfo_AnonymousNumberInfo, arguments: nil, file: nil, action: { [weak self] in + guard let self else { + return + } + + let _ = (collectibleInfo.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] initialData in + guard let self else { + return + } + if let initialData { + self.view.endEditing(true) + self.controller?.push(self.context.sharedContext.makeCollectibleItemInfoScreen(context: self.context, initialData: initialData)) + } else { + self.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: "https://fragment.com/numbers", forceExternal: true, presentationData: self.presentationData, navigationController: nil, dismissInput: {}) + } + }) + }) + } + let contextController = ContextController(presentationData: strongSelf.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) + strongSelf.controller?.present(contextController, in: .window(.root)) + }) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenStories.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenStories.swift new file mode 100644 index 00000000..ba736db8 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenStories.swift @@ -0,0 +1,138 @@ +import Foundation +import UIKit +import Display +import AccountContext +import StoryContainerScreen +import SwiftSignalKit + +extension PeerInfoScreenNode { + func openStories(fromAvatar: Bool) { + guard let controller = self.controller else { + return + } + if let expiringStoryList = self.expiringStoryList, let expiringStoryListState = self.expiringStoryListState, !expiringStoryListState.items.isEmpty { + if fromAvatar { + StoryContainerScreen.openPeerStories(context: self.context, peerId: self.peerId, parentController: controller, avatarNode: self.headerNode.avatarListNode.avatarContainerNode.avatarNode) + return + } + + let _ = expiringStoryList + let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: self.peerId, singlePeer: true) + let _ = (storyContent.state + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] storyContentState in + guard let self else { + return + } + var transitionIn: StoryContainerScreen.TransitionIn? + + if fromAvatar { + let transitionView = self.headerNode.avatarListNode.avatarContainerNode.avatarNode.view + transitionIn = StoryContainerScreen.TransitionIn( + sourceView: transitionView, + sourceRect: transitionView.bounds, + sourceCornerRadius: transitionView.bounds.height * 0.5, + sourceIsAvatar: true + ) + self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = true + } else if let (expandedStorySetIndicatorTransitionView, subRect) = self.headerNode.avatarListNode.listContainerNode.expandedStorySetIndicatorTransitionView { + transitionIn = StoryContainerScreen.TransitionIn( + sourceView: expandedStorySetIndicatorTransitionView, + sourceRect: subRect, + sourceCornerRadius: expandedStorySetIndicatorTransitionView.bounds.height * 0.5, + sourceIsAvatar: false + ) + expandedStorySetIndicatorTransitionView.isHidden = true + } + + let storyContainerScreen = StoryContainerScreen( + context: self.context, + content: storyContent, + transitionIn: transitionIn, + transitionOut: { [weak self] peerId, _ in + guard let self else { + return nil + } + if !fromAvatar { + self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = false + + if let (expandedStorySetIndicatorTransitionView, subRect) = self.headerNode.avatarListNode.listContainerNode.expandedStorySetIndicatorTransitionView { + return StoryContainerScreen.TransitionOut( + destinationView: expandedStorySetIndicatorTransitionView, + transitionView: StoryContainerScreen.TransitionView( + makeView: { [weak expandedStorySetIndicatorTransitionView] in + let parentView = UIView() + if let copyView = expandedStorySetIndicatorTransitionView?.snapshotContentTree(unhide: true) { + copyView.layer.anchorPoint = CGPoint() + parentView.addSubview(copyView) + } + return parentView + }, + updateView: { copyView, state, transition in + guard let view = copyView.subviews.first else { + return + } + let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress) + transition.setPosition(view: view, position: CGPoint(x: 0.0, y: 0.0)) + transition.setScale(view: view, scale: size.width / state.destinationSize.width) + }, + insertCloneTransitionView: nil + ), + destinationRect: subRect, + destinationCornerRadius: expandedStorySetIndicatorTransitionView.bounds.height * 0.5, + destinationIsAvatar: false, + completed: { [weak self] in + guard let self else { + return + } + + if let (expandedStorySetIndicatorTransitionView, _) = self.headerNode.avatarListNode.listContainerNode.expandedStorySetIndicatorTransitionView { + expandedStorySetIndicatorTransitionView.isHidden = false + } + } + ) + } + + return nil + } + + let transitionView = self.headerNode.avatarListNode.avatarContainerNode.avatarNode.view + return StoryContainerScreen.TransitionOut( + destinationView: transitionView, + transitionView: StoryContainerScreen.TransitionView( + makeView: { [weak transitionView] in + let parentView = UIView() + if let copyView = transitionView?.snapshotContentTree(unhide: true) { + parentView.addSubview(copyView) + } + return parentView + }, + updateView: { copyView, state, transition in + guard let view = copyView.subviews.first else { + return + } + let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress) + transition.setPosition(view: view, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5)) + transition.setScale(view: view, scale: size.width / state.destinationSize.width) + }, + insertCloneTransitionView: nil + ), + destinationRect: transitionView.bounds, + destinationCornerRadius: transitionView.bounds.height * 0.5, + destinationIsAvatar: true, + completed: { [weak self] in + guard let self else { + return + } + self.headerNode.avatarListNode.avatarContainerNode.avatarNode.isHidden = false + } + ) + } + ) + self.controller?.push(storyContainerScreen) + }) + + return + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift new file mode 100644 index 00000000..476ce5a7 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift @@ -0,0 +1,317 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import OpenInExternalAppUI +import PresentationDataUtils +import OverlayStatusController +import HashtagSearchUI +import ShareController +import UndoUI + +extension PeerInfoScreenNode { + func openResolved(_ result: ResolvedUrl) { + guard let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(peerId: self.peerId, message: nil, updatedPresentationData: self.controller?.updatedPresentationData), navigationController: navigationController, forceExternal: false, forceUpdate: false, openPeer: { [weak self] peer, navigation in + guard let strongSelf = self else { + return + } + switch navigation { + case let .chat(inputState, subject, peekData): + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: subject, updateTextInputState: inputState, activateInput: inputState != nil ? .text : nil, keepStack: .always, peekData: peekData)) + case .info: + if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + strongSelf.controller?.push(infoController) + } + } + case let .withBotStartPayload(startPayload): + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botStart: startPayload, keepStack: .always)) + case let .withAttachBot(attachBotStart): + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), attachBotStart: attachBotStart)) + case let .withBotApp(botAppStart): + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botAppStart: botAppStart)) + default: + break + } + }, + sendFile: nil, + sendSticker: nil, + sendEmoji: nil, + requestMessageActionUrlAuth: nil, + joinVoiceChat: { peerId, invite, call in + + }, present: { [weak self] c, a in + self?.controller?.present(c, in: .window(.root), with: a) + }, dismissInput: { [weak self] in + self?.view.endEditing(true) + }, contentContext: nil, progress: nil, completion: nil) + } + + func openUrl(url: String, concealed: Bool, external: Bool, forceExternal: Bool = false, commit: @escaping () -> Void = {}) { + let _ = openUserGeneratedUrl(context: self.context, peerId: self.peerId, url: url, concealed: concealed, present: { [weak self] c in + self?.controller?.present(c, in: .window(.root)) + }, openResolved: { [weak self] tempResolved in + guard let strongSelf = self else { + return + } + + let result: ResolvedUrl = external ? .externalUrl(url) : tempResolved + + strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, forceExternal: forceExternal, forceUpdate: false, openPeer: { peer, navigation in + self?.openPeer(peerId: peer.id, navigation: navigation) + commit() + }, sendFile: nil, + sendSticker: nil, + sendEmoji: nil, + requestMessageActionUrlAuth: nil, + joinVoiceChat: { peerId, invite, call in + + }, + present: { c, a in + self?.controller?.present(c, in: .window(.root), with: a) + }, dismissInput: { + self?.view.endEditing(true) + }, contentContext: nil, progress: nil, completion: nil) + }) + } + + func openUrlIn(_ url: String) { + let actionSheet = OpenInActionSheetController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, item: .url(url: url), openUrl: { [weak self] url in + if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { + strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.presentationData, navigationController: navigationController, dismissInput: { + }) + } + }) + self.controller?.present(actionSheet, in: .window(.root)) + } + + func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default) { + let disposable: MetaDisposable + if let resolvePeerByNameDisposable = self.resolvePeerByNameDisposable { + disposable = resolvePeerByNameDisposable + } else { + disposable = MetaDisposable() + self.resolvePeerByNameDisposable = disposable + } + var resolveSignal = self.context.engine.peers.resolvePeerByName(name: name, referrer: nil, ageLimit: 10) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + + var cancelImpl: (() -> Void)? + let presentationData = self.presentationData + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.controller?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + resolveSignal = resolveSignal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { [weak self] in + self?.resolvePeerByNameDisposable?.set(nil) + } + disposable.set((resolveSignal + |> take(1) + |> mapToSignal { peer -> Signal in + return .single(peer?._asPeer()) + } + |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self { + if let peer = peer { + var navigation = navigation + if case .default = navigation { + if let peer = peer as? TelegramUser, peer.botInfo != nil { + navigation = .chat(textInputState: nil, subject: nil, peekData: nil) + } + } + strongSelf.openResolved(.peer(peer, navigation)) + } else { + strongSelf.controller?.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + } + } + })) + } + + func openHashtag(_ hashtag: String, peerName: String?) { + if self.resolvePeerByNameDisposable == nil { + self.resolvePeerByNameDisposable = MetaDisposable() + } + var resolveSignal: Signal + if let peerName = peerName { + resolveSignal = self.context.engine.peers.resolvePeerByName(name: peerName, referrer: nil) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> mapToSignal { peer -> Signal in + return .single(peer?._asPeer()) + } + } else { + resolveSignal = self.context.account.postbox.loadedPeerWithId(self.peerId) + |> map(Optional.init) + } + var cancelImpl: (() -> Void)? + let presentationData = self.presentationData + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.controller?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + resolveSignal = resolveSignal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { [weak self] in + self?.resolvePeerByNameDisposable?.set(nil) + } + self.resolvePeerByNameDisposable?.set((resolveSignal + |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self, !hashtag.isEmpty { + let searchController = HashtagSearchController(context: strongSelf.context, peer: peer.flatMap(EnginePeer.init), query: hashtag) + strongSelf.controller?.push(searchController) + } + })) + } + + func openUsername(value: String, isMainUsername: Bool, progress: Promise?) { + let url: String + if value.hasPrefix("https://") { + url = value + } else { + url = "https://t.me/\(value)" + } + + let openShare: (TelegramCollectibleItemInfo?) -> Void = { [weak self] collectibleItemInfo in + guard let self else { + return + } + let shareController = ShareController(context: self.context, subject: .url(url), updatedPresentationData: self.controller?.updatedPresentationData, collectibleItemInfo: collectibleItemInfo) + shareController.completed = { [weak self] peerIds in + guard let strongSelf = self else { + return + } + let _ = (strongSelf.context.engine.data.get( + EngineDataList( + peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) + ) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in + guard let strongSelf = self else { + return + } + + let peers = peerList.compactMap { $0 } + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + + let text: String + var savedMessages = false + if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId { + text = presentationData.strings.UserInfo_LinkForwardTooltip_SavedMessages_One + savedMessages = true + } else { + if peers.count == 1, let peer = peers.first { + let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.UserInfo_LinkForwardTooltip_Chat_One(peerName).string + } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { + let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.UserInfo_LinkForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string + } else if let peer = peers.first { + let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.UserInfo_LinkForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string + } else { + text = "" + } + } + + strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in + if savedMessages, let self, action == .info { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + guard let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) + }) + } + return false + }), in: .current) + }) + } + shareController.actionCompleted = { [weak self] in + if let strongSelf = self { + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + } + self.view.endEditing(true) + self.controller?.present(shareController, in: .window(.root)) + } + + if let pathComponents = URL(string: url)?.pathComponents, pathComponents.count >= 2, !pathComponents[1].isEmpty { + let namePart = pathComponents[1] + progress?.set(.single(true)) + let _ = (self.context.sharedContext.makeCollectibleItemInfoScreenInitialData(context: self.context, peerId: self.peerId, subject: .username(namePart)) + |> deliverOnMainQueue).start(next: { [weak self] initialData in + guard let self else { + return + } + + progress?.set(.single(false)) + + if let initialData { + if isMainUsername { + openShare(initialData.collectibleItemInfo) + } else { + self.view.endEditing(true) + self.controller?.push(self.context.sharedContext.makeCollectibleItemInfoScreen(context: self.context, initialData: initialData)) + } + } else { + openShare(nil) + } + }) + } else { + openShare(nil) + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenUsername.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenUsername.swift new file mode 100644 index 00000000..b9ee8fea --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenUsername.swift @@ -0,0 +1,57 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import AsyncDisplayKit +import ContextUI +import UndoUI + +extension PeerInfoScreenNode { + func openUsernameContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { + guard let sourceNode = node as? ContextExtractedContentContainingNode else { + return + } + guard let peer = self.data?.peer else { + return + } + guard let username = peer.addressName else { + return + } + + let copyAction = { [weak self] in + guard let self else { + return + } + UIPasteboard.general.string = "@\(username)" + + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: self.presentationData.strings.Conversation_UsernameCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + + var items: [ContextMenuItem] = [] + + if self.isMyProfile { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_UsernameActionEdit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { + guard let self else { + return + } + self.openSettings(section: .username) + } + }))) + } + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.MyProfile_UsernameActionCopy, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { + copyAction() + } + }))) + + let actions = ContextController.Items(content: .list(items)) + + let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) + self.controller?.present(contextController, in: .window(.root)) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift new file mode 100644 index 00000000..8ac338ed --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift @@ -0,0 +1,1214 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import UndoUI +import ContextUI +import TelegramPresentationData +import NotificationPeerExceptionController +import NotificationExceptionsScreen +import ShareController +import TranslateUI + +extension PeerInfoScreenNode { + func performButtonAction(key: PeerInfoHeaderButtonKey, gesture: ContextGesture?) { + guard let controller = self.controller else { + return + } + switch key { + case .message: + if let navigationController = controller.navigationController as? NavigationController, let peer = self.data?.peer { + if let channel = peer as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId { + Task { @MainActor [weak self] in + guard let self else { + return + } + + guard let peer = await self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId)).get() else { + return + } + + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default)) + } + } else { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in + if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { controller in + if controller is PeerInfoScreen { + return false + } + return true + } + navigationController.setViewControllers(viewControllers, animated: false) + } + })) + } + } + case .discussion: + if let cachedData = self.data?.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: linkedDiscussionPeerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] linkedDiscussionPeer in + guard let self, let linkedDiscussionPeer else { + return + } + if let navigationController = controller.navigationController as? NavigationController { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(linkedDiscussionPeer))) + } + }) + } + case .call: + self.requestCall(isVideo: false) + case .videoCall: + self.requestCall(isVideo: true) + case .voiceChat: + self.requestCall(isVideo: false, gesture: gesture) + case .mute: + var displayCustomNotificationSettings = false + + let chatIsMuted = peerInfoIsChatMuted(peer: self.data?.peer, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings) + if chatIsMuted { + } else { + displayCustomNotificationSettings = true + } + if self.data?.threadData == nil, let channel = self.data?.peer as? TelegramChannel, channel.isForumOrMonoForum { + displayCustomNotificationSettings = true + } + + let peerId = self.data?.peer?.id ?? self.peerId + + if !displayCustomNotificationSettings { + let _ = self.context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: self.chatLocation.threadId, muteInterval: 0).startStandalone() + + let iconColor: UIColor = .white + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: self.presentationData.strings.PeerInfo_TooltipUnmuted, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + } else { + self.state = self.state.withHighlightedButton(.mute) + if let (layout, navigationHeight) = self.validLayout { + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_MuteFor, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Mute2d"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + guard let strongSelf = self else { + return + } + var subItems: [ContextMenuItem] = [] + + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { c, _ in + c?.popItems() + }))) + subItems.append(.separator) + + let presetValues: [Int32] = [ + 1 * 60 * 60, + 8 * 60 * 60, + 1 * 24 * 60 * 60, + 7 * 24 * 60 * 60 + ] + + for value in presetValues { + subItems.append(.action(ContextMenuActionItem(text: muteForIntervalString(strings: strongSelf.presentationData.strings, value: value), icon: { _ in + return nil + }, action: { _, f in + f(.default) + + let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: strongSelf.chatLocation.threadId, muteInterval: value).startStandalone() + + strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedFor(mutedForTimeIntervalString(strings: strongSelf.presentationData.strings, value: value)).string, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } + + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_MuteForCustom, icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.openCustomMute() + }))) + + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + }))) + + items.append(.separator) + + var isSoundEnabled = true + let notificationSettings = self.data?.threadNotificationSettings ?? self.data?.peerNotificationSettings + if let notificationSettings { + switch notificationSettings.messageSound { + case .none: + isSoundEnabled = false + default: + break + } + } + + if !chatIsMuted { + if !isSoundEnabled { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_EnableSound, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + guard let strongSelf = self else { + return + } + let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: strongSelf.chatLocation.threadId, sound: .default).startStandalone() + + strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_sound_on", scale: 0.056, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipSoundEnabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } else { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_DisableSound, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOff"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + guard let strongSelf = self else { + return + } + let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: strongSelf.chatLocation.threadId, sound: .none).startStandalone() + + strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_sound_off", scale: 0.056, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipSoundDisabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } + } + + let context = self.context + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_NotificationsCustomize, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.NotificationSettings.Global() + ) + |> deliverOnMainQueue).startStandalone(next: { globalSettings in + guard let strongSelf = self, let peer = strongSelf.data?.peer else { + return + } + let threadId = strongSelf.chatLocation.threadId + + let context = strongSelf.context + let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue + } + + let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in + return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue + } + + let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal = { + peerId, displayPreviews in + return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue + } + + let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal = { + peerId, mute in + return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue + } + + let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal = { + peerId, hideSender in + return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue + } + + let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue + } + + let mode: NotificationExceptionMode + let defaultSound: PeerMessageSound + if let _ = peer as? TelegramUser { + mode = .users([:]) + defaultSound = globalSettings.privateChats.sound._asMessageSound() + } else if let _ = peer as? TelegramSecretChat { + mode = .users([:]) + defaultSound = globalSettings.privateChats.sound._asMessageSound() + } else if let channel = peer as? TelegramChannel { + if case .broadcast = channel.info { + mode = .channels([:]) + defaultSound = globalSettings.channels.sound._asMessageSound() + } else { + mode = .groups([:]) + defaultSound = globalSettings.groupChats.sound._asMessageSound() + } + } else { + mode = .groups([:]) + defaultSound = globalSettings.groupChats.sound._asMessageSound() + } + let _ = mode + + let canRemove = false + + let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: EnginePeer(peer), threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: globalSettings.privateChats.storySettings.sound, edit: true, updatePeerSound: { peerId, sound in + let _ = (updatePeerSound(peer.id, sound) + |> deliverOnMainQueue).startStandalone(next: { _ in + }) + }, updatePeerNotificationInterval: { peerId, muteInterval in + let _ = (updatePeerNotificationInterval(peerId, muteInterval) + |> deliverOnMainQueue).startStandalone(next: { _ in + guard let strongSelf = self else { + return + } + if let muteInterval = muteInterval, muteInterval == Int32.max { + let iconColor: UIColor = .white + strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + } + }) + }, updatePeerDisplayPreviews: { peerId, displayPreviews in + let _ = (updatePeerDisplayPreviews(peerId, displayPreviews) + |> deliverOnMainQueue).startStandalone(next: { _ in + + }) + }, updatePeerStoriesMuted: { peerId, mute in + let _ = (updatePeerStoriesMuted(peerId, mute) + |> deliverOnMainQueue).startStandalone() + }, updatePeerStoriesHideSender: { peerId, hideSender in + let _ = (updatePeerStoriesHideSender(peerId, hideSender) + |> deliverOnMainQueue).startStandalone() + }, updatePeerStorySound: { peerId, sound in + let _ = (updatePeerStorySound(peer.id, sound) + |> deliverOnMainQueue).startStandalone() + }, removePeerFromExceptions: { + }, modifiedPeer: { + }) + exceptionController.navigationPresentation = .modal + controller.push(exceptionController) + }) + }))) + + if chatIsMuted { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_ButtonUnmute, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unmute"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + + let _ = self.context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: self.chatLocation.threadId, muteInterval: 0).startStandalone() + + let iconColor: UIColor = .white + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: self.presentationData.strings.PeerInfo_TooltipUnmuted, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } else { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_MuteForever, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Muted"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, f in + f(.default) + + guard let strongSelf = self else { + return + } + + let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: strongSelf.chatLocation.threadId, muteInterval: Int32.max).startStandalone() + + let iconColor: UIColor = .white + strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } + + var tip: ContextController.Tip? + tip = nil + if !self.forumTopicNotificationExceptions.isEmpty { + items.append(.separator) + + let text: String = self.presentationData.strings.PeerInfo_TopicNotificationExceptions(Int32(self.forumTopicNotificationExceptions.count)) + + items.append(.action(ContextMenuActionItem( + text: text, + textLayout: .multiline, + textFont: .small, + parseMarkdown: true, + badge: nil, + icon: { _ in + return nil + }, + action: { [weak self] _, f in + guard let self else { + return + } + f(.default) + self.controller?.push(threadNotificationExceptionsScreen(context: self.context, peerId: self.peerId, notificationExceptions: self.forumTopicNotificationExceptions, updated: { [weak self] value in + guard let self else { + return + } + self.forumTopicNotificationExceptions = value + })) + } + ))) + } + + self.view.endEditing(true) + + if let sourceNode = self.headerNode.buttonNodes[.mute]?.referenceNode { + let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items), tip: tip)), gesture: gesture) + contextController.dismissed = { [weak self] in + if let strongSelf = self { + strongSelf.state = strongSelf.state.withHighlightedButton(nil) + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + } + } + controller.presentInGlobalOverlay(contextController) + } + } + case .more: + guard let data = self.data, let peer = data.peer, let chatPeer = data.chatPeer else { + return + } + let presentationData = self.presentationData + self.state = self.state.withHighlightedButton(.more) + if let (layout, navigationHeight) = self.validLayout { + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + + var mainItemsImpl: (() -> Signal<[ContextMenuItem], NoError>)? + mainItemsImpl = { [weak self] in + var items: [ContextMenuItem] = [] + guard let strongSelf = self else { + return .single(items) + } + + let allHeaderButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: false, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false, threadInfo: data.threadData?.info)) + let headerButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: true, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false, threadInfo: strongSelf.data?.threadData?.info)) + + let filteredButtons = allHeaderButtons.subtracting(headerButtons) + + var currentAutoremoveTimeout: Int32? + if let cachedData = data.cachedData as? CachedUserData { + switch cachedData.autoremoveTimeout { + case let .known(value): + currentAutoremoveTimeout = value?.peerValue + case .unknown: + break + } + } else if let cachedData = data.cachedData as? CachedGroupData { + switch cachedData.autoremoveTimeout { + case let .known(value): + currentAutoremoveTimeout = value?.peerValue + case .unknown: + break + } + } else if let cachedData = data.cachedData as? CachedChannelData { + switch cachedData.autoremoveTimeout { + case let .known(value): + currentAutoremoveTimeout = value?.peerValue + case .unknown: + break + } + } + + var canSetupAutoremoveTimeout = false + + if let secretChat = chatPeer as? TelegramSecretChat { + currentAutoremoveTimeout = secretChat.messageAutoremoveTimeout + canSetupAutoremoveTimeout = false + } else if let group = chatPeer as? TelegramGroup { + if !group.hasBannedPermission(.banChangeInfo) { + canSetupAutoremoveTimeout = true + } + } else if let user = chatPeer as? TelegramUser { + if user.id != strongSelf.context.account.peerId { + canSetupAutoremoveTimeout = true + } + } else if let channel = chatPeer as? TelegramChannel { + if channel.hasPermission(.changeInfo) { + canSetupAutoremoveTimeout = true + } + } + + if filteredButtons.contains(.call) { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ButtonCall, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + self?.requestCall(isVideo: false) + }))) + } + if filteredButtons.contains(.search) { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatSearch_SearchPlaceholder, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + self?.openChatWithMessageSearch() + }))) + } + + var hasDiscussion = false + if let channel = chatPeer as? TelegramChannel { + switch channel.info { + case let .broadcast(info): + hasDiscussion = info.flags.contains(.hasDiscussionGroup) + case .group: + hasDiscussion = false + } + } + if !headerButtons.contains(.discussion) && hasDiscussion { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ViewDiscussion, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + self?.performButtonAction(key: .discussion, gesture: nil) + }))) + } + + if let user = peer as? TelegramUser { + if user.botInfo == nil && strongSelf.data?.encryptionKeyFingerprint == nil && !user.isDeleted { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ChangeWallpaper, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) + + self?.openChatForThemeChange() + }))) + } + + if let _ = user.botInfo { + if user.addressName != nil { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ShareBot, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + self?.openShareBot() + }))) + } + + var addedPrivacy = false + var privacyPolicyUrl: String? + if let cachedData = (data.cachedData as? CachedUserData), let botInfo = cachedData.botInfo { + if let url = botInfo.privacyPolicyUrl { + privacyPolicyUrl = url + } else if botInfo.commands.contains(where: { $0.text == "privacy" }) { + + } else { + privacyPolicyUrl = presentationData.strings.WebApp_PrivacyPolicy_URL + } + } + if let privacyPolicyUrl { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotPrivacy, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + guard let self else { + return + } + self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: privacyPolicyUrl, forceExternal: false, presentationData: self.presentationData, navigationController: self.controller?.navigationController as? NavigationController, dismissInput: {}) + }))) + addedPrivacy = true + } + if let cachedData = data.cachedData as? CachedUserData, let botInfo = cachedData.botInfo { + for command in botInfo.commands { + if command.text == "settings" { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotSettings, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Bots"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + self?.performBotCommand(command: .settings) + }))) + } else if command.text == "help" { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotHelp, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Help"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + self?.performBotCommand(command: .help) + }))) + } else if command.text == "privacy" && !addedPrivacy { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotPrivacy, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + self?.performBotCommand(command: .privacy) + }))) + } + } + } + } + + if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) { + if let cachedUserData = strongSelf.data?.cachedData as? CachedUserData, let _ = cachedUserData.sendPaidMessageStars { + + } else { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_StartSecretChat, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Lock"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) + + self?.openStartSecretChat() + }))) + } + } + + if user.botInfo == nil && data.isContact, let peer = strongSelf.data?.peer as? TelegramUser, let phone = peer.phone { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_ShareContactButton, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + if let strongSelf = self { + let contact = TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: phone, peerId: peer.id, vCardData: nil) + let shareController = ShareController(context: strongSelf.context, subject: .media(.standalone(media: contact), nil), updatedPresentationData: strongSelf.controller?.updatedPresentationData) + shareController.completed = { [weak self] peerIds in + if let strongSelf = self { + let _ = (strongSelf.context.engine.data.get( + EngineDataList( + peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) + ) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in + guard let strongSelf = self else { + return + } + + let peers = peerList.compactMap { $0 } + + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + + let text: String + var savedMessages = false + if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId { + text = presentationData.strings.UserInfo_ContactForwardTooltip_SavedMessages_One + savedMessages = true + } else { + if peers.count == 1, let peer = peers.first { + let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.UserInfo_ContactForwardTooltip_Chat_One(peerName).string + } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { + let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.UserInfo_ContactForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string + } else if let peer = peers.first { + let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.UserInfo_ContactForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string + } else { + text = "" + } + } + + strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in + if savedMessages, let self, action == .info { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + guard let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) + }) + } + return false + }), in: .current) + }) + } + } + strongSelf.controller?.present(shareController, in: .window(.root)) + } + }))) + } + + if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser, !user.isDeleted && user.botInfo == nil && !user.flags.contains(.isSupport) { + if let cachedData = data.cachedData as? CachedUserData, cachedData.disallowedGifts == .All { + } else { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_SendGift, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + if let self { + self.openPremiumGift() + } + }))) + } + } + + if let cachedData = data.cachedData as? CachedUserData, canTranslateChats(context: strongSelf.context), cachedData.flags.contains(.translationHidden) { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + if let strongSelf = self { + let _ = updateChatTranslationStateInteractively(engine: strongSelf.context.engine, peerId: strongSelf.peerId, threadId: nil, { current in + return current?.withIsEnabled(true) + }).startStandalone() + + Queue.mainQueue().after(0.2, { + let _ = (strongSelf.context.engine.messages.togglePeerMessagesTranslationHidden(peerId: strongSelf.peerId, hidden: false) + |> deliverOnMainQueue).startStandalone(completed: { [weak self] in + self?.openChatForTranslation() + }) + }) + } + }))) + } + + let itemsCount = items.count + + if canSetupAutoremoveTimeout { + let strings = strongSelf.presentationData.strings + items.append(.action(ContextMenuActionItem(text: currentAutoremoveTimeout == nil ? strongSelf.presentationData.strings.PeerInfo_EnableAutoDelete : strongSelf.presentationData.strings.PeerInfo_AdjustAutoDelete, icon: { theme in + if let currentAutoremoveTimeout = currentAutoremoveTimeout { + let text = NSAttributedString(string: shortTimeIntervalString(strings: strings, value: currentAutoremoveTimeout), font: Font.regular(14.0), textColor: theme.contextMenu.primaryColor) + let bounds = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + return generateImage(bounds.size.integralFloor, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + text.draw(in: bounds) + UIGraphicsPopContext() + }) + } else { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor) + } + }, action: { [weak self] c, _ in + var subItems: [ContextMenuItem] = [] + + subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { c, _ in + c?.popItems() + }))) + subItems.append(.separator) + + let presetValues: [Int32] = [ + 1 * 24 * 60 * 60, + 7 * 24 * 60 * 60, + 31 * 24 * 60 * 60 + ] + + for value in presetValues { + subItems.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: strings, value: value), icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.setAutoremove(timeInterval: value) + }))) + } + + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteSettingOther, icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.openAutoremove(currentValue: currentAutoremoveTimeout) + }))) + + if let _ = currentAutoremoveTimeout { + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteDisable, textColor: .destructive, icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.setAutoremove(timeInterval: nil) + }))) + } + + subItems.append(.separator) + + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in + return nil + }, textLinkAction: { [weak c] in + c?.dismiss(completion: nil) + + guard let self else { + return + } + self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, forceUpdate: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in + guard let self else { + return + } + self.controller?.view.endEditing(true) + }, contentContext: nil, progress: nil, completion: nil) + }, action: nil as ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + }))) + } + + let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: user, chatPeer: chatPeer, cachedData: strongSelf.data?.cachedData) + if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil { + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) + }, action: { c, _ in + if let c { + self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: user, chatPeer: user) + } + }))) + } + + if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) { + if data.isContact { + if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked { + } else { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_BlockUser, textColor: .destructive, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.destructiveColor) + }, action: { _, f in + f(.dismissWithoutContent) + + self?.updateBlocked(block: true) + }))) + } + } + } else if strongSelf.peerId.namespace == Namespaces.Peer.SecretChat && data.isContact { + if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked { + } else { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_BlockUser, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.updateBlocked(block: true) + }))) + } + } + + let finalItemsCount = items.count + + if finalItemsCount > itemsCount { + items.insert(.separator, at: itemsCount) + } + } else if let channel = peer as? TelegramChannel { + if let cachedData = strongSelf.data?.cachedData as? CachedChannelData { + if case .broadcast = channel.info, cachedData.flags.contains(.starGiftsAvailable) { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_SendGift, badge: nil, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openPremiumGift() + }))) + } + + let boostTitle: String + switch channel.info { + case .group: + boostTitle = presentationData.strings.PeerInfo_Group_Boost + case .broadcast: + boostTitle = presentationData.strings.PeerInfo_Channel_Boost + } + items.append(.action(ContextMenuActionItem(text: boostTitle, badge: nil, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Boost"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openBoost() + }))) + + if channel.hasPermission(.editStories) { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_Channel_ArchivedStories, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openStoryArchive() + }))) + } + if cachedData.flags.contains(.canViewStats) { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_Stats, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openStats(section: .stats) + }))) + } + if cachedData.flags.contains(.translationHidden) { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + if let strongSelf = self { + let _ = updateChatTranslationStateInteractively(engine: strongSelf.context.engine, peerId: strongSelf.peerId, threadId: nil, { current in + return current?.withIsEnabled(true) + }).startStandalone() + + Queue.mainQueue().after(0.2, { + let _ = (strongSelf.context.engine.messages.togglePeerMessagesTranslationHidden(peerId: strongSelf.peerId, hidden: false) + |> deliverOnMainQueue).startStandalone(completed: { [weak self] in + self?.openChatForTranslation() + }) + }) + } + }))) + } + } + + var canReport = true + if channel.adminRights != nil { + canReport = false + } + if channel.flags.contains(.isCreator) { + canReport = false + } + if canReport { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.ReportPeer_Report, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + self?.openReport(type: .default, contextController: c, backAction: { c in + if let mainItemsImpl = mainItemsImpl { + c.setItems(mainItemsImpl() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + } + }) + }))) + } + + if canSetupAutoremoveTimeout { + let strings = strongSelf.presentationData.strings + items.append(.action(ContextMenuActionItem(text: currentAutoremoveTimeout == nil ? strongSelf.presentationData.strings.PeerInfo_EnableAutoDelete : strongSelf.presentationData.strings.PeerInfo_AdjustAutoDelete, icon: { theme in + if let currentAutoremoveTimeout = currentAutoremoveTimeout { + let text = NSAttributedString(string: shortTimeIntervalString(strings: strings, value: currentAutoremoveTimeout), font: Font.regular(14.0), textColor: theme.contextMenu.primaryColor) + let bounds = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + return generateImage(bounds.size.integralFloor, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + text.draw(in: bounds) + UIGraphicsPopContext() + }) + } else { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor) + } + }, action: { [weak self] c, _ in + var subItems: [ContextMenuItem] = [] + + subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { c, _ in + c?.popItems() + }))) + subItems.append(.separator) + + let presetValues: [Int32] = [ + 1 * 24 * 60 * 60, + 7 * 24 * 60 * 60, + 31 * 24 * 60 * 60 + ] + + for value in presetValues { + subItems.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: strings, value: value), icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.setAutoremove(timeInterval: value) + }))) + } + + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteSettingOther, icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.openAutoremove(currentValue: currentAutoremoveTimeout) + }))) + + if let _ = currentAutoremoveTimeout { + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteDisable, textColor: .destructive, icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.setAutoremove(timeInterval: nil) + }))) + } + + subItems.append(.separator) + + let baseText: String + if case .broadcast = channel.info { + baseText = strongSelf.presentationData.strings.PeerInfo_ChannelAutoDeleteInfo + } else { + baseText = strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + } + + subItems.append(.action(ContextMenuActionItem(text: baseText + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in + return nil + }, textLinkAction: { [weak c] in + c?.dismiss(completion: nil) + + guard let self else { + return + } + self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, forceUpdate: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in + guard let self else { + return + } + self.controller?.view.endEditing(true) + }, contentContext: nil, progress: nil, completion: nil) + }, action: nil as ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + }))) + } + + let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: channel, chatPeer: channel, cachedData: strongSelf.data?.cachedData) + if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil { + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) + }, action: { c, _ in + if let c { + self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: channel, chatPeer: channel) + } + }))) + } + + switch channel.info { + case .broadcast: + if case .member = channel.participationStatus, !headerButtons.contains(.leave) { + if !items.isEmpty { + items.append(.separator) + } + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Channel_LeaveChannel, textColor: .destructive, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Logout"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openLeavePeer(delete: false) + }))) + } + case .group: + if case .member = channel.participationStatus, !headerButtons.contains(.leave) { + if !items.isEmpty { + items.append(.separator) + } + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_LeaveGroup, textColor: .primary, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Logout"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openLeavePeer(delete: false) + }))) + if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canDeleteHistory) { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_DeleteGroup, textColor: .destructive, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openLeavePeer(delete: true) + }))) + } + } + } + } else if let group = peer as? TelegramGroup { + if canSetupAutoremoveTimeout { + let strings = strongSelf.presentationData.strings + items.append(.action(ContextMenuActionItem(text: currentAutoremoveTimeout == nil ? strongSelf.presentationData.strings.PeerInfo_EnableAutoDelete : strongSelf.presentationData.strings.PeerInfo_AdjustAutoDelete, icon: { theme in + if let currentAutoremoveTimeout = currentAutoremoveTimeout { + let text = NSAttributedString(string: shortTimeIntervalString(strings: strings, value: currentAutoremoveTimeout), font: Font.regular(14.0), textColor: theme.contextMenu.primaryColor) + let bounds = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + return generateImage(bounds.size.integralFloor, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + text.draw(in: bounds) + UIGraphicsPopContext() + }) + } else { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor) + } + }, action: { [weak self] c, _ in + var subItems: [ContextMenuItem] = [] + + subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { c, _ in + c?.popItems() + }))) + subItems.append(.separator) + + let presetValues: [Int32] = [ + 1 * 24 * 60 * 60, + 7 * 24 * 60 * 60, + 31 * 24 * 60 * 60 + ] + + for value in presetValues { + subItems.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: strings, value: value), icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.setAutoremove(timeInterval: value) + }))) + } + + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteSettingOther, icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.openAutoremove(currentValue: currentAutoremoveTimeout) + }))) + + if let _ = currentAutoremoveTimeout { + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteDisable, textColor: .destructive, icon: { _ in + return nil + }, action: { _, f in + f(.default) + + self?.setAutoremove(timeInterval: nil) + }))) + } + + subItems.append(.separator) + + subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in + return nil + }, textLinkAction: { [weak c] in + c?.dismiss(completion: nil) + + guard let self else { + return + } + self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, forceUpdate: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in + guard let self else { + return + } + self.controller?.view.endEditing(true) + }, contentContext: nil, progress: nil, completion: nil) + }, action: nil as ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + }))) + } + + if let cachedData = data.cachedData as? CachedGroupData, cachedData.flags.contains(.translationHidden) { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + if let strongSelf = self { + let _ = updateChatTranslationStateInteractively(engine: strongSelf.context.engine, peerId: strongSelf.peerId, threadId: nil, { current in + return current?.withIsEnabled(true) + }).startStandalone() + + Queue.mainQueue().after(0.2, { + let _ = (strongSelf.context.engine.messages.togglePeerMessagesTranslationHidden(peerId: strongSelf.peerId, hidden: false) + |> deliverOnMainQueue).startStandalone(completed: { [weak self] in + self?.openChatForTranslation() + }) + }) + } + }))) + } + + var canReport = true + if case .creator = group.role { + canReport = false + } + if canReport { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.ReportPeer_Report, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + self?.openReport(type: .default, contextController: c, backAction: { c in + if let mainItemsImpl = mainItemsImpl { + c.setItems(mainItemsImpl() |> map { ContextController.Items(content: .list($0)) }, minHeight: nil, animated: true) + } + }) + }))) + } + + let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: group, chatPeer: group, cachedData: strongSelf.data?.cachedData) + if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil { + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) + }, action: { c, _ in + if let c { + self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: group, chatPeer: group) + } + }))) + } + + if case .Member = group.membership, !headerButtons.contains(.leave) { + if !items.isEmpty { + items.append(.separator) + } + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_LeaveGroup, textColor: .destructive, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Logout"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openLeavePeer(delete: false) + }))) + + if case .creator = group.role { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_DeleteGroup, textColor: .destructive, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + self?.openLeavePeer(delete: true) + }))) + } + } + } + + return .single(items) + } + + self.view.endEditing(true) + + if let sourceNode = self.headerNode.buttonNodes[.more]?.referenceNode { + let items = mainItemsImpl?() ?? .single([]) + let contextController = ContextController(presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + contextController.dismissed = { [weak self] in + if let strongSelf = self { + strongSelf.state = strongSelf.state.withHighlightedButton(nil) + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + } + } + controller.presentInGlobalOverlay(contextController) + } + case .addMember: + self.openAddMember() + case .search: + self.openChatWithMessageSearch() + case .leave: + self.openLeavePeer(delete: false) + case .stop: + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: self.presentationData.strings.PeerInfo_BotBlockedTitle, text: self.presentationData.strings.PeerInfo_BotBlockedText, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + self.updateBlocked(block: true) + case .addContact: + self.openAddContact() + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenSettingsActions.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenSettingsActions.swift new file mode 100644 index 00000000..be447bae --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenSettingsActions.swift @@ -0,0 +1,356 @@ +import Foundation +import UIKit +import Display +import AccountContext +import SwiftSignalKit +import Postbox +import TelegramCore +import SettingsUI +import PeerInfoStoryGridScreen +import CallListUI +import PassportUI +import AccountUtils +import OverlayStatusController +import PremiumUI +import TelegramPresentationData +import PresentationDataUtils +import PasswordSetupUI + +extension PeerInfoScreenNode { + func openSettings(section: PeerInfoSettingsSection) { + let push: (ViewController) -> Void = { [weak self] c in + guard let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController else { + return + } + + if strongSelf.isMyProfile { + navigationController.pushViewController(c) + } else { + var updatedControllers = navigationController.viewControllers + for controller in navigationController.viewControllers.reversed() { + if controller !== strongSelf && !(controller is TabBarController) { + updatedControllers.removeLast() + } else { + break + } + } + updatedControllers.append(c) + + var animated = true + if let validLayout = strongSelf.validLayout?.0, case .regular = validLayout.metrics.widthClass { + animated = false + } + navigationController.setViewControllers(updatedControllers, animated: animated) + } + } + switch section { + case .avatar: + self.controller?.openAvatarForEditing() + case .edit: + self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) + case .proxy: + self.controller?.push(proxySettingsController(context: self.context)) + case .profile: + self.controller?.push(PeerInfoScreenImpl( + context: self.context, + updatedPresentationData: self.controller?.updatedPresentationData, + peerId: self.context.account.peerId, + avatarInitiallyExpanded: false, + isOpenedFromChat: false, + nearbyPeerDistance: nil, + reactionSourceMessageId: nil, + callMessages: [], + isMyProfile: true, + profileGiftsContext: self.data?.profileGiftsContext + )) + case .stories: + push(PeerInfoStoryGridScreen(context: self.context, peerId: self.context.account.peerId, scope: .saved)) + case .savedMessages: + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + guard let self, let peer = peer else { + return + } + if let controller = self.controller, let navigationController = controller.navigationController as? NavigationController { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + } + }) + case .recentCalls: + push(CallListController(context: context, mode: .navigation)) + case .devices: + let _ = (self.activeSessionsContextAndCount.get() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] activeSessionsContextAndCount in + if let strongSelf = self, let activeSessionsContextAndCount = activeSessionsContextAndCount { + let (activeSessionsContext, _, webSessionsContext) = activeSessionsContextAndCount + push(recentSessionsController(context: strongSelf.context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false)) + } + }) + case .chatFolders: + let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: false, scrollToTags: false, dismissed: nil) + push(controller) + case .notificationsAndSounds: + if let settings = self.data?.globalSettings { + push(notificationsAndSoundsController(context: self.context, exceptionsList: settings.notificationExceptions)) + } + case .privacyAndSecurity: + if let settings = self.data?.globalSettings { + let _ = (combineLatest(self.blockedPeers.get(), self.hasTwoStepAuth.get()) + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] blockedPeersContext, hasTwoStepAuth in + if let strongSelf = self { + let loginEmailPattern = strongSelf.twoStepAuthData.get() |> map { data -> String? in + return data?.loginEmailPattern + } + push(privacyAndSecurityController(context: strongSelf.context, initialSettings: settings.privacySettings, updatedSettings: { [weak self] settings in + self?.privacySettings.set(.single(settings)) + }, updatedBlockedPeers: { [weak self] blockedPeersContext in + self?.blockedPeers.set(.single(blockedPeersContext)) + }, updatedHasTwoStepAuth: { [weak self] hasTwoStepAuthValue in + self?.hasTwoStepAuth.set(.single(hasTwoStepAuthValue)) + }, focusOnItemTag: nil, activeSessionsContext: settings.activeSessionsContext, webSessionsContext: settings.webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth, loginEmailPattern: loginEmailPattern, updatedTwoStepAuthData: { [weak self] in + if let strongSelf = self { + strongSelf.twoStepAuthData.set( + strongSelf.context.engine.auth.twoStepAuthData() + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + ) + } + }, requestPublicPhotoSetup: { [weak self] completion in + if let self { + self.controller?.openAvatarForEditing(mode: .fallback, completion: completion) + } + }, requestPublicPhotoRemove: { [weak self] completion in + if let self { + self.controller?.openAvatarRemoval(mode: .fallback, completion: completion) + } + })) + } + }) + } + case .passwordSetup: + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.6, execute: { [weak self] in + guard let self else { + return + } + let _ = self.context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPassword.id).startStandalone() + }) + + let controller = self.context.sharedContext.makeSetupTwoFactorAuthController(context: self.context) + push(controller) + case .dataAndStorage: + push(dataAndStorageController(context: self.context)) + case .appearance: + push(themeSettingsController(context: self.context)) + case .language: + push(LocalizationListController(context: self.context)) + case .premium: + let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .settings, forceDark: false, dismissed: nil) + self.controller?.push(controller) + case .premiumGift: + guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { + return + } + let _ = (self.context.account.stateManager.contactBirthdays + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] birthdays in + guard let self else { + return + } + let giftsController = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .settings(birthdays), completion: nil) + self.controller?.push(giftsController) + }) + case .stickers: + if let settings = self.data?.globalSettings { + push(installedStickerPacksController(context: self.context, mode: .general, archivedPacks: settings.archivedStickerPacks, updatedPacks: { [weak self] packs in + self?.archivedPacks.set(.single(packs)) + })) + } + case .passport: + self.controller?.push(SecureIdAuthController(context: self.context, mode: .list)) + case .watch: + push(watchSettingsController(context: self.context)) + case .support: + let supportPeer = Promise() + supportPeer.set(context.engine.peers.supportPeerId()) + + self.controller?.present(textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.Settings_FAQ_Intro, actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_FAQ_Button, action: { [weak self] in + self?.openFaq() + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { [weak self] in + guard let self else { + return + } + self.supportPeerDisposable.set((supportPeer.get() |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak self] peerId in + if let strongSelf = self, let peerId = peerId { + push(strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil)) + } + })) + })]), in: .window(.root)) + case .faq: + self.openFaq() + case .tips: + self.openTips() + case .phoneNumber: + guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { + return + } + if let user = self.data?.peer as? TelegramUser, let phoneNumber = user.phone { + let introController = PrivacyIntroController(context: self.context, mode: .changePhoneNumber(phoneNumber), proceedAction: { [weak self] in + if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { + navigationController.replaceTopController(ChangePhoneNumberController(context: strongSelf.context), animated: true) + } + }) + push(introController) + } + case .username: + guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { + return + } + push(usernameSetupController(context: self.context)) + case .addAccount: + let _ = (activeAccountsAndPeers(context: context) + |> take(1) + |> deliverOnMainQueue + ).startStandalone(next: { [weak self] accountAndPeer, accountsAndPeers in + guard let strongSelf = self else { + return + } + var maximumAvailableAccounts: Int = 3 + if accountAndPeer?.1.isPremium == true && !strongSelf.context.account.testingEnvironment { + maximumAvailableAccounts = 4 + } + var count: Int = 1 + for (accountContext, peer, _) in accountsAndPeers { + if !accountContext.account.testingEnvironment { + if peer.isPremium { + maximumAvailableAccounts = 4 + } + count += 1 + } + } + + if count >= maximumAvailableAccounts { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: strongSelf.context, subject: .accounts, count: Int32(count), action: { + let controller = PremiumIntroScreen(context: strongSelf.context, source: .accounts) + replaceImpl?(controller) + return true + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { + navigationController.pushViewController(controller) + } + } else { + strongSelf.context.sharedContext.beginNewAuth(testingEnvironment: strongSelf.context.account.testingEnvironment) + } + }) + case .logout: + if let user = self.data?.peer as? TelegramUser, let phoneNumber = user.phone { + if let controller = self.controller, let navigationController = controller.navigationController as? NavigationController { + self.controller?.push(logoutOptionsController(context: self.context, navigationController: navigationController, canAddAccounts: true, phoneNumber: phoneNumber)) + } + } + case .rememberPassword: + let context = self.context + let controller = TwoFactorDataInputScreen(sharedContext: self.context.sharedContext, engine: .authorized(self.context.engine), mode: .rememberPassword(doneText: self.presentationData.strings.TwoFactorSetup_Done_Action), stateUpdated: { _ in + }, presentation: .modalInLargeLayout) + controller.twoStepAuthSettingsController = { configuration in + return twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: false, data: .single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: configuration, password: nil))))) + } + controller.passwordRemembered = { + let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.validatePassword.id).startStandalone() + } + push(controller) + case .emojiStatus: + self.headerNode.invokeDisplayPremiumIntro() + case .profileColor: + self.interaction.editingOpenNameColorSetup() + case .powerSaving: + push(energySavingSettingsScreen(context: self.context)) + case .businessSetup: + guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { + return + } + push(self.context.sharedContext.makeBusinessSetupScreen(context: self.context)) + case .premiumManagement: + guard let controller = self.controller else { + return + } + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) + let url = premiumConfiguration.subscriptionManagementUrl + guard !url.isEmpty else { + return + } + self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: !url.hasPrefix("tg://") && !url.contains("?start="), presentationData: self.context.sharedContext.currentPresentationData.with({$0}), navigationController: controller.navigationController as? NavigationController, dismissInput: {}) + case .stars: + if let starsContext = self.controller?.starsContext { + push(self.context.sharedContext.makeStarsTransactionsScreen(context: self.context, starsContext: starsContext)) + } + case .ton: + if let tonContext = self.controller?.tonContext { + push(self.context.sharedContext.makeStarsTransactionsScreen(context: self.context, starsContext: tonContext)) + } + case .ghostgram: + push(ghostgramSettingsController(context: self.context)) + } + } + + func openFaq(anchor: String? = nil) { + let presentationData = self.presentationData + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) + self?.controller?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + let _ = (self.cachedFaq.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] resolvedUrl in + progressDisposable.dispose() + + if let strongSelf = self, let resolvedUrl = resolvedUrl { + var resolvedUrl = resolvedUrl + if case let .instantView(webPage, _) = resolvedUrl, let customAnchor = anchor { + resolvedUrl = .instantView(webPage, customAnchor) + } + strongSelf.context.sharedContext.openResolvedUrl(resolvedUrl, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, forceExternal: false, forceUpdate: false, openPeer: { peer, navigation in + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak self] controller, arguments in + self?.controller?.push(controller) + }, dismissInput: {}, contentContext: nil, progress: nil, completion: nil) + } + }) + } + + private func openTips() { + let controller = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: nil)) + self.controller?.present(controller, in: .window(.root)) + + let context = self.context + let navigationController = self.controller?.navigationController as? NavigationController + self.tipsPeerDisposable.set((self.context.engine.peers.resolvePeerByName(name: self.presentationData.strings.Settings_TipsUsername, referrer: nil) + |> mapToSignal { result -> Signal in + guard case let .result(result) = result else { + return .complete() + } + return .single(result) + } + |> deliverOnMainQueue).startStrict(next: { [weak controller] peer in + controller?.dismiss() + if let peer = peer, let navigationController = navigationController { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) + } + })) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift new file mode 100644 index 00000000..2e029773 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift @@ -0,0 +1,209 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import TelegramCore +import TelegramPresentationData +import ChatMessageSelectionInputPanelNode +import ChatPresentationInterfaceState +import AccountContext + +final class PeerInfoSelectionPanelNode: ASDisplayNode { + private let context: AccountContext + private let peerId: EnginePeer.Id + + private let deleteMessages: () -> Void + private let shareMessages: () -> Void + private let forwardMessages: () -> Void + private let reportMessages: () -> Void + private let displayCopyProtectionTip: (UIView, Bool) -> Void + + let selectionPanel: ChatMessageSelectionInputPanelNode + let separatorNode: ASDisplayNode + let backgroundNode: NavigationBackgroundNode + + var viewForOverlayContent: UIView? { + return self.selectionPanel.viewForOverlayContent + } + + init(context: AccountContext, presentationData: PresentationData, peerId: EnginePeer.Id, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void, reportMessages: @escaping () -> Void, displayCopyProtectionTip: @escaping (UIView, Bool) -> Void) { + self.context = context + self.peerId = peerId + self.deleteMessages = deleteMessages + self.shareMessages = shareMessages + self.forwardMessages = forwardMessages + self.reportMessages = reportMessages + self.displayCopyProtectionTip = displayCopyProtectionTip + + let presentationData = presentationData + + self.separatorNode = ASDisplayNode() + self.backgroundNode = NavigationBackgroundNode(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor) + + self.selectionPanel = ChatMessageSelectionInputPanelNode(theme: presentationData.theme, strings: presentationData.strings, peerMedia: true) + self.selectionPanel.context = context + + let interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _, _ in + }, setupEditMessage: { _, _ in + }, beginMessageSelection: { _, _ in + }, cancelMessageSelection: { _ in + }, deleteSelectedMessages: { + deleteMessages() + }, reportSelectedMessages: { + reportMessages() + }, reportMessages: { _, _ in + }, blockMessageAuthor: { _, _ in + }, deleteMessages: { _, _, f in + f(.default) + }, forwardSelectedMessages: { + forwardMessages() + }, forwardCurrentForwardMessages: { + }, forwardMessages: { _ in + }, updateForwardOptionsState: { _ in + }, presentForwardOptions: { _ in + }, presentReplyOptions: { _ in + }, presentLinkOptions: { _ in + }, presentSuggestPostOptions: { + }, shareSelectedMessages: { + shareMessages() + }, updateTextInputStateAndMode: { _ in + }, updateInputModeAndDismissedButtonKeyboardMessageId: { _ in + }, openStickers: { + }, editMessage: { + }, beginMessageSearch: { _, _ in + }, dismissMessageSearch: { + }, updateMessageSearch: { _ in + }, openSearchResults: { + }, navigateMessageSearch: { _ in + }, openCalendarSearch: { + }, toggleMembersSearch: { _ in + }, navigateToMessage: { _, _, _, _ in + }, navigateToChat: { _ in + }, navigateToProfile: { _ in + }, openPeerInfo: { + }, togglePeerNotifications: { + }, sendContextResult: { _, _, _, _ in + return false + }, sendBotCommand: { _, _ in + }, sendShortcut: { _ in + }, openEditShortcuts: { + }, sendBotStart: { _ in + }, botSwitchChatWithPayload: { _, _ in + }, beginMediaRecording: { _ in + }, finishMediaRecording: { _ in + }, stopMediaRecording: { + }, lockMediaRecording: { + }, resumeMediaRecording: { + }, deleteRecordedMedia: { + }, sendRecordedMedia: { _, _ in + }, displayRestrictedInfo: { _, _ in + }, displayVideoUnmuteTip: { _ in + }, switchMediaRecordingMode: { + }, setupMessageAutoremoveTimeout: { + }, sendSticker: { _, _, _, _, _, _ in + return false + }, unblockPeer: { + }, pinMessage: { _, _ in + }, unpinMessage: { _, _, _ in + }, unpinAllMessages: { + }, openPinnedList: { _ in + }, shareAccountContact: { + }, reportPeer: { + }, presentPeerContact: { + }, dismissReportPeer: { + }, deleteChat: { + }, beginCall: { _ in + }, toggleMessageStickerStarred: { _ in + }, presentController: { _, _ in + }, presentControllerInCurrent: { _, _ in + }, getNavigationController: { + return nil + }, presentGlobalOverlayController: { _, _ in + }, navigateFeed: { + }, openGrouping: { + }, toggleSilentPost: { + }, requestUnvoteInMessage: { _ in + }, requestStopPollInMessage: { _ in + }, updateInputLanguage: { _ in + }, unarchiveChat: { + }, openLinkEditing: { + }, displaySlowmodeTooltip: { _, _ in + }, displaySendMessageOptions: { _, _ in + }, openScheduledMessages: { + }, openPeersNearby: { + }, displaySearchResultsTooltip: { _, _ in + }, unarchivePeer: { + }, scrollToTop: { + }, viewReplies: { _, _ in + }, activatePinnedListPreview: { _, _ in + }, joinGroupCall: { _ in + }, presentInviteMembers: { + }, presentGigagroupHelp: { + }, openMonoforum: { + }, editMessageMedia: { _, _ in + }, updateShowCommands: { _ in + }, updateShowSendAsPeers: { _ in + }, openInviteRequests: { + }, openSendAsPeer: { _, _ in + }, presentChatRequestAdminInfo: { + }, displayCopyProtectionTip: { view, save in + displayCopyProtectionTip(view, save) + }, openWebView: { _, _, _, _ in + }, updateShowWebView: { _ in + }, insertText: { _ in + }, backwardsDeleteText: { + }, restartTopic: { + }, toggleTranslation: { _ in + }, changeTranslationLanguage: { _ in + }, addDoNotTranslateLanguage: { _ in + }, hideTranslationPanel: { + }, openPremiumGift: { + }, openSuggestPost: { _, _ in + }, openPremiumRequiredForMessaging: { + }, openStarsPurchase: { _ in + }, openMessagePayment: { + }, openBoostToUnrestrict: { + }, updateRecordingTrimRange: { _, _, _, _ in + }, dismissAllTooltips: { + }, editTodoMessage: { _, _, _ in + }, dismissUrlPreview: { + }, dismissForwardMessages: { + }, dismissSuggestPost: { + }, displayUndo: { _ in + }, sendEmoji: { _, _, _ in + }, updateHistoryFilter: { _ in + }, updateChatLocationThread: { _, _ in + }, toggleChatSidebarMode: { + }, updateDisplayHistoryFilterAsList: { _ in + }, requestLayout: { _ in + }, chatController: { + return nil + }, statuses: nil) + + self.selectionPanel.interfaceInteraction = interfaceInteraction + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.separatorNode) + self.addSubnode(self.selectionPanel) + } + + func update(layout: ContainerViewLayout, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat { + self.backgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) + self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor + + let interfaceState = ChatPresentationInterfaceState(chatWallpaper: .color(0), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: .regular, bubbleCorners: PresentationChatBubbleCorners(mainRadius: 16.0, auxiliaryRadius: 8.0, mergeBubbleCorners: true), accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: self.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) + let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height, maxOverlayHeight: layout.size.height, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics, isMediaInputExpanded: false) + + transition.updateFrame(node: self.selectionPanel, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeight))) + + let panelHeightWithInset = panelHeight + layout.intrinsicInsets.bottom + + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeightWithInset))) + self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition) + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + + return panelHeightWithInset + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSettingsItems.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSettingsItems.swift new file mode 100644 index 00000000..e59306be --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSettingsItems.swift @@ -0,0 +1,509 @@ +import Foundation +import UIKit +import Display +import AccountContext +import TelegramPresentationData +import TelegramCore +import Postbox +import PhoneNumberFormat +import ItemListUI +import SwiftSignalKit +import PhotoResources +import ItemListPeerItem +import DeviceAccess +import TelegramStringFormatting +import PeerNameColorItem + +enum SettingsSection: Int, CaseIterable { + case edit + case phone + case accounts + case myProfile + case proxy + case apps + case shortcuts + case advanced + case payment + case extra + case support +} + +func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, isExpanded: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] { + guard let data = data else { + return [] + } + + var items: [SettingsSection: [PeerInfoScreenItem]] = [:] + for section in SettingsSection.allCases { + items[section] = [] + } + + let setPhotoTitle: String + if let peer = data.peer, !peer.profileImageRepresentations.isEmpty { + setPhotoTitle = presentationData.strings.Settings_ChangeProfilePhoto + } else { + setPhotoTitle = presentationData.strings.Settings_SetProfilePhotoOrVideo + } + + var setStatusTitle: String = "" + let displaySetStatus: Bool + var hasEmojiStatus = false + if let peer = data.peer as? TelegramUser, peer.isPremium { + if peer.emojiStatus != nil { + hasEmojiStatus = true + setStatusTitle = presentationData.strings.PeerInfo_ChangeEmojiStatus + } else { + setStatusTitle = presentationData.strings.PeerInfo_SetEmojiStatus + } + displaySetStatus = true + } else { + displaySetStatus = false + } + + if displaySetStatus { + items[.edit]!.append(PeerInfoScreenActionItem(id: 0, text: setStatusTitle, icon: UIImage(bundleImageName: hasEmojiStatus ? "Settings/EditEmojiStatus" : "Settings/SetEmojiStatus"), action: { + interaction.openSettings(.emojiStatus) + })) + + items[.edit]!.append(PeerInfoScreenActionItem(id: 1, text: presentationData.strings.PeerInfo_ChangeProfileColor, icon: UIImage(bundleImageName: "Premium/BoostPerk/CoverColor"), action: { + interaction.openSettings(.profileColor) + })) + } + + items[.edit]!.append(PeerInfoScreenActionItem(id: 2, text: setPhotoTitle, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: { + interaction.openSettings(.avatar) + })) + + if let peer = data.peer, (peer.addressName ?? "").isEmpty { + items[.edit]!.append(PeerInfoScreenActionItem(id: 3, text: presentationData.strings.Settings_SetUsername, icon: UIImage(bundleImageName: "Settings/SetUsername"), action: { + interaction.openSettings(.username) + })) + } + + if let settings = data.globalSettings { + if settings.premiumGracePeriod { + items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: "Your access to Telegram Premium will expire soon!", text: .markdown("Unfortunately, your latest payment didn't come through. To keep your access to exclusive features, please renew the subscription."), isWarning: true, linkAction: nil)) + items[.phone]!.append(PeerInfoScreenActionItem(id: 1, text: "Restore Subscription", action: { + interaction.openSettings(.premiumManagement) + })) + } else if settings.suggestPhoneNumberConfirmation, let peer = data.peer as? TelegramUser { + let phoneNumber = formatPhoneNumber(context: context, number: peer.phone ?? "") + items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: presentationData.strings.Settings_CheckPhoneNumberTitle(phoneNumber).string, text: .markdown(presentationData.strings.Settings_CheckPhoneNumberText), linkAction: { link in + if case .tap = link { + interaction.openFaq(presentationData.strings.Settings_CheckPhoneNumberFAQAnchor) + } + })) + items[.phone]!.append(PeerInfoScreenActionItem(id: 1, text: presentationData.strings.Settings_KeepPhoneNumber(phoneNumber).string, action: { + let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.validatePhoneNumber.id).startStandalone() + })) + items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_ChangePhoneNumber, action: { + interaction.openSettings(.phoneNumber) + })) + } else if settings.suggestPasswordConfirmation { + items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: presentationData.strings.Settings_CheckPasswordTitle, text: .markdown(presentationData.strings.Settings_CheckPasswordText), linkAction: { _ in + })) + items[.phone]!.append(PeerInfoScreenActionItem(id: 1, text: presentationData.strings.Settings_KeepPassword, action: { + let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.validatePassword.id).startStandalone() + })) + items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_TryEnterPassword, action: { + interaction.openSettings(.rememberPassword) + })) + } else if settings.suggestPasswordSetup { + items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: presentationData.strings.Settings_SuggestSetupPasswordTitle, text: .markdown(presentationData.strings.Settings_SuggestSetupPasswordText), linkAction: { _ in + })) + items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_SuggestSetupPasswordAction, action: { + interaction.openSettings(.passwordSetup) + })) + } + + if !settings.accountsAndPeers.isEmpty { + for (peerAccountContext, peer, badgeCount) in settings.accountsAndPeers { + let mappedContext = ItemListPeerItem.Context.custom(ItemListPeerItem.Context.Custom( + accountPeerId: peerAccountContext.account.peerId, + postbox: peerAccountContext.account.postbox, + network: peerAccountContext.account.network, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + isPremiumDisabled: false, + resolveInlineStickers: { fileIds in + return context.engine.stickers.resolveInlineStickers(fileIds: fileIds) + } + )) + let member: PeerInfoMember = .account(peer: RenderedPeer(peer: peer._asPeer())) + items[.accounts]!.append(PeerInfoScreenMemberItem(id: member.id, context: mappedContext, enclosingPeer: nil, member: member, badge: badgeCount > 0 ? "\(compactNumericCountString(Int(badgeCount), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))" : nil, isAccount: true, action: { action in + switch action { + case .open: + interaction.switchToAccount(peerAccountContext.account.id) + case .remove: + interaction.logoutAccount(peerAccountContext.account.id) + default: + break + } + }, contextAction: { node, gesture in + interaction.accountContextMenu(peerAccountContext.account.id, node, gesture) + })) + } + + items[.accounts]!.append(PeerInfoScreenActionItem(id: 100, text: presentationData.strings.Settings_AddAccount, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), action: { + interaction.openSettings(.addAccount) + })) + } + + items[.myProfile]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_MyProfile, icon: PresentationResourcesSettings.myProfile, action: { + interaction.openSettings(.profile) + })) + + if !settings.proxySettings.servers.isEmpty { + let proxyType: String + if settings.proxySettings.enabled, let activeServer = settings.proxySettings.activeServer { + switch activeServer.connection { + case .mtp: + proxyType = presentationData.strings.SocksProxySetup_ProxyTelegram + case .socks5: + proxyType = presentationData.strings.SocksProxySetup_ProxySocks5 + } + } else { + proxyType = presentationData.strings.Settings_ProxyDisabled + } + items[.proxy]!.append(PeerInfoScreenDisclosureItem(id: 0, label: .text(proxyType), text: presentationData.strings.Settings_Proxy, icon: PresentationResourcesSettings.proxy, action: { + interaction.openSettings(.proxy) + })) + } + } + + var appIndex = 1000 + if let settings = data.globalSettings { + for bot in settings.bots { + let iconSignal: Signal + if let peer = PeerReference(bot.peer._asPeer()), let icon = bot.icons[.iOSSettingsStatic] { + let fileReference: FileMediaReference = .attachBot(peer: peer, media: icon) + iconSignal = instantPageImageFile(account: context.account, userLocation: .other, fileReference: fileReference, fetched: true) + |> map { generator -> UIImage? in + let size = CGSize(width: 29.0, height: 29.0) + let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: .zero)) + return context?.generateImage() + } + let _ = freeMediaFileInteractiveFetched(account: context.account, userLocation: .other, fileReference: fileReference).startStandalone() + } else { + iconSignal = .single(UIImage()) + } + let label: PeerInfoScreenDisclosureItem.Label = bot.flags.contains(.notActivated) || bot.flags.contains(.showInSettingsDisclaimer) ? .titleBadge(presentationData.strings.Settings_New, presentationData.theme.list.itemAccentColor) : .none + items[.apps]!.append(PeerInfoScreenDisclosureItem(id: bot.peer.id.id._internalGetInt64Value(), label: label, text: bot.shortName, icon: nil, iconSignal: iconSignal, action: { + interaction.openBotApp(bot) + })) + appIndex += 1 + } + } + + items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_SavedMessages, icon: PresentationResourcesSettings.savedMessages, action: { + interaction.openSettings(.savedMessages) + })) + items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 2, text: presentationData.strings.CallSettings_RecentCalls, icon: PresentationResourcesSettings.recentCalls, action: { + interaction.openSettings(.recentCalls) + })) + + let devicesLabel: String + if let settings = data.globalSettings, let otherSessionsCount = settings.otherSessionsCount { + if settings.enableQRLogin { + devicesLabel = otherSessionsCount == 0 ? presentationData.strings.Settings_AddDevice : "\(otherSessionsCount + 1)" + } else { + devicesLabel = otherSessionsCount == 0 ? "" : "\(otherSessionsCount + 1)" + } + } else { + devicesLabel = "" + } + + items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 3, label: .text(devicesLabel), text: presentationData.strings.Settings_Devices, icon: PresentationResourcesSettings.devices, action: { + interaction.openSettings(.devices) + })) + items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 4, text: presentationData.strings.Settings_ChatFolders, icon: PresentationResourcesSettings.chatFolders, action: { + interaction.openSettings(.chatFolders) + })) + + let notificationsWarning: Bool + if let settings = data.globalSettings { + notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: settings.notificationAuthorizationStatus, suppressed: settings.notificationWarningSuppressed) + } else { + notificationsWarning = false + } + items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 0, label: notificationsWarning ? .badge("!", presentationData.theme.list.itemDestructiveColor) : .none, text: presentationData.strings.Settings_NotificationsAndSounds, icon: PresentationResourcesSettings.notifications, action: { + interaction.openSettings(.notificationsAndSounds) + })) + items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_PrivacySettings, icon: PresentationResourcesSettings.security, action: { + interaction.openSettings(.privacyAndSecurity) + })) + items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 1001, text: "Ghostgram Settings", icon: UIImage(bundleImageName: "Settings/Menu/Appearance"), action: { + interaction.openSettings(.ghostgram) + })) + items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 2, text: presentationData.strings.Settings_ChatSettings, icon: PresentationResourcesSettings.dataAndStorage, action: { + interaction.openSettings(.dataAndStorage) + })) + items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 3, text: presentationData.strings.Settings_Appearance, icon: PresentationResourcesSettings.appearance, action: { + interaction.openSettings(.appearance) + })) + + items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 6, label: .text(data.isPowerSavingEnabled == true ? presentationData.strings.Settings_PowerSavingOn : presentationData.strings.Settings_PowerSavingOff), text: presentationData.strings.Settings_PowerSaving, icon: PresentationResourcesSettings.powerSaving, action: { + interaction.openSettings(.powerSaving) + })) + + let languageName = presentationData.strings.primaryComponent.localizedName + items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 4, label: .text(languageName.isEmpty ? presentationData.strings.Localization_LanguageName : languageName), text: presentationData.strings.Settings_AppLanguage, icon: PresentationResourcesSettings.language, action: { + interaction.openSettings(.language) + })) + + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + let isPremiumDisabled = premiumConfiguration.isPremiumDisabled + if !isPremiumDisabled || context.isPremium { + items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: presentationData.strings.Settings_Premium, icon: PresentationResourcesSettings.premium, action: { + interaction.openSettings(.premium) + })) + } + if let starsState = data.starsState { + if !isPremiumDisabled || abs(starsState.balance.value) > 0 { + let balanceText: NSAttributedString + if abs(starsState.balance.value) > 0 { + let formattedLabel = formatStarsAmountText(starsState.balance, dateTimeFormat: presentationData.dateTimeFormat) + let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) + let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) + let labelColor = presentationData.theme.list.itemSecondaryTextColor + balanceText = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) + } else { + balanceText = NSAttributedString() + } + items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 102, label: .attributedText(balanceText), text: presentationData.strings.Settings_Stars, icon: PresentationResourcesSettings.stars, action: { + interaction.openSettings(.stars) + })) + } + } + if let tonState = data.tonState { + if abs(tonState.balance.value) > 0 { + let balanceText: NSAttributedString + if abs(tonState.balance.value) > 0 { + let formattedLabel = formatTonAmountText(tonState.balance.value, dateTimeFormat: presentationData.dateTimeFormat) + let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) + let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) + let labelColor = presentationData.theme.list.itemSecondaryTextColor + balanceText = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) + } else { + balanceText = NSAttributedString() + } + items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 103, label: .attributedText(balanceText), text: presentationData.strings.Settings_MyTon, icon: PresentationResourcesSettings.ton, action: { + interaction.openSettings(.ton) + })) + } + } + if !isPremiumDisabled || context.isPremium { + items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 104, label: .text(""), additionalBadgeLabel: nil, text: presentationData.strings.Settings_Business, icon: PresentationResourcesSettings.business, action: { + interaction.openSettings(.businessSetup) + })) + } + if let starsState = data.starsState { + if !isPremiumDisabled || starsState.balance > StarsAmount.zero { + items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 105, label: .text(""), text: presentationData.strings.Settings_SendGift, icon: PresentationResourcesSettings.premiumGift, action: { + interaction.openSettings(.premiumGift) + })) + } + } + + if let settings = data.globalSettings { + if settings.hasPassport { + items[.extra]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_Passport, icon: PresentationResourcesSettings.passport, action: { + interaction.openSettings(.passport) + })) + } + if settings.hasWatchApp { + items[.extra]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_AppleWatch, icon: PresentationResourcesSettings.watch, action: { + interaction.openSettings(.watch) + })) + } + } + + items[.support]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_Support, icon: PresentationResourcesSettings.support, action: { + interaction.openSettings(.support) + })) + items[.support]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_FAQ, icon: PresentationResourcesSettings.faq, action: { + interaction.openSettings(.faq) + })) + items[.support]!.append(PeerInfoScreenDisclosureItem(id: 2, text: presentationData.strings.Settings_Tips, icon: PresentationResourcesSettings.tips, action: { + interaction.openSettings(.tips) + })) + + var result: [(AnyHashable, [PeerInfoScreenItem])] = [] + for section in SettingsSection.allCases { + if let sectionItems = items[section], !sectionItems.isEmpty { + result.append((section, sectionItems)) + } + } + return result +} + +func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, isMyProfile: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] { + guard let data = data else { + return [] + } + + enum Section: Int, CaseIterable { + case help + case bio + case birthday + case info + case account + case logout + } + + var items: [Section: [PeerInfoScreenItem]] = [:] + for section in Section.allCases { + items[section] = [] + } + + let ItemNameHelp = 0 + let ItemBio: AnyHashable = AnyHashable("bio_edit") + let ItemBioHelp = 2 + let ItemPhoneNumber = 3 + let ItemUsername = 4 + let ItemAddAccount = 5 + let ItemAddAccountHelp = 6 + let ItemLogout = 7 + let ItemPeerColor = 8 + let ItemBirthday = 9 + let ItemBirthdayPicker = 10 + let ItemBirthdayRemove = 11 + let ItemBirthdayHelp = 12 + let ItemPeerPersonalChannel = 13 + + items[.help]!.append(PeerInfoScreenCommentItem(id: ItemNameHelp, text: presentationData.strings.EditProfile_NameAndPhotoOrVideoHelp)) + + if let cachedData = data.cachedData as? CachedUserData { + items[.bio]!.append(PeerInfoScreenMultilineInputItem(id: ItemBio, text: state.updatingBio ?? (cachedData.about ?? ""), placeholder: presentationData.strings.UserInfo_About_Placeholder, textUpdated: { updatedText in + interaction.updateBio(updatedText) + }, action: { + interaction.dismissInput() + }, maxLength: Int(data.globalSettings?.userLimits.maxAboutLength ?? 70))) + items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_PrivacyHelp, linkAction: { _ in + interaction.openBioPrivacy() + })) + } + + + var birthday: TelegramBirthday? + if let updatingBirthDate = state.updatingBirthDate { + birthday = updatingBirthDate + } else { + birthday = (data.cachedData as? CachedUserData)?.birthday + } + + var birthDateString: String + if let birthday { + birthDateString = stringForCompactBirthday(birthday, strings: presentationData.strings) + } else { + birthDateString = presentationData.strings.Settings_Birthday_Add + } + + let isEditingBirthDate = state.isEditingBirthDate + items[.birthday]!.append(PeerInfoScreenDisclosureItem(id: ItemBirthday, label: .coloredText(birthDateString, isEditingBirthDate ? .accent : .generic), text: presentationData.strings.Settings_Birthday, icon: nil, hasArrow: false, action: { + interaction.updateIsEditingBirthdate(!isEditingBirthDate) + })) + if isEditingBirthDate, let birthday { + items[.birthday]!.append(PeerInfoScreenBirthdatePickerItem(id: ItemBirthdayPicker, value: birthday, valueUpdated: { value in + interaction.updateBirthdate(value) + })) + items[.birthday]!.append(PeerInfoScreenActionItem(id: ItemBirthdayRemove, text: presentationData.strings.Settings_Birthday_Remove, alignment: .natural, action: { + interaction.updateBirthdate(.some(nil)) + interaction.updateIsEditingBirthdate(false) + })) + } + + + var birthdayIsForContactsOnly = false + if let birthdayPrivacy = data.globalSettings?.privacySettings?.birthday, case .enableContacts = birthdayPrivacy { + birthdayIsForContactsOnly = true + } + items[.birthday]!.append(PeerInfoScreenCommentItem(id: ItemBirthdayHelp, text: birthdayIsForContactsOnly ? presentationData.strings.Settings_Birthday_ContactsHelp : presentationData.strings.Settings_Birthday_Help, linkAction: { _ in + interaction.openBirthdatePrivacy() + })) + + if let user = data.peer as? TelegramUser { + items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPhoneNumber, label: .text(user.phone.flatMap({ formatPhoneNumber(context: context, number: $0) }) ?? ""), text: presentationData.strings.Settings_PhoneNumber, action: { + interaction.openSettings(.phoneNumber) + })) + } + var username = "" + if let addressName = data.peer?.addressName, !addressName.isEmpty { + username = "@\(addressName)" + } + items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(username), text: presentationData.strings.Settings_Username, action: { + interaction.openSettings(.username) + })) + + if let peer = data.peer as? TelegramUser { + var colors: [PeerNameColors.Colors] = [] + if let nameColor = peer.nameColor { + let nameColors: PeerNameColors.Colors + switch nameColor { + case let .preset(nameColor): + nameColors = context.peerNameColors.get(nameColor, dark: presentationData.theme.overallDarkAppearance) + case let .collectible(collectibleColor): + nameColors = collectibleColor.peerNameColors(dark: presentationData.theme.overallDarkAppearance) + } + colors.append(nameColors) + } + if let profileColor = peer.effectiveProfileColor.flatMap({ context.peerNameColors.getProfile($0, dark: presentationData.theme.overallDarkAppearance, subject: .palette) }) { + colors.append(profileColor) + } + let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors) + + items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), text: presentationData.strings.Settings_YourColor, icon: nil, action: { + interaction.editingOpenNameColorSetup() + })) + + var displayPersonalChannel = false + if data.personalChannel != nil { + displayPersonalChannel = true + } else if let personalChannels = state.personalChannels, !personalChannels.isEmpty { + displayPersonalChannel = true + } + if displayPersonalChannel { + var personalChannelTitle: String? + if let personalChannel = data.personalChannel, let peer = personalChannel.peer.chatOrMonoforumMainPeer { + personalChannelTitle = peer.compactDisplayTitle + } + + items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerPersonalChannel, label: .text(personalChannelTitle ?? presentationData.strings.Settings_PersonalChannelEmptyValue), text: presentationData.strings.Settings_PersonalChannelItem, icon: nil, action: { + interaction.editingOpenPersonalChannel() + })) + } + } + + items[.account]!.append(PeerInfoScreenActionItem(id: ItemAddAccount, text: presentationData.strings.Settings_AddAnotherAccount, alignment: .center, action: { + interaction.openSettings(.addAccount) + })) + + var hasPremiumAccounts = false + if data.peer?.isPremium == true && !context.account.testingEnvironment { + hasPremiumAccounts = true + } + if let settings = data.globalSettings { + for (accountContext, peer, _) in settings.accountsAndPeers { + if !accountContext.account.testingEnvironment { + if peer.isPremium { + hasPremiumAccounts = true + break + } + } + } + } + + items[.account]!.append(PeerInfoScreenCommentItem(id: ItemAddAccountHelp, text: hasPremiumAccounts ? presentationData.strings.Settings_AddAnotherAccount_PremiumHelp : presentationData.strings.Settings_AddAnotherAccount_Help)) + + items[.logout]!.append(PeerInfoScreenActionItem(id: ItemLogout, text: presentationData.strings.Settings_Logout, color: .destructive, alignment: .center, action: { + interaction.openSettings(.logout) + })) + + var result: [(AnyHashable, [PeerInfoScreenItem])] = [] + for section in Section.allCases { + if let sectionItems = items[section], !sectionItems.isEmpty { + result.append((section, sectionItems)) + } + } + return result +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSettingsTabActions.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSettingsTabActions.swift new file mode 100644 index 00000000..5c9bb8c7 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSettingsTabActions.swift @@ -0,0 +1,322 @@ +import Foundation +import UIKit +import Display +import AccountContext +import TelegramPresentationData +import SwiftSignalKit +import ContextUI +import TelegramCore +import AvatarNode +import AsyncDisplayKit +import ComponentFlow +import ComponentDisplayAdapters +import EmojiStatusComponent + +extension PeerInfoScreenNode { + func accountContextMenuItems(context: AccountContext, logout: @escaping () -> Void) -> Signal<[ContextMenuItem], NoError> { + let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings + return context.engine.messages.unreadChatListPeerIds(groupId: .root, filterPredicate: nil) + |> map { unreadChatListPeerIds -> [ContextMenuItem] in + var items: [ContextMenuItem] = [] + + if !unreadChatListPeerIds.isEmpty { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAllAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = (context.engine.messages.markAllChatsAsReadInteractively(items: [(groupId: .root, filterPredicate: nil)]) + |> deliverOnMainQueue).startStandalone(completed: { + f(.default) + }) + }))) + } + + items.append(.action(ContextMenuActionItem(text: strings.Settings_Context_Logout, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Logout"), color: theme.contextMenu.destructiveColor) }, action: { _, f in + logout() + f(.default) + }))) + + return items + } + } + + func accountContextMenu(id: AccountRecordId, node: ASDisplayNode, gesture: ContextGesture?) { + var selectedAccount: Account? + let _ = (self.accountsAndPeers.get() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { accountsAndPeers in + for (account, _, _) in accountsAndPeers { + if account.account.id == id { + selectedAccount = account.account + break + } + } + }) + if let selectedAccount = selectedAccount { + let accountContext = self.context.sharedContext.makeTempAccountContext(account: selectedAccount) + let chatListController = accountContext.sharedContext.makeChatListController(context: accountContext, location: .chatList(groupId: EngineChatList.Group(.root)), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) + + let contextController = ContextController(presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node)), items: accountContextMenuItems(context: accountContext, logout: { [weak self] in + self?.logoutAccount(id: id) + }) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + self.controller?.presentInGlobalOverlay(contextController) + } else { + gesture?.cancel() + } + } + + func switchToAccount(id: AccountRecordId) { + self.accountsAndPeers.set(.never()) + self.context.sharedContext.switchToAccount(id: id, fromSettingsController: nil, withChatListController: nil) + } + + func logoutAccount(id: AccountRecordId) { + let controller = ActionSheetController(presentationData: self.presentationData) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + + var items: [ActionSheetItem] = [] + items.append(ActionSheetTextItem(title: self.presentationData.strings.Settings_LogoutConfirmationText.trimmingCharacters(in: .whitespacesAndNewlines))) + items.append(ActionSheetButtonItem(title: self.presentationData.strings.Settings_Logout, color: .destructive, action: { [weak self] in + dismissAction() + if let strongSelf = self { + let _ = logoutFromAccount(id: id, accountManager: strongSelf.context.sharedContext.accountManager, alreadyLoggedOutRemotely: false).startStandalone() + } + })) + controller.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + self.controller?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } +} + +final class AccountPeerContextItem: ContextMenuCustomItem { + let context: AccountContext + let account: Account + let peer: EnginePeer + let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void + + init(context: AccountContext, account: Account, peer: EnginePeer, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) { + self.context = context + self.account = account + self.peer = peer + self.action = action + } + + public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode { + return AccountPeerContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected) + } +} + +private final class AccountPeerContextItemNode: ASDisplayNode, ContextMenuCustomNode { + private let item: AccountPeerContextItem + private let presentationData: PresentationData + private let getController: () -> ContextControllerProtocol? + private let actionSelected: (ContextMenuActionResult) -> Void + + private let highlightedBackgroundNode: ASDisplayNode + private let buttonNode: HighlightTrackingButtonNode + private let textNode: ImmediateTextNode + private let avatarNode: AvatarNode + private let emojiStatusView: ComponentView + + init(presentationData: PresentationData, item: AccountPeerContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) { + self.item = item + self.presentationData = presentationData + self.getController = getController + self.actionSelected = actionSelected + + let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 17.0 / 17.0) + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isAccessibilityElement = false + self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor + self.highlightedBackgroundNode.alpha = 0.0 + + self.textNode = ImmediateTextNode() + self.textNode.isAccessibilityElement = false + self.textNode.isUserInteractionEnabled = false + self.textNode.displaysAsynchronously = false + let peerTitle = item.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + self.textNode.attributedText = NSAttributedString(string: peerTitle, font: textFont, textColor: presentationData.theme.contextMenu.primaryColor) + self.textNode.maximumNumberOfLines = 1 + + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0)) + + self.emojiStatusView = ComponentView() + + self.buttonNode = HighlightTrackingButtonNode() + self.buttonNode.isAccessibilityElement = true + self.buttonNode.accessibilityLabel = peerTitle + + super.init() + + self.addSubnode(self.highlightedBackgroundNode) + self.addSubnode(self.textNode) + self.addSubnode(self.avatarNode) + self.addSubnode(self.buttonNode) + + self.buttonNode.highligthedChanged = { [weak self] highligted in + guard let strongSelf = self else { + return + } + if highligted { + strongSelf.highlightedBackgroundNode.alpha = 1.0 + } else { + strongSelf.highlightedBackgroundNode.alpha = 0.0 + strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + } + } + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) { + let sideInset: CGFloat = 16.0 + let iconSideInset: CGFloat = 12.0 + let verticalInset: CGFloat = 12.0 + + let iconSize = CGSize(width: 28.0, height: 28.0) + + let standardIconWidth: CGFloat = 32.0 + var rightTextInset: CGFloat = sideInset + if !iconSize.width.isZero { + rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset - 12.0 + } + + self.avatarNode.setPeer(context: self.item.context, account: self.item.account, theme: self.presentationData.theme, peer: self.item.peer) + + if self.item.peer.emojiStatus != nil { + rightTextInset += 32.0 + } + + let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude)) + + return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + textSize.height), { size, transition in + let verticalOrigin = floor((size.height - textSize.height) / 2.0) + let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize) + transition.updateFrameAdditive(node: self.textNode, frame: textFrame) + + var iconContent: EmojiStatusComponent.Content? + if case let .user(user) = self.item.peer { + if let emojiStatus = user.emojiStatus { + iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever) + } else if user.isPremium { + iconContent = .premium(color: self.presentationData.theme.list.itemAccentColor) + } + } else if case let .channel(channel) = self.item.peer { + if let emojiStatus = channel.emojiStatus { + iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever) + } + } + if let iconContent { + let emojiStatusSize = self.emojiStatusView.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: self.item.context, + animationCache: self.item.context.animationCache, + animationRenderer: self.item.context.animationRenderer, + content: iconContent, + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: CGSize(width: 24.0, height: 24.0) + ) + if let view = self.emojiStatusView.view { + if view.superview == nil { + self.view.addSubview(view) + } + transition.updateFrame(view: view, frame: CGRect(origin: CGPoint(x: textFrame.maxX + 2.0, y: textFrame.minY + floor((textFrame.height - emojiStatusSize.height) / 2.0)), size: emojiStatusSize)) + } + } + + transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)) + + transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) + transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) + }) + } + + func updateTheme(presentationData: PresentationData) { + self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor + + if let attributedText = self.textNode.attributedText { + let updatedAttributedText = NSMutableAttributedString(attributedString: attributedText) + updatedAttributedText.addAttribute(.foregroundColor, value: presentationData.theme.contextMenu.primaryColor.cgColor, range: NSRange(location: 0, length: updatedAttributedText.length)) + self.textNode.attributedText = updatedAttributedText + } + } + + @objc private func buttonPressed() { + self.performAction() + } + + func canBeHighlighted() -> Bool { + return true + } + + func setIsHighlighted(_ value: Bool) { + if value { + self.highlightedBackgroundNode.alpha = 1.0 + } else { + self.highlightedBackgroundNode.alpha = 0.0 + } + } + + func updateIsHighlighted(isHighlighted: Bool) { + self.setIsHighlighted(isHighlighted) + } + + func performAction() { + guard let controller = self.getController() else { + return + } + self.item.action(controller, { [weak self] result in + self?.actionSelected(result) + }) + } +} + +final class PeerInfoControllerContextReferenceContentSource: ContextReferenceContentSource { + let controller: ViewController + let sourceView: UIView + let insets: UIEdgeInsets + let contentInsets: UIEdgeInsets + + init(controller: ViewController, sourceView: UIView, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets()) { + self.controller = controller + self.sourceView = sourceView + self.insets = insets + self.contentInsets = contentInsets + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets) + } +} + +final class HeaderContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceView: UIView + + init(controller: ViewController, sourceView: UIView) { + self.controller = controller + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} + +private func cancelContextGestures(view: UIView) { + if let gestureRecognizers = view.gestureRecognizers { + for gesture in gestureRecognizers { + if let gesture = gesture as? ContextGesture { + gesture.cancel() + } + } + } + for subview in view.subviews { + cancelContextGestures(view: subview) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PhotoUpdateConfirmationController.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PhotoUpdateConfirmationController.swift index 4e257669..ce30f6f2 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PhotoUpdateConfirmationController.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PhotoUpdateConfirmationController.swift @@ -9,239 +9,110 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext import AppBundle -import AvatarNode +import ComponentFlow +import AlertComponent +import AlertTransferHeaderComponent +import AvatarComponent -private final class PhotoUpdateConfirmationAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - private let text: String - - private let textNode: ASTextNode - private let avatarNode: AvatarNode - private let arrowNode: ASImageNode - private let iconNode: ASImageNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer, image: UIImage, text: String, actions: [TextAlertAction]) { - self.strings = strings - self.text = text - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) - - self.arrowNode = ASImageNode() - self.arrowNode.displaysAsynchronously = false - self.arrowNode.displayWithoutProcessing = true - - self.iconNode = ASImageNode() - self.iconNode.clipsToBounds = true - self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true - self.iconNode.image = image - self.iconNode.cornerRadius = 30.0 - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.textNode) - self.addSubnode(self.avatarNode) - self.addSubnode(self.arrowNode) - self.addSubnode(self.iconNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - - self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let avatarSize = CGSize(width: 60.0, height: 60.0) - self.avatarNode.updateSize(size: avatarSize) - - let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize) - transition.updateFrame(node: self.avatarNode, frame: avatarFrame) - - if let arrowImage = self.arrowNode.image { - let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) - transition.updateFrame(node: self.arrowNode, frame: arrowFrame) - } - - if let _ = self.iconNode.image { - let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize) - transition.updateFrame(node: self.iconNode, frame: iconFrame) - origin.y += avatarSize.height + 10.0 - } - - let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultSize = CGSize(width: contentWidth, height: avatarSize.height + textSize.height + actionsHeight + 16.0 + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} - -func photoUpdateConfirmationController(context: AccountContext, peer: EnginePeer, image: UIImage, text: String, doneTitle: String, isDark: Bool = true, commit: @escaping () -> Void) -> AlertController { - let theme = defaultDarkColorPresentationTheme - var presentationData = context.sharedContext.currentPresentationData.with { $0 } - if isDark { - presentationData = presentationData.withUpdated(theme: theme) - } +func photoUpdateConfirmationController( + context: AccountContext, + peer: EnginePeer, + image: UIImage, + text: String, + doneTitle: String, + isDark: Bool = true, + commit: @escaping () -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertTransferHeaderComponent( + fromComponent: AnyComponentWithIdentity(id: "user", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: peer + ) + )), + toComponent: AnyComponentWithIdentity(id: "image", component: AnyComponent( + Image( + image: image, + size: CGSize(width: 60.0, height: 60.0), + cornerRadius: 30.0 + ) + )), + type: .transfer + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) - var dismissImpl: ((Bool) -> Void)? - var contentNode: PhotoUpdateConfirmationAlertContentNode? - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - }), TextAlertAction(type: .defaultAction, title: doneTitle, action: { - dismissImpl?(true) - commit() - })] - - contentNode = PhotoUpdateConfirmationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peer: peer, image: image, text: text, actions: actions) - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } + var updatedPresentationData: (PresentationData, Signal) + if isDark { + updatedPresentationData = (presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), .single(presentationData.withUpdated(theme: defaultDarkColorPresentationTheme))) + } else { + updatedPresentationData = (presentationData, context.sharedContext.presentationData) } - return controller + + let alertController = AlertScreen( + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: doneTitle, type: .default, action: { + commit() + }) + ], + updatedPresentationData: updatedPresentationData + ) + return alertController } + +//private final class RoundImageComponent: Component { +// let image: UIImage +// +// public init( +// image: UIImage +// ) { +// self.image = image +// } +// +// public static func ==(lhs: RoundImageComponent, rhs: RoundImageComponent) -> Bool { +// if lhs.image !== rhs.image { +// return false +// } +// return true +// } +// +// public final class View: UIImageView { +// private var component: RoundImageComponent? +// private weak var state: EmptyComponentState? +// +// func update(component: RoundImageComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { +// self.component = component +// self.state = state +// +// self.clipsToBounds = true +// self.image = component.image +// self.layer.cornerRadius = 30.0 +// +// return CGSize(width: 60.0, height: 60.0) +// } +// } +// +// public func makeView() -> View { +// return View(frame: CGRect()) +// } +// +// public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { +// return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) +// } +//} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift new file mode 100644 index 00000000..af50c523 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift @@ -0,0 +1,220 @@ +import Foundation +import UIKit +import Display +import AccountContext +import TelegramPresentationData +import SwiftSignalKit +import Postbox +import TelegramCore +import InviteLinksUI +import SendInviteLinkScreen +import UndoUI +import PresentationDataUtils + +public func presentAddMembersImpl(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) { + let members: Promise<[PeerId]> = Promise() + if groupPeer.id.namespace == Namespaces.Peer.CloudChannel { + /*var membersDisposable: Disposable? + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { listState in + members.set(.single(listState.list.map {$0.peer.id})) + membersDisposable?.dispose() + }) + membersDisposable = disposable*/ + members.set(.single([])) + } else { + members.set(.single([])) + } + + let _ = (members.get() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak parentController] recentIds in + var createInviteLinkImpl: (() -> Void)? + var confirmationImpl: ((PeerId) -> Signal)? + let _ = confirmationImpl + var options: [ContactListAdditionalOption] = [] + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + + var canCreateInviteLink = false + if let group = groupPeer as? TelegramGroup { + switch group.role { + case .creator: + canCreateInviteLink = true + case let .admin(rights, _): + canCreateInviteLink = rights.rights.contains(.canInviteUsers) + default: + break + } + } else if let channel = groupPeer as? TelegramChannel, (channel.addressName?.isEmpty ?? true) { + if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) { + canCreateInviteLink = true + } + } + + if canCreateInviteLink { + options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: { + createInviteLinkImpl?() + }, clearHighlightAutomatically: true)) + } + + let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: .single(options), filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true, isGroupInvitation: true)) + contactsController.navigationPresentation = .modal + + confirmationImpl = { [weak contactsController] peerId in + return context.account.postbox.loadedPeerWithId(peerId) + |> deliverOnMainQueue + |> mapToSignal { peer in + let result = ValuePromise() + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + if let contactsController = contactsController { + let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: { + result.set(false) + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { + result.set(true) + }) + ]) + contactsController.present(alertController, in: .window(.root)) + } + + return result.get() + } + } + + let addMembers: ([ContactListPeerId]) -> Signal<[(PeerId, AddChannelMemberError)], NoError> = { members -> Signal<[(PeerId, AddChannelMemberError)], NoError> in + let memberIds = members.compactMap { contact -> PeerId? in + switch contact { + case let .peer(peerId): + return peerId + default: + return nil + } + } + return context.account.postbox.multiplePeersView(memberIds) + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { view -> Signal<[(PeerId, AddChannelMemberError)], NoError> in + if groupPeer.id.namespace == Namespaces.Peer.CloudChannel { + if memberIds.count == 1 { + return context.peerChannelMemberCategoriesContextsManager.addMember(engine: context.engine, peerId: groupPeer.id, memberId: memberIds[0]) + |> map { _ -> [(PeerId, AddChannelMemberError)] in + } + |> then(Signal<[(PeerId, AddChannelMemberError)], AddChannelMemberError>.single([])) + |> `catch` { error -> Signal<[(PeerId, AddChannelMemberError)], NoError> in + return .single([(memberIds[0], error)]) + } + } else { + return context.peerChannelMemberCategoriesContextsManager.addMembersAllowPartial(engine: context.engine, peerId: groupPeer.id, memberIds: memberIds) + } + } else { + var signals: [Signal<(PeerId, AddChannelMemberError)?, NoError>] = [] + for memberId in memberIds { + let signal: Signal<(PeerId, AddChannelMemberError)?, NoError> = context.engine.peers.addGroupMember(peerId: groupPeer.id, memberId: memberId) + |> mapError { error -> AddChannelMemberError in + switch error { + case .generic: + return .generic + case .groupFull: + return .limitExceeded + case let .privacy(privacy): + return .restricted(privacy?.forbiddenPeers.first) + case .notMutualContact: + return .notMutualContact + case .tooManyChannels: + return .generic + } + } + |> ignoreValues + |> map { _ -> (PeerId, AddChannelMemberError)? in + } + |> then(Signal<(PeerId, AddChannelMemberError)?, AddChannelMemberError>.single(nil)) + |> `catch` { error -> Signal<(PeerId, AddChannelMemberError)?, NoError> in + return .single((memberId, error)) + } + signals.append(signal) + } + return combineLatest(signals) + |> map { values -> [(PeerId, AddChannelMemberError)] in + return values.compactMap { $0 } + } + } + } + } + + createInviteLinkImpl = { [weak contactsController] in + contactsController?.view.window?.endEditing(true) + contactsController?.present(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: groupPeer.id), initialInvite: nil, parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root)) + } + + parentController?.push(contactsController) + do { + selectAddMemberDisposable.set(( + combineLatest(queue: .mainQueue(), + context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: groupPeer.id)), + contactsController.result + ) + |> deliverOnMainQueue).start(next: { [weak contactsController] exportedInvitation, result in + var peers: [ContactListPeerId] = [] + if case let .result(peerIdsValue, _) = result { + peers = peerIdsValue + } + + contactsController?.displayProgress = true + addMemberDisposable.set((addMembers(peers) + |> deliverOnMainQueue).start(next: { failedPeerIds in + if failedPeerIds.isEmpty { + contactsController?.dismiss() + + let mappedPeerIds: [EnginePeer.Id] = peers.compactMap { peer -> EnginePeer.Id? in + switch peer { + case let .peer(id): + return id + default: + return nil + } + } + if !mappedPeerIds.isEmpty { + let _ = (context.engine.data.get(EngineDataMap(mappedPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))) + |> deliverOnMainQueue).startStandalone(next: { maybePeers in + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let peers = maybePeers.compactMap { $0.value } + + let text: String + if peers.count == 1 { + text = presentationData.strings.PeerInfo_NotificationMemberAdded(peers[0].displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + } else { + text = presentationData.strings.PeerInfo_NotificationMultipleMembersAdded(Int32(peers.count)) + } + parentController?.present(UndoOverlayController(presentationData: presentationData, content: .peers(context: context, peers: peers, title: nil, text: text, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + }) + } + } else { + let failedPeers = failedPeerIds.compactMap { _, error -> TelegramForbiddenInvitePeer? in + if case let .restricted(peer) = error { + return peer + } else { + return nil + } + } + + if !failedPeers.isEmpty, let contactsController, let navigationController = contactsController.navigationController as? NavigationController { + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === contactsController }) { + let inviteScreen = SendInviteLinkScreen(context: context, subject: .chat(peer: EnginePeer(groupPeer), link: exportedInvitation?.link), peers: failedPeers) + viewControllers.remove(at: index) + viewControllers.append(inviteScreen) + navigationController.setViewControllers(viewControllers, animated: true) + } + } else { + contactsController?.dismiss() + } + } + })) + })) + contactsController.dismissed = { + selectAddMemberDisposable.set(nil) + addMemberDisposable.set(nil) + } + } + }) +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift index 22e0ecf4..925c3098 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift @@ -676,7 +676,7 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } if self.selectionModeCompletion != nil { - self.titleView?.titleContent = .custom(presentationData.strings.Stories_AddStoriesTitle, nil, false) + self.titleView?.titleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(presentationData.strings.Stories_AddStoriesTitle))], subtitle: nil, isEnabled: false) } else { switch self.scope { case .saved: @@ -691,7 +691,7 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer { } else { title = nil } - self.titleView?.titleContent = .custom(presentationData.strings.StoryList_TitleSaved, title, false) + self.titleView?.titleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(presentationData.strings.StoryList_TitleSaved))], subtitle: title, isEnabled: false) if paneNode.isSelectionModeActive { self.navigationItem.setRightBarButton(self.doneBarButtonItem, animated: false) @@ -708,7 +708,7 @@ public class PeerInfoStoryGridScreen: ViewControllerComponentContainer { } else { title = presentationData.strings.StoryList_TitleArchive } - self.titleView?.titleContent = .custom(title, nil, false) + self.titleView?.titleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(title))], subtitle: nil, isEnabled: false) var hasMenu = false if componentView.selectedCount != 0 { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift index 025a6703..3a15d112 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift @@ -277,12 +277,12 @@ public final class StorySearchGridScreen: ViewControllerComponentContainer { switch self.scope { case let .query(peer, query): if let peer, let addressName = peer.addressName { - self.titleView?.titleContent = .custom("\(query)@\(addressName)", title, false) + self.titleView?.titleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text("\(query)@\(addressName)"))], subtitle: title, isEnabled: false) } else { - self.titleView?.titleContent = .custom("\(query)", title, false) + self.titleView?.titleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text("\(query)"))], subtitle: title, isEnabled: false) } case .location: - self.titleView?.titleContent = .custom(presentationData.strings.StoryGridScreen_TitleLocationSearch, nil, false) + self.titleView?.titleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(presentationData.strings.StoryGridScreen_TitleLocationSearch))], subtitle: nil, isEnabled: false) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD index 16458e00..3719417c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD @@ -58,7 +58,8 @@ swift_library( "//submodules/TelegramUI/Components/BottomButtonPanelComponent", "//submodules/PromptUI", "//submodules/TelegramUI/Components/EmojiTextAttachmentView", - "//submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent" + "//submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift index 26e09bea..60884480 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift @@ -484,18 +484,18 @@ private final class FilterHeaderButton: HighlightableButtonNode { component: AnyComponent( BundleIconComponent( name: "Peer Info/SortIcon", - tintColor: theme.rootController.navigationBar.accentTextColor + tintColor: theme.chat.inputPanel.panelControlColor ) ), environment: {}, - containerSize: CGSize(width: 30.0, height: 30.0) + containerSize: CGSize(width: 44.0, height: 44.0) ) if let view = self.icon.view { if view.superview == nil { view.isUserInteractionEnabled = false self.referenceNode.view.addSubview(view) } - view.frame = CGRect(origin: CGPoint(x: 14.0, y: 7.0), size: iconSize) + view.frame = CGRect(origin: CGPoint(x: 7.0, y: 7.0), size: iconSize) } self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 44.0, height: 44.0)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index c1409f7b..d9a85eb9 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -35,6 +35,7 @@ import EmojiTextAttachmentView import TextFormat import PromptUI import CollectionTabItemComponent +import EdgeEffect public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate { public enum GiftCollection: Equatable { @@ -91,8 +92,8 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private let tabSelector = ComponentView() public private(set) var currentCollection: GiftCollection = .all - private var panelBackground: NavigationBackgroundNode? - private var panelSeparator: ASDisplayNode? + private var panelEdgeEffectView: EdgeEffectView? + private var panelContentContainer: UIView? private var panelButton: ComponentView? private var panelCheck: ComponentView? @@ -233,7 +234,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr return } - let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Title, titleFont: .bold, subtitle: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Text, value: "", placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 12, displayCharacterLimit: true, apply: { [weak self] value in + let promptController = promptController(context: self.context, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Title, titleFont: .bold, subtitle: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Text, value: "", placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 12, displayCharacterLimit: true, apply: { [weak self] value in guard let self, let value else { return } @@ -304,7 +305,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr return } - let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_RenameCollection_Title, titleFont: .bold, value: collection.title, placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 12, displayCharacterLimit: true, apply: { [weak self] value in + let promptController = promptController(context: self.context, updatedPresentationData: nil, text: params.presentationData.strings.PeerInfo_Gifts_RenameCollection_Title, titleFont: .bold, value: collection.title, placeholder: params.presentationData.strings.PeerInfo_Gifts_CreateCollection_Placeholder, characterLimit: 12, displayCharacterLimit: true, apply: { [weak self] value in guard let self, let value else { return } @@ -579,7 +580,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr if let params = self.currentParams { let visibleBounds = self.scrollNode.bounds.insetBy(dx: 0.0, dy: -10.0) - var topInset: CGFloat = 60.0 + var topInset: CGFloat = params.topInset var canEditCollections = false if self.peerId == self.context.account.peerId || self.canManage { @@ -657,14 +658,15 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr component: AnyComponent(TabSelectorComponent( context: self.context, colors: TabSelectorComponent.Colors( - foreground: params.presentationData.theme.list.itemSecondaryTextColor, + foreground: params.presentationData.theme.list.itemPrimaryTextColor, selection: params.presentationData.theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15), simple: true ), theme: params.presentationData.theme, customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(14.0), - spacing: 2.0 + font: Font.medium(15.0), + spacing: 2.0, + height: 44.0 - 5.0 * 2.0 ), items: tabSelectorItems, selectedId: AnyHashable(self.currentCollection.rawValue), @@ -701,7 +703,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } )), environment: {}, - containerSize: CGSize(width: params.size.width - 10.0 * 2.0, height: 50.0) + containerSize: CGSize(width: params.size.width - 14.0 * 2.0, height: 44.0) ) if let tabSelectorView = self.tabSelector.view { if tabSelectorView.superview == nil { @@ -712,9 +714,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr tabSelectorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } } - transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((params.size.width - tabSelectorSize.width) / 2.0), y: 60.0), size: tabSelectorSize)) + transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((params.size.width - tabSelectorSize.width) / 2.0), y: topInset), size: tabSelectorSize)) - topInset += tabSelectorSize.height + 14.0 + topInset += tabSelectorSize.height + 15.0 } } else if let tabSelectorView = self.tabSelector.view { tabSelectorView.alpha = 0.0 @@ -731,32 +733,31 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let bottomInset = params.bottomInset let presentationData = params.presentationData - let themeUpdated = self.theme !== presentationData.theme self.theme = presentationData.theme - let panelBackground: NavigationBackgroundNode - let panelSeparator: ASDisplayNode + let panelEdgeEffectView: EdgeEffectView + let panelContentContainer: UIView var panelVisibility = params.expandProgress < 1.0 ? 0.0 : 1.0 if !self.canGift || self.resultsAreEmpty { panelVisibility = 0.0 } - let panelTransition: ComponentTransition = .immediate - if let current = self.panelBackground { - panelBackground = current + if let current = self.panelContentContainer { + panelContentContainer = current } else { - panelBackground = NavigationBackgroundNode(color: presentationData.theme.rootController.tabBar.backgroundColor) - self.addSubnode(panelBackground) - self.panelBackground = panelBackground + panelContentContainer = UIView() + self.view.addSubview(panelContentContainer) + self.panelContentContainer = panelContentContainer } - if let current = self.panelSeparator { - panelSeparator = current + let panelTransition: ComponentTransition = .immediate + if let current = self.panelEdgeEffectView { + panelEdgeEffectView = current } else { - panelSeparator = ASDisplayNode() - panelBackground.addSubnode(panelSeparator) - self.panelSeparator = panelSeparator + panelEdgeEffectView = EdgeEffectView() + panelContentContainer.addSubview(panelEdgeEffectView) + self.panelEdgeEffectView = panelEdgeEffectView } let panelButton: ComponentView @@ -767,7 +768,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self.panelButton = panelButton } - let buttonSideInset = sideInset + 16.0 + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: params.bottomInset, innerDiameter: 52.0 * 0.5, sideInset: sideInset + 30.0) let buttonTitle: String var buttonIconName: String? @@ -800,6 +801,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr component: AnyComponent( ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: presentationData.theme.list.itemCheckColors.fillColor, foreground: presentationData.theme.list.itemCheckColors.foregroundColor, pressedColor: presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) @@ -815,32 +817,27 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr ) ), environment: {}, - containerSize: CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0) + containerSize: CGSize(width: size.width - buttonInsets.left * 2.0, height: 52.0) ) var scrollOffset: CGFloat = max(0.0, size.height - params.visibleHeight) - let effectiveBottomInset = max(8.0, bottomInset) - var bottomPanelHeight = effectiveBottomInset + panelButtonSize.height + 8.0 + let effectiveBottomInset = max(buttonInsets.bottom, bottomInset) + let bottomPanelHeight = effectiveBottomInset + panelButtonSize.height + 8.0 if params.visibleHeight < 110.0 { scrollOffset -= bottomPanelHeight } if let panelButtonView = panelButton.view { if panelButtonView.superview == nil { - panelBackground.view.addSubview(panelButtonView) + panelContentContainer.addSubview(panelButtonView) } - panelButtonView.frame = CGRect(origin: CGPoint(x: buttonSideInset, y: 8.0), size: panelButtonSize) + panelButtonView.frame = CGRect(origin: CGPoint(x: buttonInsets.left, y: 8.0), size: panelButtonSize) } - if themeUpdated { - panelBackground.updateColor(color: presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) - panelSeparator.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor - } + panelTransition.setFrame(view: panelContentContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - bottomPanelHeight), size: CGSize(width: size.width, height: bottomPanelHeight))) if self.canManage { - bottomPanelHeight -= 9.0 - let panelCheck: ComponentView if let current = self.panelCheck { panelCheck = current @@ -903,20 +900,20 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr ) if let panelCheckView = panelCheck.view { if panelCheckView.superview == nil { - panelBackground.view.addSubview(panelCheckView) + panelContentContainer.addSubview(panelCheckView) } - panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: 16.0), size: panelCheckSize) + panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: 16.0 + 16.0), size: panelCheckSize) } if let panelButtonView = panelButton.view { panelButtonView.isHidden = true } } - panelTransition.setFrame(view: panelBackground.view, frame: CGRect(x: 0.0, y: size.height - bottomPanelHeight - scrollOffset, width: size.width, height: bottomPanelHeight)) - ComponentTransition.spring(duration: 0.4).setSublayerTransform(view: panelBackground.view, transform: CATransform3DMakeTranslation(0.0, bottomPanelHeight * (1.0 - panelVisibility), 0.0)) + let edgeEffectFrame = CGRect(x: 0.0, y: 0.0, width: size.width, height: bottomPanelHeight) + panelTransition.setFrame(view: panelEdgeEffectView, frame: edgeEffectFrame) + panelEdgeEffectView.update(content: presentationData.theme.list.blocksBackgroundColor, blur: false, rect: edgeEffectFrame, edge: .bottom, edgeSize: 40.0, transition: panelTransition) - panelBackground.update(size: CGSize(width: size.width, height: bottomPanelHeight), transition: transition.containedViewLayoutTransition) - panelTransition.setFrame(view: panelSeparator.view, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: UIScreenPixel)) + ComponentTransition.spring(duration: 0.4).setSublayerTransform(view: panelContentContainer, transform: CATransform3DMakeTranslation(0.0, bottomPanelHeight * (1.0 - panelVisibility), 0.0)) contentHeight += bottomPanelHeight bottomScrollInset = bottomPanelHeight - 40.0 @@ -932,7 +929,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let bottomContentOffset = max(0.0, self.scrollNode.view.contentSize.height - self.scrollNode.view.contentOffset.y - self.scrollNode.view.frame.height) if bottomContentOffset < 200.0 { - self.giftsListView.loadMore() + Queue.mainQueue().justDispatch { + self.giftsListView.loadMore() + } } } @@ -1438,7 +1437,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self.presentationDataPromise.set(.single(presentationData)) self.backgroundNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 48.0), size: size)) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: size.height - topInset))) transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) let visibleBounds = self.scrollNode.bounds.insetBy(dx: 0.0, dy: -10.0) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index d6810166..4df8073a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -6,6 +6,7 @@ import TelegramCore import SwiftSignalKit import Postbox import TelegramPresentationData +import PresentationDataUtils import AccountContext import ContextUI import PhotoResources @@ -1824,6 +1825,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.maxStoriesPerFolder = maxStoriesPerFolder super.init() + + self.clipsToBounds = true if case .peer = self.scope { let _ = (ApplicationSpecificNotice.getSharedMediaScrollingTooltip(accountManager: context.sharedContext.accountManager) @@ -2112,7 +2115,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } if case .botPreview = scope { - let backgroundColor = presentationData.theme.list.plainBackgroundColor + let backgroundColor = presentationData.theme.list.blocksBackgroundColor let foregroundColor = presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.6) return SparseItemGrid.ShimmerColors(background: backgroundColor.argb, foreground: foregroundColor.argb) @@ -4024,14 +4027,17 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr transition: folderTabTransition, component: AnyComponent(TabSelectorComponent( colors: TabSelectorComponent.Colors( - foreground: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.8), - selection: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05) + foreground: self.presentationData.theme.list.itemPrimaryTextColor, + selection: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), + normal: self.presentationData.theme.list.itemPrimaryTextColor, + simple: true ), theme: self.presentationData.theme, customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(14.0), + font: Font.medium(15.0), spacing: 9.0, - verticalInset: 11.0 + verticalInset: 11.0, + height: 44.0 - 5.0 * 2.0 ), items: folderItems, selectedId: selectedId, @@ -4090,9 +4096,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } )), environment: {}, - containerSize: CGSize(width: size.width, height: 44.0) + containerSize: CGSize(width: size.width - 6.0 * 2.0, height: 44.0) ) - var folderTabFrame = CGRect(origin: CGPoint(x: floor((size.width - folderTabSize.width) * 0.5), y: topInset - 11.0), size: folderTabSize) + var folderTabFrame = CGRect(origin: CGPoint(x: floor((size.width - folderTabSize.width) * 0.5), y: topInset - 21.0), size: folderTabSize) let effectiveScrollingOffset: CGFloat effectiveScrollingOffset = self.itemGrid.scrollingOffset @@ -4236,7 +4242,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } var hasBarBackground = false - if self.isProfileEmbedded { + if self.isProfileEmbedded && !"".isEmpty { if case .botPreview = self.scope { hasBarBackground = true } else if case let .peer(_, _, isArchived) = self.scope, ((self.canManageStories && !isArchived) || !self.currentStoryFolders.isEmpty) { @@ -4278,10 +4284,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if case .botPreview = self.scope { updateBotPreviewFooter(size: size, bottomInset: 0.0, transition: transition) if let botPreviewFooterView = self.botPreviewFooter?.view { - listBottomInset += 18.0 + botPreviewFooterView.bounds.height + listBottomInset += 18.0 + botPreviewFooterView.bounds.height + } } } - } if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories, case let .peer(peerId, _, isArchived) = self.scope { let selectionPanel: ComponentView @@ -4589,12 +4595,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } else { backgroundColor = presentationData.theme.list.blocksBackgroundColor } + let _ = backgroundColor - if self.didUpdateItemsOnce { + /*if self.didUpdateItemsOnce { ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: backgroundColor) } else { self.view.backgroundColor = backgroundColor - } + }*/ } else { let emptyStateView: ComponentView var emptyStateTransition = ComponentTransition(transition) @@ -4656,12 +4663,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } else { backgroundColor = presentationData.theme.list.blocksBackgroundColor } + let _ = backgroundColor - if self.didUpdateItemsOnce { + /*if self.didUpdateItemsOnce { ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: backgroundColor) } else { self.view.backgroundColor = backgroundColor - } + }*/ } } else if case .botPreview = self.scope, let items = self.items, items.items.isEmpty, items.count == 0 { let emptyStateView: ComponentView @@ -4741,12 +4749,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } else { backgroundColor = presentationData.theme.list.blocksBackgroundColor } + let _ = backgroundColor - if self.didUpdateItemsOnce { + /*if self.didUpdateItemsOnce { ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: backgroundColor) } else { self.view.backgroundColor = backgroundColor - } + }*/ } else if case let .peer(_, _, isArchived) = self.scope, self.canManageStories, !isArchived, self.isProfileEmbedded, let items = self.items, items.items.isEmpty, items.count == 0 { let emptyStateView: ComponentView var emptyStateTransition = ComponentTransition(transition) @@ -4822,12 +4831,13 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } else { backgroundColor = presentationData.theme.list.blocksBackgroundColor } + let _ = backgroundColor - if self.didUpdateItemsOnce { + /*if self.didUpdateItemsOnce { ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: backgroundColor) } else { self.view.backgroundColor = backgroundColor - } + }*/ } else { if let emptyStateView = self.emptyStateView { let subTransition = ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)) @@ -4843,17 +4853,17 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } } - if self.isProfileEmbedded, case .botPreview = self.scope { + /*if self.isProfileEmbedded, case .botPreview = self.scope { subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor) } else if self.isProfileEmbedded, case let .peer(_, _, isArchived) = self.scope, ((self.canManageStories && !isArchived) || !self.currentStoryFolders.isEmpty), self.isProfileEmbedded { subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor) } else if self.isProfileEmbedded { - subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.plainBackgroundColor) + subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor) } else { subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor) - } + }*/ } else { - if self.isProfileEmbedded, case .botPreview = self.scope { + /*if self.isProfileEmbedded, case .botPreview = self.scope { self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor } else if self.isProfileEmbedded, case let .peer(_, _, isArchived) = self.scope, self.canManageStories, self.isProfileEmbedded, !isArchived { self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor @@ -4863,7 +4873,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } else { self.view.backgroundColor = .clear } - } + }*/ } } @@ -5013,7 +5023,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr private func presentAddStoryFolder(addItems: [EngineStoryItem] = []) { let promptController = promptController( - sharedContext: self.context.sharedContext, + context: self.context, updatedPresentationData: nil, text: self.presentationData.strings.Stories_CreateAlbum_Title, titleFont: .bold, @@ -5046,7 +5056,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr private func presentRenameStoryFolder(id: Int64, title: String) { let promptController = promptController( - sharedContext: self.context.sharedContext, + context: self.context, updatedPresentationData: nil, text: self.presentationData.strings.Stories_EditAlbum_Title, titleFont: .bold, @@ -5173,14 +5183,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } public func presentUnableToAddMorePreviewsAlert() { - self.parentController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.BotPreviews_AlertTooManyPreviews(Int32(self.maxBotPreviewCount)), actions: [ + self.parentController?.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.BotPreviews_AlertTooManyPreviews(Int32(self.maxBotPreviewCount)), actions: [ TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { }) ], parseMarkdown: true), in: .window(.root)) } public func presentDeleteBotPreviewLanguage() { - self.parentController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: self.presentationData.strings.BotPreviews_DeleteTranslationAlert_Title, text: self.presentationData.strings.BotPreviews_DeleteTranslationAlert_Text, actions: [ + self.parentController?.present(textAlertController(context: self.context, title: self.presentationData.strings.BotPreviews_DeleteTranslationAlert_Title, text: self.presentationData.strings.BotPreviews_DeleteTranslationAlert_Text, actions: [ TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.Common_OK, action: { [weak self] in diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD index 4166ff84..d0bc74ab 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD @@ -24,6 +24,7 @@ swift_library( "//submodules/Components/MultilineTextComponent", "//submodules/Markdown", "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/Components/BundleIconComponent", "//submodules/TextFormat", "//submodules/TelegramUI/Components/ListSectionComponent", @@ -32,6 +33,8 @@ swift_library( "//submodules/TelegramStringFormatting", "//submodules/TelegramUI/Components/ListItemComponentAdaptor", "//submodules/TelegramUI/Components/PeerInfo/MessagePriceItem", + "//submodules/UndoUI", + "//submodules/ShareController", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift index be77701a..4917bbf0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift @@ -22,6 +22,10 @@ import Markdown import TelegramStringFormatting import MessagePriceItem import ListItemComponentAdaptor +import ButtonComponent +import PlainButtonComponent +import UndoUI +import ShareController final class PostSuggestionsSettingsScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -68,6 +72,7 @@ final class PostSuggestionsSettingsScreenComponent: Component { private let subtitle = ComponentView() private let switchSection = ComponentView() private let contentSection = ComponentView() + private let linkSection = ComponentView() private var isUpdating: Bool = false @@ -166,6 +171,91 @@ final class PostSuggestionsSettingsScreenComponent: Component { } } + func dismissAllTooltips() { + guard let environment = self.environment, let controller = environment.controller() else { + return + } + controller.window?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitAction() + } + }) + } + + func copyLink(_ link: String) { + guard let component = self.component, let environment = self.environment, let controller = environment.controller() else { + return + } + UIPasteboard.general.string = link + + self.dismissAllTooltips() + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + } + + func shareLink(_ link: String) { + guard let component = self.component, let environment = self.environment, let controller = environment.controller() else { + return + } + + let context = component.context + let shareController = ShareController(context: context, subject: .url(link), updatedPresentationData: nil) + shareController.completed = { [weak controller] peerIds in + let _ = (context.engine.data.get( + EngineDataList( + peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) + ) + ) + |> deliverOnMainQueue).start(next: { [weak controller] peerList in + let peers = peerList.compactMap { $0 } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let text: String + var savedMessages = false + if peerIds.count == 1, let peerId = peerIds.first, peerId == context.account.peerId { + text = presentationData.strings.InviteLink_InviteLinkForwardTooltip_SavedMessages_One + savedMessages = true + } else { + if peers.count == 1, let peer = peers.first { + let peerName = peer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.UserInfo_LinkForwardTooltip_Chat_One(peerName).string + } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { + let firstPeerName = firstPeer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let secondPeerName = secondPeer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.UserInfo_LinkForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string + } else if let peer = peers.first { + let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + text = presentationData.strings.UserInfo_LinkForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string + } else { + text = "" + } + } + + controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in + if savedMessages, action == .info { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak controller] peer in + guard let peer else { + return + } + guard let navigationController = controller?.navigationController as? NavigationController else { + return + } + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), forceOpenChat: true)) + }) + } + return false + }), in: .window(.root)) + }) + } + shareController.actionCompleted = { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + } + controller.present(shareController, in: .window(.root)) + } + func update(component: PostSuggestionsSettingsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -429,6 +519,50 @@ final class PostSuggestionsSettingsScreenComponent: Component { if self.areSuggestionsEnabled { contentHeight += contentSectionSize.height + contentHeight += sectionSpacing + } + + let address = component.peer?.addressName ?? "" + let link = "t.me/\(address)?direct" + let fullLink = "https://\(link)" + var linkSectionItems: [AnyComponentWithIdentity] = [] + linkSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent( + LinkComponent( + theme: environment.theme, + strings: environment.strings, + link: link, + copyAction: { [weak self] in + self?.copyLink(fullLink) + }, + shareAction: { [weak self] in + self?.shareLink(fullLink) + } + ) + ))) + let linkSectionSize = self.linkSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + style: .glass, + header: nil, + footer: nil, + items: linkSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let linkSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: linkSectionSize) + if let linkSectionView = self.linkSection.view { + if linkSectionView.superview == nil { + self.scrollView.addSubview(linkSectionView) + self.linkSection.parentState = state + } + transition.setFrame(view: linkSectionView, frame: linkSectionFrame) + alphaTransition.setAlpha(view: linkSectionView, alpha: self.areSuggestionsEnabled && !address.isEmpty ? 1.0 : 0.0) + } + if self.areSuggestionsEnabled && !address.isEmpty { + contentHeight += switchSectionSize.height + contentHeight += sectionSpacing } contentHeight += bottomContentInset @@ -546,3 +680,323 @@ public final class PostSuggestionsSettingsScreen: ViewControllerComponentContain super.containerLayoutUpdated(layout, transition: transition) } } + + +private final class LinkContentComponent: Component { + let theme: PresentationTheme + let link: String + + init( + theme: PresentationTheme, + link: String + ) { + self.theme = theme + self.link = link + } + + static func ==(lhs: LinkContentComponent, rhs: LinkContentComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.link != rhs.link { + return false + } + return true + } + + final class View: UIView { + private var component: LinkContentComponent? + + private let background = ComponentView() + private let link = ComponentView() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: LinkContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let padding: CGFloat = 10.0 + + let backgroundSize = self.background.update( + transition: transition, + component: AnyComponent( + FilledRoundedRectangleComponent( + color: component.theme.list.itemInputField.backgroundColor, + cornerRadius: .minEdge, + smoothCorners: false + ) + ), + environment: {}, + containerSize: availableSize + ) + let backgroundFrame = CGRect(origin: .zero, size: backgroundSize) + if let backgroundView = self.background.view { + if backgroundView.superview == nil { + self.addSubview(backgroundView) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + + let linkFont = Font.regular(17.0) + let linkSize = self.link.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: component.link, font: linkFont, textColor: component.theme.list.itemPrimaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 2 + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - padding * 4.0, height: availableSize.height) + ) + let linkFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - linkSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - linkSize.height) / 2.0) - UIScreenPixel), size: linkSize) + if let linkView = self.link.view { + if linkView.superview == nil { + self.addSubview(linkView) + } + transition.setFrame(view: linkView, frame: linkFrame) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class LinkComponent: Component { + let theme: PresentationTheme + let strings: PresentationStrings + let link: String + let copyAction: () -> Void + let shareAction: () -> Void + + init( + theme: PresentationTheme, + strings: PresentationStrings, + link: String, + copyAction: @escaping () -> Void, + shareAction: @escaping () -> Void + ) { + self.theme = theme + self.strings = strings + self.link = link + self.copyAction = copyAction + self.shareAction = shareAction + } + + static func ==(lhs: LinkComponent, rhs: LinkComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.link != rhs.link { + return false + } + return true + } + + final class View: UIView { + private let linkButton = ComponentView() + private let moreButton = ComponentView() + private var copyButton = ComponentView() + private var shareButton = ComponentView() + + private var component: LinkComponent? + private weak var state: EmptyComponentState? + + private var cachedMoreImage: (UIImage, PresentationTheme)? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: LinkComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let sideInset: CGFloat = 16.0 + var contentHeight: CGFloat = sideInset + + let linkButtonSize = self.linkButton.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent(LinkContentComponent(theme: component.theme, link: component.link)), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.copyAction() + }, + animateScale: false + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 52.0) + ) + let linkButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: linkButtonSize) + if let linkButtonView = self.linkButton.view { + if linkButtonView.superview == nil { + self.addSubview(linkButtonView) + } + linkButtonView.frame = linkButtonFrame + } + + let moreButtonImage: UIImage + if let (image, theme) = self.cachedMoreImage, theme === component.theme { + moreButtonImage = image + } else { + moreButtonImage = actionButtonImage(color: component.theme.list.itemInputField.controlColor)! + self.cachedMoreImage = (moreButtonImage, component.theme) + } + + let moreButtonSize = self.moreButton.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent(Image(image: moreButtonImage, contentMode: .center)), + minSize: CGSize(width: 52.0, height: 52.0), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.copyAction() + }, + animateScale: false + ) + ), + environment: {}, + containerSize: CGSize(width: 52.0, height: 52.0) + ) + let moreButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - moreButtonSize.width, y: contentHeight), size: moreButtonSize) + if let moreButtonView = self.moreButton.view { + if moreButtonView.superview == nil { + self.addSubview(moreButtonView) + } + moreButtonView.frame = moreButtonFrame + } + + contentHeight += linkButtonSize.height + contentHeight += 10.0 + + var buttonWidth = availableSize.width - sideInset * 2.0 + buttonWidth = (buttonWidth - 10.0) / 2.0 + + let copyButtonSize = self.copyButton.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: component.theme.list.itemCheckColors.fillColor, + foreground: component.theme.list.itemCheckColors.foregroundColor, + pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: component.strings.FolderLinkScreen_LinkActionCopy, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.copyAction() + } + )), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: 52.0) + ) + let copyButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: copyButtonSize) + if let copyButtonView = self.copyButton.view { + if copyButtonView.superview == nil { + self.addSubview(copyButtonView) + } + copyButtonView.frame = copyButtonFrame + } + + let shareButtonSize = self.shareButton.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: component.theme.list.itemCheckColors.fillColor, + foreground: component.theme.list.itemCheckColors.foregroundColor, + pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: component.strings.FolderLinkScreen_LinkActionShare, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.shareAction() + } + )), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: 52.0) + ) + let shareButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - shareButtonSize.width, y: contentHeight), size: shareButtonSize) + if let shareButtonView = self.shareButton.view { + if shareButtonView.superview == nil { + self.addSubview(shareButtonView) + } + shareButtonView.frame = shareButtonFrame + } + + contentHeight += copyButtonSize.height + contentHeight += sideInset + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +func stringForRemainingTime(_ duration: Int32) -> String { + let hours = duration / 3600 + let minutes = duration / 60 % 60 + let seconds = duration % 60 + let durationString: String + if hours > 0 { + durationString = String(format: "%d:%02d", hours, minutes) + } else { + durationString = String(format: "%02d:%02d", minutes, seconds) + } + return durationString +} + +private func actionButtonImage(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(color.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + context.setBlendMode(.clear) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 4.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0))) + }) +} diff --git a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/Sources/ProfileLevelRatingBarComponent.swift b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/Sources/ProfileLevelRatingBarComponent.swift index 8ba5e861..102613ff 100644 --- a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/Sources/ProfileLevelRatingBarComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/Sources/ProfileLevelRatingBarComponent.swift @@ -483,7 +483,7 @@ public final class ProfileLevelRatingBarComponent: Component { self.state = state if self.barBackground.image == nil { - self.barBackground.image = generateStretchableFilledCircleImage(diameter: 12.0, color: .white)?.withRenderingMode(.alwaysTemplate) + self.barBackground.image = generateStretchableFilledCircleImage(diameter: 30.0, color: .white)?.withRenderingMode(.alwaysTemplate) self.barForeground.image = self.barBackground.image } diff --git a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift index 5541bace..9f563290 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift +++ b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift @@ -391,7 +391,7 @@ private final class OldChannelsSearchItemNode: ItemListControllerSearchNode { self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: OldChannelsSearchContainerNode(context: self.context, peers: self.peers, selectedPeerIds: self.selectedPeerIds, togglePeer: self.togglePeer), cancel: { [weak self] in self?.cancel() - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in diff --git a/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/BUILD b/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/BUILD index 86c46876..0afb5003 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/BUILD +++ b/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/BUILD @@ -18,11 +18,12 @@ swift_library( "//submodules/PresentationDataUtils", "//submodules/AccountContext", "//submodules/TextFormat", - "//submodules/AlertUI", "//submodules/PasswordSetupUI", "//submodules/Markdown", "//submodules/ActivityIndicator", "//submodules/TelegramUI/Components/PeerManagement/OldChannelsController", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/Sources/ChannelOwnershipTransferController.swift b/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/Sources/ChannelOwnershipTransferController.swift index e68b26f8..4ed82f94 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/Sources/ChannelOwnershipTransferController.swift +++ b/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/Sources/ChannelOwnershipTransferController.swift @@ -5,460 +5,92 @@ import Display import SwiftSignalKit import TelegramCore import TelegramPresentationData -import ActivityIndicator import TextFormat import AccountContext -import AlertUI import PresentationDataUtils import PasswordSetupUI -import Markdown import OldChannelsController +import ComponentFlow +import AlertComponent +import AlertInputFieldComponent -private final class ChannelOwnershipTransferPasswordFieldNode: ASDisplayNode, UITextFieldDelegate { - private var theme: PresentationTheme - private let backgroundNode: ASImageNode - private let textInputNode: TextFieldNode - private let placeholderNode: ASTextNode - private var clearOnce: Bool = false - private let inputActivityNode: ActivityIndicator - - private var isChecking = false - - var complete: (() -> Void)? - var textChanged: ((String) -> Void)? - - private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 22.0, bottom: 15.0, right: 22.0) - private let inputInsets = UIEdgeInsets(top: 5.0, left: 11.0, bottom: 5.0, right: 11.0) - - var password: String { - get { - return self.textInputNode.textField.text ?? "" - } - set { - self.textInputNode.textField.text = newValue - self.placeholderNode.isHidden = !newValue.isEmpty - } - } - - var placeholder: String = "" { - didSet { - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - } - } - - init(theme: PresentationTheme, placeholder: String) { - self.theme = theme - - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: UIScreenPixel) - - self.textInputNode = TextFieldNode() +private func commitChannelOwnershipTransferController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + peer: EnginePeer, + member: TelegramUser, + present: @escaping (ViewController, Any?) -> Void, + push: @escaping (ViewController) -> Void, + completion: @escaping (EnginePeer.Id?) -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings - self.placeholderNode = ASTextNode() - self.placeholderNode.isUserInteractionEnabled = false - self.placeholderNode.displaysAsynchronously = false - self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - - self.inputActivityNode = ActivityIndicator(type: .custom(theme.list.itemAccentColor, 18.0, 1.5, false)) - - super.init() - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.textInputNode) - self.addSubnode(self.placeholderNode) - self.addSubnode(self.inputActivityNode) - - self.inputActivityNode.isHidden = true - } - - override func didLoad() { - super.didLoad() - - self.textInputNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(14.0), NSAttributedString.Key.foregroundColor: self.theme.actionSheet.inputTextColor] - self.textInputNode.textField.font = Font.regular(14.0) - self.textInputNode.textField.textColor = self.theme.list.itemPrimaryTextColor - self.textInputNode.textField.isSecureTextEntry = true - self.textInputNode.textField.returnKeyType = .done - self.textInputNode.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance - self.textInputNode.clipsToBounds = true - self.textInputNode.textField.delegate = self - self.textInputNode.textField.addTarget(self, action: #selector(self.textFieldTextChanged(_:)), for: .editingChanged) - self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) - self.textInputNode.textField.tintColor = self.theme.list.itemAccentColor - } - - func updateTheme(_ theme: PresentationTheme) { - self.theme = theme - - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: UIScreenPixel) - self.textInputNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance - self.textInputNode.textField.textColor = theme.list.itemPrimaryTextColor - self.textInputNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(14.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor] - self.textInputNode.textField.tintColor = theme.list.itemAccentColor - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(14.0), textColor: theme.actionSheet.inputPlaceholderColor) - } - - func updateIsChecking(_ isChecking: Bool) { - self.isChecking = isChecking - self.inputActivityNode.isHidden = !isChecking - } - - func updateIsInvalid() { - self.clearOnce = true - } - - func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let textFieldHeight: CGFloat = 30.0 - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - - let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom)) - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - - let placeholderSize = self.placeholderNode.measure(backgroundFrame.size) - transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) - - transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height))) - - let activitySize = CGSize(width: 18.0, height: 18.0) - transition.updateFrame(node: self.inputActivityNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX - activitySize.width - 6.0, y: backgroundFrame.minY + floor((backgroundFrame.height - activitySize.height) / 2.0)), size: activitySize)) - - return panelHeight - } - - func activateInput() { - self.textInputNode.becomeFirstResponder() - } - - func deactivateInput() { - self.textInputNode.resignFirstResponder() - } - - @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { - self.textChanged?(editableTextNode.textView.text) - self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty - } - - @objc func textFieldTextChanged(_ textField: UITextField) { - let text = textField.text ?? "" - self.textChanged?(text) - self.placeholderNode.isHidden = !text.isEmpty - } - - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - if self.isChecking { - return false - } - - if string == "\n" { - self.complete?() - return false - } - - if self.clearOnce { - self.clearOnce = false - if range.length > string.count { - textField.text = "" - return false - } - } - - return true - } -} + let inputState = AlertInputFieldComponent.ExternalState() -public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - private let title: String - private let text: String - - private let titleNode: ASTextNode - private let textNode: ASTextNode - fileprivate let inputFieldNode: ChannelOwnershipTransferPasswordFieldNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let disposable = MetaDisposable() - - private var validLayout: CGSize? - - private let hapticFeedback = HapticFeedback() - - public var complete: (() -> Void)? { - didSet { - self.inputFieldNode.complete = self.complete - } + let doneIsEnabled: Signal = inputState.valueSignal + |> map { value in + return !value.isEmpty } - public var theme: PresentationTheme { - didSet { - self.inputFieldNode.updateTheme(self.theme) - } - } + let doneInProgressPromise = ValuePromise(false) - public override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - public init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, title: String, text: String, actions: [TextAlertAction]) { - self.strings = strings - self.theme = ptheme - self.title = title - self.text = text - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 2 - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 4 - - self.inputFieldNode = ChannelOwnershipTransferPasswordFieldNode(theme: ptheme, placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder) - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - - self.addSubnode(self.inputFieldNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - self.actionNodes.last?.actionEnabled = false - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.inputFieldNode.textChanged = { [weak self] text in - if let strongSelf = self, let lastNode = strongSelf.actionNodes.last { - lastNode.actionEnabled = !text.isEmpty - } - } - - self.updateTheme(theme) - } - - deinit { - self.disposable.dispose() - } - - public func dismissInput() { - self.inputFieldNode.deactivateInput() - } - - public var password: String { - return self.inputFieldNode.password - } - - public func updateIsChecking(_ checking: Bool) { - self.inputFieldNode.updateIsChecking(checking) - } - - public override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - public override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) - - let hadValidLayout = self.validLayout != nil - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let titleSize = self.titleNode.measure(measureSize) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 4.0 - - let textSize = self.textNode.measure(measureSize) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 6.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - var contentWidth = max(titleSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - let inputFieldWidth = resultWidth - let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) - let inputHeight = inputFieldHeight - transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight)) - transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0) - - let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + actionsHeight + inputHeight + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Channel_OwnershipTransfer_EnterPassword) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.Channel_OwnershipTransfer_EnterPasswordText)) + ) + )) + + var applyImpl: (() -> Void)? + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder, + isSecureTextEntry: true, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if !hadValidLayout { - self.inputFieldNode.activateInput() - } - - return resultSize - } + ) + ) + )) - public func animateError() { - self.inputFieldNode.updateIsInvalid() - self.inputFieldNode.layer.addShakeAnimation() - self.hapticFeedback.error() + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) } -} - -private func commitChannelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (EnginePeer.Id?) -> Void) -> ViewController { - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } var dismissImpl: (() -> Void)? - var proceedImpl: (() -> Void)? - - var pushControllerImpl: ((ViewController) -> Void)? - - let disposable = MetaDisposable() - - let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.Channel_OwnershipTransfer_EnterPassword, text: presentationData.strings.Channel_OwnershipTransfer_EnterPasswordText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?() - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.OwnershipTransfer_Transfer, action: { - proceedImpl?() - })]) - - contentNode.complete = { - proceedImpl?() - } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.inputFieldNode.updateTheme(presentationData.theme) - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - disposable.dispose() - } - dismissImpl = { [weak controller, weak contentNode] in - contentNode?.dismissInput() - controller?.dismissAnimated() - } - proceedImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - contentNode.updateIsChecking(true) + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.OwnershipTransfer_Transfer, type: .default, action: { + applyImpl?() + }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get()) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + applyImpl = { + doneInProgressPromise.set(true) let signal: Signal if case let .channel(peer) = peer { - signal = context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: peer.id, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in + signal = context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: peer.id, memberId: member.id, password: inputState.value) |> mapToSignal { _ in return .complete() } |> then(.single(nil)) @@ -478,7 +110,7 @@ private func commitChannelOwnershipTransferController(context: AccountContext, u guard let upgradedPeerId = upgradedPeerId else { return .fail(.generic) } - return context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: upgradedPeerId, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in + return context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: upgradedPeerId, memberId: member.id, password: inputState.value) |> mapToSignal { _ in return .complete() } |> then(.single(upgradedPeerId)) @@ -487,54 +119,61 @@ private func commitChannelOwnershipTransferController(context: AccountContext, u signal = .never() } - disposable.set((signal |> deliverOnMainQueue).start(next: { upgradedPeerId in + let _ = (signal + |> deliverOnMainQueue).start(next: { upgradedPeerId in dismissImpl?() completion(upgradedPeerId) - }, error: { [weak contentNode] error in + }, error: { error in var isGroup = true if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } + doneInProgressPromise.set(false) + var errorTextAndActions: (String, [TextAlertAction])? switch error { - case .tooMuchJoined: - pushControllerImpl?(oldChannelsController(context: context, intent: .upgrade)) - return - case .invalidPassword: - contentNode?.animateError() - case .limitExceeded: - errorTextAndActions = (presentationData.strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - case .adminsTooMuch: - errorTextAndActions = (isGroup ? presentationData.strings.Group_OwnershipTransfer_ErrorAdminsTooMuch : presentationData.strings.Channel_OwnershipTransfer_ErrorAdminsTooMuch, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - case .userPublicChannelsTooMuch: - errorTextAndActions = (presentationData.strings.Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - case .userLocatedGroupsTooMuch: - errorTextAndActions = (presentationData.strings.Group_OwnershipTransfer_ErrorLocatedGroupsTooMuch, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - case .userBlocked, .restricted: - errorTextAndActions = (isGroup ? presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted : presentationData.strings.Channel_OwnershipTransfer_ErrorPrivacyRestricted, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - default: - errorTextAndActions = (presentationData.strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + case .tooMuchJoined: + push(oldChannelsController(context: context, intent: .upgrade)) + return + case .invalidPassword: + inputState.animateError() + case .limitExceeded: + errorTextAndActions = (strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) + case .adminsTooMuch: + errorTextAndActions = (isGroup ? strings.Group_OwnershipTransfer_ErrorAdminsTooMuch : strings.Channel_OwnershipTransfer_ErrorAdminsTooMuch, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) + case .userPublicChannelsTooMuch: + errorTextAndActions = (strings.Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) + case .userLocatedGroupsTooMuch: + errorTextAndActions = (strings.Group_OwnershipTransfer_ErrorLocatedGroupsTooMuch, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) + case .userBlocked, .restricted: + errorTextAndActions = (isGroup ? strings.Group_OwnershipTransfer_ErrorPrivacyRestricted : strings.Channel_OwnershipTransfer_ErrorPrivacyRestricted, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) + default: + errorTextAndActions = (strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) } - contentNode?.updateIsChecking(false) - + if let (text, actions) = errorTextAndActions { dismissImpl?() present(textAlertController(context: context, title: nil, text: text, actions: actions), nil) } - })) + }) } - - pushControllerImpl = { [weak controller] c in - controller?.push(c) + dismissImpl = { [weak alertController] in + alertController?.dismiss(completion: nil) } - - return controller + return alertController } -private func confirmChannelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (EnginePeer.Id?) -> Void) -> ViewController { +private func confirmChannelOwnershipTransferController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + peer: EnginePeer, + member: TelegramUser, + present: @escaping (ViewController, Any?) -> Void, + push: @escaping (ViewController) -> Void, + completion: @escaping (EnginePeer.Id?) -> Void +) -> ViewController { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - let theme = AlertControllerTheme(presentationData: presentationData) var isGroup = true if case let .channel(channel) = peer, case .broadcast = channel.info { @@ -551,69 +190,82 @@ private func confirmChannelOwnershipTransferController(context: AccountContext, text = presentationData.strings.Channel_OwnershipTransfer_DescriptionInfo(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), EnginePeer.user(member).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string } - let attributedTitle = NSAttributedString(string: title, font: Font.semibold(presentationData.listsFontSize.baseDisplaySize), textColor: theme.primaryColor, paragraphAlignment: .center) - let body = MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor) - let bold = MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - let controller = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_OwnershipTransfer_ChangeOwner, action: { - present(commitChannelOwnershipTransferController(context: context, peer: peer, member: member, present: present, completion: completion), nil) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: { - })], actionLayout: .vertical) + let controller = textAlertController( + context: context, + updatedPresentationData: updatedPresentationData, + title: title, + text: text, + actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_OwnershipTransfer_ChangeOwner, action: { + present(commitChannelOwnershipTransferController(context: context, peer: peer, member: member, present: present, push: push, completion: completion), nil) + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}) + ], + actionLayout: .vertical + ) return controller } -public func channelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, member: TelegramUser, initialError: ChannelOwnershipTransferError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (EnginePeer.Id?) -> Void) -> ViewController { +public func channelOwnershipTransferController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + peer: EnginePeer, + member: TelegramUser, + initialError: ChannelOwnershipTransferError, + present: @escaping (ViewController, Any?) -> Void, + push: @escaping (ViewController) -> Void, + completion: @escaping (EnginePeer.Id?) -> Void +) -> ViewController { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - let theme = AlertControllerTheme(presentationData: presentationData) + let strings = presentationData.strings - var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.OwnershipTransfer_SecurityCheck, font: Font.semibold(presentationData.listsFontSize.itemListBaseFontSize), textColor: theme.primaryColor, paragraphAlignment: .center) - - var text = presentationData.strings.OwnershipTransfer_SecurityRequirements - let textFontSize = presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0 + var title: String? = strings.OwnershipTransfer_SecurityCheck + var text = strings.OwnershipTransfer_SecurityRequirements var isGroup = true if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } - var actions: [TextAlertAction] = [] + var actions: [AlertScreen.Action] = [ + .init(title: strings.Common_OK, type: .default) + ] switch initialError { - case .requestPassword: - return confirmChannelOwnershipTransferController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, member: member, present: present, completion: completion) - case .twoStepAuthTooFresh, .authSessionTooFresh: - text = text + presentationData.strings.OwnershipTransfer_ComeBackLater - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] - case .twoStepAuthMissing: - actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.OwnershipTransfer_SetupTwoStepAuth, action: { + case .requestPassword: + return confirmChannelOwnershipTransferController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, member: member, present: present, push: push, completion: completion) + case .twoStepAuthTooFresh, .authSessionTooFresh: + text = text + strings.OwnershipTransfer_ComeBackLater + case .twoStepAuthMissing: + actions = [ + .init(title: strings.OwnershipTransfer_SetupTwoStepAuth, type: .default, action: { let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in if shouldDismiss { controller.dismiss() } }) present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})] - case .adminsTooMuch: - title = nil - text = isGroup ? presentationData.strings.Group_OwnershipTransfer_ErrorAdminsTooMuch : presentationData.strings.Channel_OwnershipTransfer_ErrorAdminsTooMuch - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] - case .userPublicChannelsTooMuch: - title = nil - text = presentationData.strings.Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] - case .userBlocked, .restricted: - title = nil - text = isGroup ? presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted : presentationData.strings.Channel_OwnershipTransfer_ErrorPrivacyRestricted - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] - default: - title = nil - text = presentationData.strings.Login_UnknownError - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] + }), + .init(title: strings.Common_Cancel) + ] + case .adminsTooMuch: + title = nil + text = isGroup ? strings.Group_OwnershipTransfer_ErrorAdminsTooMuch : strings.Channel_OwnershipTransfer_ErrorAdminsTooMuch + case .userPublicChannelsTooMuch: + title = nil + text = strings.Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch + case .userBlocked, .restricted: + title = nil + text = isGroup ? strings.Group_OwnershipTransfer_ErrorPrivacyRestricted : strings.Channel_OwnershipTransfer_ErrorPrivacyRestricted + default: + title = nil + text = strings.Login_UnknownError } - let body = MarkdownAttributeSet(font: Font.regular(textFontSize), textColor: theme.primaryColor) - let bold = MarkdownAttributeSet(font: Font.semibold(textFontSize), textColor: theme.primaryColor) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - return richTextAlertController(context: context, title: title, text: attributedText, actions: actions) + return AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + title: title, + text: text, + actions: actions + ) } diff --git a/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/Sources/OwnershipTransferController.swift b/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/Sources/OwnershipTransferController.swift index 4d5f6e9e..baa29fbf 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/Sources/OwnershipTransferController.swift +++ b/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/Sources/OwnershipTransferController.swift @@ -8,113 +8,162 @@ import TelegramPresentationData import ActivityIndicator import TextFormat import AccountContext -import AlertUI import PresentationDataUtils import PasswordSetupUI -import Markdown +import ComponentFlow +import AlertComponent +import AlertInputFieldComponent -private func commitOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, present: @escaping (ViewController, Any?) -> Void, commit: @escaping (String) -> Signal, completion: @escaping (MessageActionCallbackResult) -> Void) -> ViewController { - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } +private func commitOwnershipTransferController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + present: @escaping (ViewController, Any?) -> Void, + commit: @escaping (String) -> Signal, + completion: @escaping (MessageActionCallbackResult) -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + let inputState = AlertInputFieldComponent.ExternalState() + + let doneIsEnabled: Signal = inputState.valueSignal + |> map { value in + return !value.isEmpty + } + + let doneInProgressPromise = ValuePromise(false) + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.OwnershipTransfer_EnterPassword) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.OwnershipTransfer_EnterPasswordText)) + ) + )) + + var applyImpl: (() -> Void)? + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder, + isSecureTextEntry: true, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) + + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) + } var dismissImpl: (() -> Void)? - var proceedImpl: (() -> Void)? - - let disposable = MetaDisposable() - - let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.OwnershipTransfer_EnterPassword, text: presentationData.strings.OwnershipTransfer_EnterPasswordText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?() - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.OwnershipTransfer_Transfer, action: { - proceedImpl?() - })]) - - contentNode.complete = { - proceedImpl?() - } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.theme = presentationData.theme - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - disposable.dispose() - } - dismissImpl = { [weak controller, weak contentNode] in - contentNode?.dismissInput() - controller?.dismissAnimated() - } - proceedImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - contentNode.updateIsChecking(true) - - disposable.set((commit(contentNode.password) |> deliverOnMainQueue).start(next: { result in - completion(result) + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.OwnershipTransfer_Transfer, type: .default, action: { + applyImpl?() + }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get()) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + applyImpl = { + doneInProgressPromise.set(true) + + let _ = (commit(inputState.value) + |> deliverOnMainQueue).start(next: { result in dismissImpl?() - }, error: { [weak contentNode] error in + completion(result) + }, error: { error in var errorTextAndActions: (String, [TextAlertAction])? switch error { - case .invalidPassword: - contentNode?.animateError() - case .limitExceeded: - errorTextAndActions = (presentationData.strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - case .userBlocked, .restricted: - errorTextAndActions = (presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - default: - errorTextAndActions = (presentationData.strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + case .invalidPassword: + inputState.animateError() + case .limitExceeded: + errorTextAndActions = (strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) + case .userBlocked, .restricted: + errorTextAndActions = (presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + default: + errorTextAndActions = (strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) } - contentNode?.updateIsChecking(false) - + doneInProgressPromise.set(false) + if let (text, actions) = errorTextAndActions { dismissImpl?() present(textAlertController(context: context, title: nil, text: text, actions: actions), nil) } - })) + }) } - - return controller + dismissImpl = { [weak alertController] in + alertController?.dismiss(completion: nil) + } + return alertController } -public func ownershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, initialError: MessageActionCallbackError, present: @escaping (ViewController, Any?) -> Void, commit: @escaping (String) -> Signal, completion: @escaping (MessageActionCallbackResult) -> Void) -> ViewController { +public func ownershipTransferController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + initialError: MessageActionCallbackError, + present: @escaping (ViewController, Any?) -> Void, + commit: @escaping (String) -> Signal, + completion: @escaping (MessageActionCallbackResult) -> Void +) -> ViewController { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - let theme = AlertControllerTheme(presentationData: presentationData) + let strings = presentationData.strings - var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.OwnershipTransfer_SecurityCheck, font: Font.semibold(presentationData.listsFontSize.itemListBaseFontSize), textColor: theme.primaryColor, paragraphAlignment: .center) + var title: String? = strings.OwnershipTransfer_SecurityCheck + var text = strings.OwnershipTransfer_SecurityRequirements - var text = presentationData.strings.OwnershipTransfer_SecurityRequirements - var actions: [TextAlertAction] = [] - let textFontSize = presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0 + var actions: [AlertScreen.Action] = [ + .init(title: strings.Common_OK, type: .default) + ] switch initialError { case .requestPassword: return commitOwnershipTransferController(context: context, updatedPresentationData: updatedPresentationData, present: present, commit: commit, completion: completion) case .twoStepAuthTooFresh, .authSessionTooFresh: text = text + presentationData.strings.OwnershipTransfer_ComeBackLater - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] case .twoStepAuthMissing: - actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.OwnershipTransfer_SetupTwoStepAuth, action: { - let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in - if shouldDismiss { - controller.dismiss() - } - }) - present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})] + actions = [ + .init(title: strings.OwnershipTransfer_SetupTwoStepAuth, type: .default, action: { + let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in + if shouldDismiss { + controller.dismiss() + } + }) + present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }), + .init(title: strings.Common_Cancel) + ] case .userBlocked, .restricted: title = nil text = presentationData.strings.Group_OwnershipTransfer_ErrorPrivacyRestricted - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] default: title = nil text = presentationData.strings.Login_UnknownError - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] } - let body = MarkdownAttributeSet(font: Font.regular(textFontSize), textColor: theme.primaryColor) - let bold = MarkdownAttributeSet(font: Font.semibold(textFontSize), textColor: theme.primaryColor) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - return richTextAlertController(context: context, title: title, text: attributedText, actions: actions) + return AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + title: title, + text: text, + actions: actions + ) } diff --git a/submodules/TelegramUI/Components/PeerSelectionController/BUILD b/submodules/TelegramUI/Components/PeerSelectionController/BUILD index 230b9636..f3d3fd0e 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/BUILD +++ b/submodules/TelegramUI/Components/PeerSelectionController/BUILD @@ -21,7 +21,7 @@ swift_library( "//submodules/ChatListUI", "//submodules/SearchBarNode", "//submodules/ContactListUI", - "//submodules/SegmentedControlNode", + "//submodules/TelegramUI/Components/SegmentControlComponent", "//submodules/AttachmentTextInputPanelNode", "//submodules/ChatPresentationInterfaceState", "//submodules/ChatSendMessageActionUI", @@ -35,6 +35,10 @@ swift_library( "//submodules/TextFormat", "//submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode", "//submodules/CounterControllerTitleView", + "//submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift index db095daa..d5361bc5 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift @@ -10,6 +10,7 @@ import AccountContext import SearchUI import ChatListUI import CounterControllerTitleView +import ChatListFilterTabContainerNode public final class PeerSelectionControllerImpl: ViewController, PeerSelectionController { private let context: AccountContext @@ -112,7 +113,9 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.immediatelyActivateMultipleSelection = params.immediatelyActivateMultipleSelection self.multipleSelectionLimit = params.multipleSelectionLimit - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + + self._hasGlassStyle = true self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -147,7 +150,6 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon if params.forumPeerId == nil { self.navigationPresentation = .modal - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) } self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) @@ -206,7 +208,9 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon if force { strongSelf.tabContainerNode?.cancelAnimations() } - strongSelf.tabContainerNode?.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: false, isEditing: false, canReorderAllChats: false, filtersLimit: tabContainerData.2, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition) + if let tabContainerNode = strongSelf.tabContainerNode { + tabContainerNode.update(size: CGSize(width: layout.size.width, height: 44.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: false, isEditing: false, canReorderAllChats: false, filtersLimit: tabContainerData.2, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition) + } } self.tabContainerNode?.tabSelected = { [weak self] id, isDisabled in @@ -244,7 +248,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) self.title = self.customTitle ?? self.presentationData.strings.Conversation_ForwardTitle self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) @@ -252,6 +256,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon } override public func loadDisplayNode() { + self.navigationBar?.secondaryContentHeight = 44.0 + 10.0 + self.displayNode = PeerSelectionControllerNode(context: self.context, controller: self, presentationData: self.presentationData, filter: self.filter, forumPeerId: self.forumPeerId, hasFilters: self.hasFilters, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessageIds: self.forwardedMessageIds, hasTypeHeaders: self.hasTypeHeaders, requestPeerType: self.requestPeerType, hasCreation: self.hasCreation, createNewGroup: self.createNewGroup, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }, presentInGlobalOverlay: { [weak self] c, a in @@ -419,8 +425,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon if let tabContainerNode = self.tabContainerNode, let mainContainerNode = self.peerSelectionNode.mainContainerNode { let tabContainerOffset: CGFloat = 0.0 let navigationBarHeight = self.navigationBar?.frame.maxY ?? 0.0 - transition.updateFrame(node: tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0))) - tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: mainContainerNode.currentItemFilter, isReordering: false, isEditing: false, canReorderAllChats: false, filtersLimit: self.tabContainerData?.2, transitionFraction: mainContainerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + transition.updateFrame(node: tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 44.0 - 8.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 44.0))) + tabContainerNode.update(size: CGSize(width: layout.size.width, height: 44.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: mainContainerNode.currentItemFilter, isReordering: false, isEditing: false, canReorderAllChats: false, filtersLimit: self.tabContainerData?.2, transitionFraction: mainContainerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) } } @@ -458,6 +464,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon } private var initializedFilters = false + private(set) var chatListFiltersNonEmpty: Bool = false + private func reloadFilters(firstUpdate: (() -> Void)? = nil) { let filterItems = chatListFilterItems(context: self.context) var notifiedFirstUpdate = false @@ -561,6 +569,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon let isEmpty = resolvedItems.count <= 1 + strongSelf.chatListFiltersNonEmpty = !isEmpty if wasEmpty != isEmpty, strongSelf.displayNavigationBar { strongSelf.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode, animated: false) } @@ -568,8 +577,8 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon if let layout = strongSelf.validLayout { if wasEmpty != isEmpty { strongSelf.containerLayoutUpdated(layout, transition: .immediate) - } else { - strongSelf.tabContainerNode?.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: false, isEditing: false, canReorderAllChats: false, filtersLimit: filtersLimit, transitionFraction: 0.0, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + } else if let tabContainerNode = strongSelf.tabContainerNode { + tabContainerNode.update(size: CGSize(width: layout.size.width, height: 44.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: false, isEditing: false, canReorderAllChats: false, filtersLimit: filtersLimit, transitionFraction: 0.0, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) } } diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index f34f345f..dbf4b785 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -11,7 +11,6 @@ import SearchBarNode import SearchUI import ContactListUI import ChatListUI -import SegmentedControlNode import AttachmentTextInputPanelNode import ChatPresentationInterfaceState import ChatSendMessageActionUI @@ -25,6 +24,10 @@ import ContextUI import TextFormat import ForwardAccessoryPanelNode import CounterControllerTitleView +import SegmentControlComponent +import ComponentFlow +import ComponentDisplayAdapters +import EdgeEffect final class PeerSelectionControllerNode: ASDisplayNode { private let context: AccountContext @@ -58,9 +61,10 @@ final class PeerSelectionControllerNode: ASDisplayNode { private let emptyTextNode: ImmediateTextNode private let emptyButtonNode: SolidRoundedButtonNode - private let toolbarBackgroundNode: NavigationBackgroundNode? - private let toolbarSeparatorNode: ASDisplayNode? - private let segmentedControlNode: SegmentedControlNode? + private let bottomEdgeEffectView: EdgeEffectView? + private let segmentedControl: ComponentView? + private var segmentedControlItems: [String]? + private var segmentedControlSelectedIndex: Int = 0 private var textInputPanelNode: AttachmentTextInputPanelNode? private var forwardAccessoryPanelNode: ForwardAccessoryPanelNode? @@ -127,7 +131,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.animationCache = context.animationCache self.animationRenderer = context.animationRenderer - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: PeerId(0)), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: PeerId(0)), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) self.presentationInterfaceState = self.presentationInterfaceState.updatedInterfaceState { $0.withUpdatedForwardMessageIds(forwardedMessageIds) } self.presentationInterfaceStatePromise.set(self.presentationInterfaceState) @@ -170,20 +174,16 @@ final class PeerSelectionControllerNode: ASDisplayNode { } if hasChatListSelector && hasContactSelector { - self.toolbarBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor) + self.bottomEdgeEffectView = EdgeEffectView() - self.toolbarSeparatorNode = ASDisplayNode() - self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor - - let items = [ + self.segmentedControl = ComponentView() + self.segmentedControlItems = [ self.presentationData.strings.DialogList_TabTitle, self.presentationData.strings.Contacts_TabTitle ] - self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: self.presentationData.theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: 0) } else { - self.toolbarBackgroundNode = nil - self.toolbarSeparatorNode = nil - self.segmentedControlNode = nil + self.bottomEdgeEffectView = nil + self.segmentedControl = nil } var chatListCategories: [ChatListNodeAdditionalCategory] = [] @@ -357,13 +357,9 @@ final class PeerSelectionControllerNode: ASDisplayNode { } if hasChatListSelector && hasContactSelector { - self.segmentedControlNode!.selectedIndexChanged = { [weak self] index in - self?.indexChanged(index) + if let bottomEdgeEffectView = self.bottomEdgeEffectView { + self.view.addSubview(bottomEdgeEffectView) } - - self.addSubnode(self.toolbarBackgroundNode!) - self.addSubnode(self.toolbarSeparatorNode!) - self.addSubnode(self.segmentedControlNode!) } if let requirementsBackgroundNode = self.requirementsBackgroundNode, let requirementsSeparatorNode = self.requirementsSeparatorNode, let requirementsTextNode = self.requirementsTextNode { @@ -700,7 +696,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } } - let controller = chatTextLinkEditController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: (presentationData, .never()), account: strongSelf.context.account, text: text?.string ?? "", link: link, apply: { [weak self] link in + let controller = chatTextLinkEditController(context: context, updatedPresentationData: (presentationData, .never()), text: text?.string ?? "", link: link, apply: { [weak self] link in if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange { if let link = link { strongSelf.updateChatPresentationInterfaceState(animated: true, { state in @@ -1016,9 +1012,6 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.updateChatPresentationInterfaceState({ $0.updatedTheme(self.presentationData.theme) }) self.requirementsBackgroundNode?.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.toolbarBackgroundNode?.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor - self.segmentedControlNode?.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme)) if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .immediate) @@ -1085,19 +1078,51 @@ final class PeerSelectionControllerNode: ASDisplayNode { toolbarHeight = countPanelHeight transition.updateFrame(node: countPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - countPanelHeight), size: CGSize(width: layout.size.width, height: countPanelHeight))) } - } else if let segmentedControlNode = self.segmentedControlNode, let toolbarBackgroundNode = self.toolbarBackgroundNode, let toolbarSeparatorNode = self.toolbarSeparatorNode { + } else if let segmentedControl = self.segmentedControl, let segmentedControlItems = self.segmentedControlItems, let bottomEdgeEffectView = self.bottomEdgeEffectView { if let textPanelHeight = textPanelHeight { toolbarHeight = textPanelHeight + accessoryHeight } else { toolbarHeight += 44.0 } - transition.updateFrame(node: toolbarBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: toolbarHeight))) - toolbarBackgroundNode.update(size: toolbarBackgroundNode.bounds.size, transition: transition) - transition.updateFrame(node: toolbarSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - let controlSize = segmentedControlNode.updateLayout(.sizeToFit(maximumWidth: layout.size.width, minimumWidth: 200.0, height: 32.0), transition: transition) + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight - 10.0), size: CGSize(width: layout.size.width, height: toolbarHeight + 10.0)) + transition.updateFrame(view: bottomEdgeEffectView, frame: edgeEffectFrame) + bottomEdgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, blur: true, alpha: 0.3, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(30.0, edgeEffectFrame.height), transition: ComponentTransition(transition)) + + let controlSize = segmentedControl.update( + transition: ComponentTransition(transition), + component: AnyComponent(SegmentControlComponent( + theme: self.presentationData.theme, + items: (0 ..< segmentedControlItems.count).map { index in + return SegmentControlComponent.Item( + id: AnyHashable(index), + title: segmentedControlItems[index] + ) + }, + selectedId: AnyHashable(self.segmentedControlSelectedIndex), + action: { [weak self] id in + guard let self, let index = id.base as? Int else { + return + } + self.segmentedControlSelectedIndex = max(0, min(segmentedControlItems.count - 1, index)) + if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) + } + + self.indexChanged(index) + } + )), + environment: {}, + containerSize: CGSize(width: 200.0, height: 32.0) + ) let controlOrigin = layout.size.height - (textPanelHeight == nil ? toolbarHeight : 0.0) + floor((44.0 - controlSize.height) / 2.0) - transition.updateFrame(node: segmentedControlNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: controlOrigin), size: controlSize)) + let segmentedControlFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: controlOrigin), size: controlSize) + if let segmentedControlView = segmentedControl.view { + if segmentedControlView.superview == nil { + self.view.addSubview(segmentedControlView) + } + transition.updateFrame(view: segmentedControlView, frame: segmentedControlFrame) + } } insets.top += navigationBarHeight @@ -1363,7 +1388,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } - } + }, fieldStyle: placeholderNode.fieldStyle ) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) @@ -1438,7 +1463,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in @@ -1459,7 +1484,9 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.chatListNode?.accessibilityElementsHidden = false self.mainContainerNode?.accessibilityElementsHidden = false - self.navigationBar?.setSecondaryContentNode(self.controller?.tabContainerNode, animated: true) + if let controller = self.controller, controller.chatListFiltersNonEmpty { + self.navigationBar?.setSecondaryContentNode(self.controller?.tabContainerNode, animated: true) + } self.controller?.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring)) searchDisplayController.deactivate(placeholder: placeholderNode) @@ -1587,7 +1614,9 @@ final class PeerSelectionControllerNode: ASDisplayNode { } } } else if let contactListNode = self.contactListNode { - self.navigationBar?.setSecondaryContentNode(self.controller?.tabContainerNode, animated: false) + if let controller = self.controller, controller.chatListFiltersNonEmpty { + self.navigationBar?.setSecondaryContentNode(self.controller?.tabContainerNode, animated: false) + } contactListNode.enableUpdates = false if let mainContainerNode = self.mainContainerNode { diff --git a/submodules/TelegramUI/Components/Premium/PremiumDiamondComponent/Sources/PremiumDiamondComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumDiamondComponent/Sources/PremiumDiamondComponent.swift index ffa39db9..06ac685c 100644 --- a/submodules/TelegramUI/Components/Premium/PremiumDiamondComponent/Sources/PremiumDiamondComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumDiamondComponent/Sources/PremiumDiamondComponent.swift @@ -251,7 +251,7 @@ public final class PremiumDiamondComponent: Component { func update(component: PremiumDiamondComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { self.component = component - self.sceneView.backgroundColor = component.theme.list.blocksBackgroundColor + //self.sceneView.backgroundColor = component.theme.list.blocksBackgroundColor self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0)) self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0) diff --git a/submodules/TelegramUI/Components/PremiumAlertController/BUILD b/submodules/TelegramUI/Components/PremiumAlertController/BUILD new file mode 100644 index 00000000..25d0e52f --- /dev/null +++ b/submodules/TelegramUI/Components/PremiumAlertController/BUILD @@ -0,0 +1,27 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "PremiumAlertController", + module_name = "PremiumAlertController", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/PremiumAlertController/Sources/PremiumAlertController.swift b/submodules/TelegramUI/Components/PremiumAlertController/Sources/PremiumAlertController.swift new file mode 100644 index 00000000..001ca763 --- /dev/null +++ b/submodules/TelegramUI/Components/PremiumAlertController/Sources/PremiumAlertController.swift @@ -0,0 +1,124 @@ +import Foundation +import UIKit +import SwiftSignalKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import AccountContext +import ComponentFlow +import AlertComponent +import PremiumStarComponent + +public func premiumAlertController( + context: AccountContext, + parentController: ViewController, + title: String? = nil, + text: String +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertPremiumStarComponent() + ) + )) + + let title = strings.PremiumNeeded_Title + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + + let alertController = AlertScreen( + context: context, + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.PremiumNeeded_Subscribe, type: .default, action: { [weak parentController] in + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .nameColor, forceDark: false, dismissed: nil) + parentController?.push(controller) + }) + ] + ) + return alertController +} + +private final class AlertPremiumStarComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + public init() { + } + + public static func ==(lhs: AlertPremiumStarComponent, rhs: AlertPremiumStarComponent) -> Bool { + return true + } + + public final class View: UIView { + private let clippingView = UIView() + private let icon = ComponentView() + + private var component: AlertPremiumStarComponent? + private weak var state: EmptyComponentState? + + func update(component: AlertPremiumStarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let starHeight: CGFloat = 105.0 + let starSize = self.icon.update( + transition: .immediate, + component: AnyComponent( + PremiumStarComponent( + theme: environment.theme, + isIntro: false, + isVisible: true, + hasIdleAnimations: true, + colors: [ + UIColor(rgb: 0x6a94ff), + UIColor(rgb: 0x9472fd), + UIColor(rgb: 0xe26bd3) + ] + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width + 60.0, height: 200.0) + ) + if let view = self.icon.view { + if view.superview == nil { + self.addSubview(self.clippingView) + self.clippingView.addSubview(view) + } + view.frame = CGRect(origin: CGPoint(x: 0.0, y: -24.0), size: starSize) + } + + self.clippingView.clipsToBounds = true + self.clippingView.layer.cornerRadius = 35.0 + self.clippingView.frame = CGRect(origin: CGPoint(x: -30.0, y: -22.0), size: CGSize(width: starSize.width, height: starSize.height)) + + return CGSize(width: availableSize.width, height: starHeight + 10.0) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift b/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift index 66566d8e..2ff9c802 100644 --- a/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift @@ -32,6 +32,7 @@ public final class SearchInputPanelComponent: Component { public let safeInsets: UIEdgeInsets public let placeholder: String? public let resetText: ResetText? + public let hasEdgeEffect: Bool public let updated: ((String) -> Void) public let cancel: () -> Void @@ -42,6 +43,7 @@ public final class SearchInputPanelComponent: Component { safeInsets: UIEdgeInsets, placeholder: String? = nil, resetText: ResetText? = nil, + hasEdgeEffect: Bool = true, updated: @escaping ((String) -> Void), cancel: @escaping () -> Void ) { @@ -51,6 +53,7 @@ public final class SearchInputPanelComponent: Component { self.safeInsets = safeInsets self.placeholder = placeholder self.resetText = resetText + self.hasEdgeEffect = hasEdgeEffect self.updated = updated self.cancel = cancel } @@ -292,7 +295,7 @@ public final class SearchInputPanelComponent: Component { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: component.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: component.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -321,6 +324,7 @@ public final class SearchInputPanelComponent: Component { let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight + 30.0), size: CGSize(width: size.width, height: edgeEffectHeight)) transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) self.edgeEffectView.update(content: edgeColor, blur: true, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition) + self.edgeEffectView.isHidden = !component.hasEdgeEffect transition.setFrame(view: self.containerView, frame: CGRect(origin: .zero, size: size)) self.containerView.update(size: size, isDark: component.theme.overallDarkAppearance, transition: transition) diff --git a/submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen/Sources/AccountFreezeInfoScreen.swift b/submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen/Sources/AccountFreezeInfoScreen.swift index 480ec2ea..8c974a43 100644 --- a/submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen/Sources/AccountFreezeInfoScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen/Sources/AccountFreezeInfoScreen.swift @@ -49,7 +49,6 @@ private final class SheetContent: CombinedComponent { final class State: ComponentState { var cachedChevronImage: (UIImage, PresentationTheme)? - var cachedCloseImage: (UIImage, PresentationTheme)? let playOnce = ActionSlot() private var didPlayAnimation = false @@ -189,14 +188,15 @@ private final class SheetContent: CombinedComponent { contentSize.height += list.size.height contentSize.height += spacing + 2.0 + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let buttonAttributedString = NSMutableAttributedString(string: strings.FrozenAccount_SubmitAppeal, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) let actionButton = actionButton.update( component: ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: environment.theme.list.itemCheckColors.fillColor, foreground: environment.theme.list.itemCheckColors.foregroundColor, pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 ), content: AnyComponentWithIdentity( id: AnyHashable(0), @@ -209,7 +209,7 @@ private final class SheetContent: CombinedComponent { component.dismiss() } ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), transition: context.transition ) context.add(actionButton @@ -223,10 +223,10 @@ private final class SheetContent: CombinedComponent { let closeButton = closeButton.update( component: ButtonComponent( background: ButtonComponent.Background( - color: .clear, - foreground: .clear, - pressedColor: .clear, - cornerRadius: 10.0 + style: .glass, + color: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.1), + foreground: theme.list.itemCheckColors.fillColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) ), content: AnyComponentWithIdentity( id: AnyHashable(1), @@ -238,19 +238,14 @@ private final class SheetContent: CombinedComponent { component.dismiss() } ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), transition: context.transition ) context.add(closeButton .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0)) ) contentSize.height += closeButton.size.height - - if environment.safeInsets.bottom > 0 { - contentSize.height += environment.safeInsets.bottom + 5.0 - } else { - contentSize.height += 12.0 - } + contentSize.height += buttonInsets.bottom state.playAnimationIfNeeded() @@ -312,6 +307,7 @@ private final class SheetContainerComponent: CombinedComponent { }) } )), + style: .glass, backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), followContentSizeChanges: true, externalState: sheetExternalState, @@ -507,8 +503,8 @@ private final class ParagraphComponent: CombinedComponent { textColor: component.titleColor, paragraphAlignment: .natural )), - horizontalAlignment: .center, - maximumNumberOfLines: 1 + horizontalAlignment: .natural, + maximumNumberOfLines: 2 ), availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), transition: .immediate @@ -576,24 +572,3 @@ private final class ParagraphComponent: CombinedComponent { } } } - -private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? { - return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(backgroundColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - - context.setLineWidth(2.0) - context.setLineCap(.round) - context.setStrokeColor(foregroundColor.cgColor) - - context.move(to: CGPoint(x: 10.0, y: 10.0)) - context.addLine(to: CGPoint(x: 20.0, y: 20.0)) - context.strokePath() - - context.move(to: CGPoint(x: 20.0, y: 10.0)) - context.addLine(to: CGPoint(x: 10.0, y: 20.0)) - context.strokePath() - }) -} diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift index 74bcf76e..6e604801 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift @@ -205,8 +205,6 @@ final class GreetingMessageListItemComponent: Component { }, openStarsTopup: { _ in }, - dismissNotice: { _ in - }, editPeer: { _ in }, openWebApp: { _ in diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift index 9a4d2879..5a30131d 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift @@ -195,7 +195,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { if self.isOn { if !self.hasAccessToAllChatsByDefault && self.additionalPeerList.categories.isEmpty && self.additionalPeerList.peers.isEmpty { - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.BusinessMessageSetup_ErrorNoRecipients_Text, actions: [ + self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: presentationData.strings.BusinessMessageSetup_ErrorNoRecipients_Text, actions: [ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: presentationData.strings.BusinessMessageSetup_ErrorNoRecipients_ResetAction, action: { @@ -223,7 +223,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { } if let errorText { - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [ + self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: errorText, actions: [ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: presentationData.strings.BusinessMessageSetup_ErrorScheduleTime_ResetAction, action: { diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift index 6eef6691..d7d1e466 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift @@ -4,6 +4,7 @@ import Display import ComponentFlow import ListSectionComponent import TelegramPresentationData +import PresentationDataUtils import AppBundle import AccountContext import ViewControllerComponent @@ -185,7 +186,7 @@ final class BusinessLinksSetupScreenComponent: Component { errorText = presentationData.strings.Business_Links_ErrorTooManyLinks } - environment.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [ + environment.controller()?.present(textAlertController(context: component.context, title: nil, text: errorText, actions: [ TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { }) ]), in: .window(.root)) diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 67c06654..6e85dd1e 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -219,8 +219,6 @@ final class QuickReplySetupScreenComponent: Component { }, openStarsTopup: { _ in }, - dismissNotice: { _ in - }, editPeer: { [weak listNode] _ in guard let listNode, let parentView = listNode.parentView else { return @@ -798,8 +796,7 @@ final class QuickReplySetupScreenComponent: Component { } ))) : nil, rightButtons: rightButtons, - backTitle: isModal ? nil : strings.Common_Back, - backPressed: { [weak self] in + backPressed: isModal ? nil :{ [weak self] in guard let self else { return } @@ -818,14 +815,15 @@ final class QuickReplySetupScreenComponent: Component { strings: strings, statusBarHeight: statusBarHeight, sideInset: insets.left, - isSearchActive: self.isSearchDisplayControllerActive, - isSearchEnabled: !self.isEditing, + search: ChatListNavigationBar.Search(isEnabled: !self.isEditing), + activeSearch: self.isSearchDisplayControllerActive ? ChatListNavigationBar.ActiveSearch(isExternal: false) : nil, primaryContent: headerContent, secondaryContent: nil, secondaryTransition: 0.0, storySubscriptions: nil, storiesIncludeHidden: false, uploadProgress: [:], + headerPanels: nil, tabsNode: nil, tabsNodeIsSearch: false, accessoryPanelContainer: nil, @@ -1022,8 +1020,9 @@ final class QuickReplySetupScreenComponent: Component { let searchBarTheme = SearchBarNodeTheme(theme: environment.theme, hasSeparator: false) searchBarNode = SearchBarNode( theme: searchBarTheme, + presentationTheme: environment.theme, strings: environment.strings, - fieldStyle: .modern, + fieldStyle: .glass, displayBackground: false ) searchBarNode.placeholderString = NSAttributedString(string: environment.strings.Common_Search, font: Font.regular(17.0), textColor: searchBarTheme.placeholder) diff --git a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerScreen.swift b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerScreen.swift index a1972adc..7c92a69b 100644 --- a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerScreen.swift @@ -127,7 +127,7 @@ private final class BirthdayPickerSheetContentComponent: Component { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in diff --git a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift index a5dee4fe..6e90d95f 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift @@ -127,8 +127,7 @@ final class BusinessDaySetupScreenComponent: Component { } } - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: enviroment.strings.BusinessHoursSetup_ErrorIntersectingHours_Text, actions: [ + self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: enviroment.strings.BusinessHoursSetup_ErrorIntersectingHours_Text, actions: [ TextAlertAction(type: .genericAction, title: enviroment.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: enviroment.strings.BusinessHoursSetup_ErrorIntersectingHours_ResetAction, action: { diff --git a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift index 5b453f54..71e325ae 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift @@ -307,8 +307,7 @@ final class BusinessHoursSetupScreenComponent: Component { let _ = component.context.engine.accountData.updateAccountBusinessHours(businessHours: businessHours).startStandalone() return true } catch _ { - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: environment.strings.BusinessHoursSetup_ErrorIntersectingDays_Text, actions: [ + self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: environment.strings.BusinessHoursSetup_ErrorIntersectingDays_Text, actions: [ TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: environment.strings.BusinessHoursSetup_ErrorIntersectingDays_ResetAction, action: { [weak self] in diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift index 1bd282be..996ed9f9 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift @@ -126,7 +126,6 @@ final class ChatIntroItemComponent: Component { pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, diff --git a/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD b/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD index 174ee14b..dd67a3b4 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD +++ b/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD @@ -20,6 +20,8 @@ swift_library( "//submodules/ComponentFlow", "//submodules/Components/MultilineTextComponent", "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/Sources/BusinessLinkNameAlertController.swift b/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/Sources/BusinessLinkNameAlertController.swift index b5bd40fb..8a531ac3 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/Sources/BusinessLinkNameAlertController.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/Sources/BusinessLinkNameAlertController.swift @@ -10,533 +10,73 @@ import AccountContext import ComponentFlow import MultilineTextComponent import BalancedTextComponent +import AlertComponent +import AlertInputFieldComponent -private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { - private var theme: PresentationTheme - private let backgroundNode: ASImageNode - private let textInputNode: EditableTextNode - private let placeholderNode: ASTextNode - private let characterLimitView = ComponentView() - - private let characterLimit: Int - - var updateHeight: (() -> Void)? - var complete: (() -> Void)? - var textChanged: ((String) -> Void)? - - private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0) - private let inputInsets: UIEdgeInsets - - private let validCharacterSets: [CharacterSet] - - var text: String { - get { - return self.textInputNode.attributedText?.string ?? "" - } - set { - self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputTextColor) - self.placeholderNode.isHidden = !newValue.isEmpty - } - } - - var placeholder: String = "" { - didSet { - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - } - } - - init(theme: PresentationTheme, placeholder: String, characterLimit: Int) { - self.theme = theme - self.characterLimit = characterLimit - - self.inputInsets = UIEdgeInsets(top: 9.0, left: 6.0, bottom: 9.0, right: 16.0) - - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - - self.textInputNode = EditableTextNode() - self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(13.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor] - self.textInputNode.clipsToBounds = true - self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) - self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: self.inputInsets.left, bottom: self.inputInsets.bottom, right: self.inputInsets.right) - self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance - self.textInputNode.keyboardType = .default - self.textInputNode.autocapitalizationType = .none - self.textInputNode.returnKeyType = .done - self.textInputNode.autocorrectionType = .no - self.textInputNode.tintColor = theme.actionSheet.controlAccentColor - - self.placeholderNode = ASTextNode() - self.placeholderNode.isUserInteractionEnabled = false - self.placeholderNode.displaysAsynchronously = false - self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - - self.validCharacterSets = [ - CharacterSet.alphanumerics, - CharacterSet(charactersIn: "0123456789_ "), - ] - - super.init() - - self.textInputNode.delegate = self - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.textInputNode) - self.addSubnode(self.placeholderNode) - } - - func updateTheme(_ theme: PresentationTheme) { - self.theme = theme - - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor - } - - func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let textFieldHeight = self.calculateTextFieldMetrics(width: width) - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - - let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom)) - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - - let placeholderSize = self.placeholderNode.measure(backgroundFrame.size) - transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left + 5.0, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) - - transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height))) - - let characterLimitString: String - let characterLimitColor: UIColor - if self.text.count <= self.characterLimit { - let remaining = self.characterLimit - self.text.count - if remaining < 5 { - characterLimitString = "\(remaining)" - } else { - characterLimitString = " " - } - characterLimitColor = self.theme.list.itemPlaceholderTextColor - } else { - characterLimitString = "\(self.characterLimit - self.text.count)" - characterLimitColor = self.theme.list.itemDestructiveColor - } - - let characterLimitSize = self.characterLimitView.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: characterLimitString, font: Font.regular(13.0), textColor: characterLimitColor)) - )), - environment: {}, - containerSize: CGSize(width: 100.0, height: 100.0) +public func businessLinkNameAlertController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + value: String?, + apply: @escaping (String?) -> Void +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + let inputState = AlertInputFieldComponent.ExternalState() + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Business_Links_LinkNameTitle) ) - if let characterLimitComponentView = self.characterLimitView.view { - if characterLimitComponentView.superview == nil { - self.view.addSubview(characterLimitComponentView) - } - characterLimitComponentView.frame = CGRect(origin: CGPoint(x: width - 23.0 - characterLimitSize.width, y: 18.0), size: characterLimitSize) - } - - return panelHeight - } - - func activateInput() { - self.textInputNode.becomeFirstResponder() - } - - func deactivateInput() { - self.textInputNode.resignFirstResponder() - } - - @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { - self.updateTextNodeText(animated: true) - self.textChanged?(editableTextNode.textView.text) - self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty - } - - func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - if text == "\n" { - self.complete?() - return false - } - if text.unicodeScalars.contains(where: { c in - return !self.validCharacterSets.contains(where: { set in - return set.contains(c) - }) - }) { - return false - } - return true - } - - private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let unboundTextFieldHeight = max(34.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height)) - - return min(61.0, max(34.0, unboundTextFieldHeight)) - } - - private func updateTextNodeText(animated: Bool) { - let backgroundInsets = self.backgroundInsets - - let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width) - - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - if !self.bounds.size.height.isEqual(to: panelHeight) { - self.updateHeight?() - } - } - - @objc func clearPressed() { - self.textInputNode.attributedText = nil - self.deactivateInput() - } -} - -public final class BusinessLinkNameAlertContentNode: AlertContentNode { - private let context: AccountContext - private var theme: AlertControllerTheme - private let strings: PresentationStrings - private let text: String - private let subtext: String - private let titleFont: PromptControllerTitleFont - - private let textView = ComponentView() - private let subtextView = ComponentView() - - fileprivate let inputFieldNode: PromptInputFieldNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let disposable = MetaDisposable() - - private var validLayout: CGSize? - private var errorText: String? - - private let hapticFeedback = HapticFeedback() - - var complete: (() -> Void)? - - override public var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, subtext: String, titleFont: PromptControllerTitleFont, value: String?, characterLimit: Int) { - self.context = context - self.theme = theme - self.strings = strings - self.text = text - self.subtext = subtext - self.titleFont = titleFont - - self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: strings.Business_Links_LinkNameInputPlaceholder, characterLimit: characterLimit) - self.inputFieldNode.text = value ?? "" - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.inputFieldNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - self.actionNodes.last?.actionEnabled = true - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.inputFieldNode.updateHeight = { [weak self] in - if let strongSelf = self { - if let _ = strongSelf.validLayout { - strongSelf.requestLayout?(.immediate) - } - } - } - - self.inputFieldNode.textChanged = { [weak self] text in - if let strongSelf = self, let lastNode = strongSelf.actionNodes.last { - lastNode.actionEnabled = text.count <= characterLimit - strongSelf.requestLayout?(.immediate) - } - } - - self.updateTheme(theme) - - self.inputFieldNode.complete = { [weak self] in - guard let self else { - return - } - if let lastNode = self.actionNodes.last, lastNode.actionEnabled { - self.complete?() - } - } - } - - deinit { - self.disposable.dispose() - } - - var value: String { - return self.inputFieldNode.text - } - - public func setErrorText(errorText: String?) { - if self.errorText != errorText { - self.errorText = errorText - self.requestLayout?(.immediate) - } - - if errorText != nil { - HapticFeedback().error() - self.inputFieldNode.layer.addShakeAnimation() - } - } - - override public func updateTheme(_ theme: AlertControllerTheme) { - self.theme = theme - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) - - let hadValidLayout = self.validLayout != nil - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 16.0) - let spacing: CGFloat = 5.0 - let subtextSpacing: CGFloat = -1.0 - - let textSize = self.textView.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: self.text, font: Font.semibold(17.0), textColor: self.theme.primaryColor)), - horizontalAlignment: .center, - maximumNumberOfLines: 0 - )), - environment: {}, - containerSize: CGSize(width: measureSize.width, height: 1000.0) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.Business_Links_LinkNameText)) ) - let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) * 0.5), y: origin.y), size: textSize) - if let textComponentView = self.textView.view { - if textComponentView.superview == nil { - textComponentView.layer.anchorPoint = CGPoint() - self.view.addSubview(textComponentView) - } - textComponentView.bounds = CGRect(origin: CGPoint(), size: textFrame.size) - transition.updatePosition(layer: textComponentView.layer, position: textFrame.origin) - } - origin.y += textSize.height + 6.0 + subtextSpacing - - let subtextSize = self.subtextView.update( - transition: .immediate, - component: AnyComponent(BalancedTextComponent( - text: .plain(NSAttributedString(string: self.errorText ?? self.subtext, font: Font.regular(13.0), textColor: self.errorText != nil ? self.theme.destructiveColor : self.theme.primaryColor)), - horizontalAlignment: .center, - maximumNumberOfLines: 0 - )), - environment: {}, - containerSize: CGSize(width: measureSize.width, height: 1000.0) - ) - let subtextFrame = CGRect(origin: CGPoint(x: floor((size.width - subtextSize.width) * 0.5), y: origin.y), size: subtextSize) - if let subtextComponentView = self.subtextView.view { - if subtextComponentView.superview == nil { - subtextComponentView.layer.anchorPoint = CGPoint() - self.view.addSubview(subtextComponentView) - } - subtextComponentView.bounds = CGRect(origin: CGPoint(), size: subtextFrame.size) - transition.updatePosition(layer: subtextComponentView.layer, position: subtextFrame.origin) - } - origin.y += subtextSize.height + 6.0 + spacing - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) - - var contentWidth = max(textSize.width, minActionsWidth) - contentWidth = max(subtextSize.width, minActionsWidth) - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - - let inputFieldWidth = resultWidth - let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) - let inputHeight = inputFieldHeight - let inputFieldFrame = CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight) - transition.updateFrame(node: self.inputFieldNode, frame: inputFieldFrame) - transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0) - - let resultSize = CGSize(width: resultWidth, height: textSize.height + subtextSpacing + subtextSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if !hadValidLayout { - self.inputFieldNode.activateInput() - } - - return resultSize - } - - func animateError() { - self.inputFieldNode.layer.addShakeAnimation() - self.hapticFeedback.error() - } -} + )) -public enum PromptControllerTitleFont { - case regular - case bold -} - -public func businessLinkNameAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, text: String, subtext: String, titleFont: PromptControllerTitleFont = .regular, value: String?, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController { - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - - var dismissImpl: ((Bool) -> Void)? var applyImpl: (() -> Void)? - - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - apply(nil) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: { - applyImpl?() - })] - - let contentNode = BusinessLinkNameAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, subtext: subtext, titleFont: titleFont, value: value, characterLimit: characterLimit) - contentNode.complete = { - applyImpl?() + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + initialValue: value, + placeholder: strings.Business_Links_LinkNameInputPlaceholder, + characterLimit: 32, + hasClearButton: false, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) + + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) } - applyImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - apply(contentNode.value) + + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.Common_Done, type: .default, action: { + applyImpl?() + }) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + applyImpl = { + apply(inputState.value) } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.inputFieldNode.updateTheme(presentationData.theme) - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - } - dismissImpl = { [weak controller] animated in - contentNode.inputFieldNode.deactivateInput() - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + return alertController } diff --git a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift index f1836fed..6f0654f8 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift @@ -127,8 +127,7 @@ final class BusinessLocationSetupScreenComponent: Component { let businessLocation = self.currentBusinessLocation() if businessLocation != component.initialValue { - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: environment.strings.BusinessLocationSetup_AlertUnsavedChanges_Text, actions: [ + self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: environment.strings.BusinessLocationSetup_AlertUnsavedChanges_Text, actions: [ TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: { }), TextAlertAction(type: .destructiveAction, title: environment.strings.BusinessLocationSetup_AlertUnsavedChanges_ResetAction, action: { @@ -250,8 +249,7 @@ final class BusinessLocationSetupScreenComponent: Component { let businessLocation = self.currentBusinessLocation() if businessLocation != nil && address.isEmpty { - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: environment.strings.BusinessLocationSetup_ErrorAddressEmpty_Text, actions: [ + self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: environment.strings.BusinessLocationSetup_ErrorAddressEmpty_Text, actions: [ TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: { }) ]), in: .window(.root)) diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD index c83d5538..210ac682 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD @@ -34,6 +34,7 @@ swift_library( "//submodules/AvatarNode", "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramUI/Components/Stories/PeerListItemComponent", + "//submodules/TelegramUI/Components/AlertComponent", "//submodules/ShimmerEffect", ], visibility = [ diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift index c7aa5fd0..f6807785 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift @@ -23,6 +23,7 @@ import LottieComponent import Markdown import PeerListItemComponent import AvatarNode +import AlertComponent private let checkIcon: UIImage = { return generateImage(CGSize(width: 12.0, height: 10.0), rotatedContext: { size, context in @@ -527,14 +528,20 @@ final class ChatbotSetupScreenComponent: Component { } else { text = environment.strings.ChatbotSetup_Gift_Warning_CombinedText(botUsername).string } - let alertController = textAlertController(context: component.context, title: environment.strings.ChatbotSetup_Gift_Warning_Title, text: text, actions: [ - TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: { - completion(false) - }), - TextAlertAction(type: .defaultAction, title: environment.strings.ChatbotSetup_Gift_Warning_Proceed, action: { - completion(true) - }) - ], parseMarkdown: true) + + let alertController = AlertScreen( + context: component.context, + title: environment.strings.ChatbotSetup_Gift_Warning_Title, + text: text, + actions: [ + .init(title: environment.strings.Common_Cancel, action: { + completion(false) + }), + .init(title: environment.strings.ChatbotSetup_Gift_Warning_Proceed, type: .default, action: { + completion(true) + }), + ] + ) alertController.dismissed = { byOutsideTap in if byOutsideTap { completion(false) @@ -785,7 +792,7 @@ final class ChatbotSetupScreenComponent: Component { self.botRights = [.reply, .readMessages, .deleteSentMessages, .deleteReceivedMessages] self.state?.updated(transition: .spring(duration: 0.3)) } else { - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.ChatbotSetup_ErrorBotNotBusinessCapable, actions: [ + self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: presentationData.strings.ChatbotSetup_ErrorBotNotBusinessCapable, actions: [ TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { }) ]), in: .window(.root)) diff --git a/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/Sources/CollectibleItemInfoScreen.swift b/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/Sources/CollectibleItemInfoScreen.swift index 8ad146fd..4e3dd73d 100644 --- a/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/Sources/CollectibleItemInfoScreen.swift +++ b/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/Sources/CollectibleItemInfoScreen.swift @@ -522,7 +522,7 @@ private final class CollectibleItemInfoScreenContentComponent: Component { if environment.safeInsets.bottom.isZero { contentHeight += 16.0 } else { - contentHeight += environment.safeInsets.bottom + 14.0 + contentHeight += environment.safeInsets.bottom + 1.0 } return CGSize(width: availableSize.width, height: contentHeight) diff --git a/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreen.swift b/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreen.swift index 9d698608..c2f3c3ac 100644 --- a/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreen.swift +++ b/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreen.swift @@ -85,7 +85,7 @@ public class LanguageSelectionScreen: ViewController { private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData), transition: .immediate) self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) self.title = self.presentationData.strings.Settings_AppLanguage self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) diff --git a/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreenNode.swift b/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreenNode.swift index 5cf8f197..357421ef 100644 --- a/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreenNode.swift +++ b/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreenNode.swift @@ -540,7 +540,8 @@ final class LanguageSelectionScreenNode: ViewControllerTracingNode { inline: true, cancel: { [weak self] in self?.requestDeactivateSearch() - } + }, + fieldStyle: placeholderNode.fieldStyle ) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) diff --git a/submodules/TelegramUI/Components/Settings/PasskeysScreen/Sources/PasskeysScreen.swift b/submodules/TelegramUI/Components/Settings/PasskeysScreen/Sources/PasskeysScreen.swift index ba88ee22..02ee5142 100644 --- a/submodules/TelegramUI/Components/Settings/PasskeysScreen/Sources/PasskeysScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PasskeysScreen/Sources/PasskeysScreen.swift @@ -179,14 +179,21 @@ final class PasskeysScreenComponent: Component { guard let component = self.component, let environment = self.environment, let controller = environment.controller() else { return } - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: environment.strings.Passkeys_DeleteAlert_Title, text: environment.strings.Passkeys_DeleteAlert_Text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: { - }), TextAlertAction(type: .destructiveAction, title: environment.strings.Passkeys_DeleteAlert_Action, action: { [weak self] in - guard let self else { - return - } - self.deletePasskey(id: id) - })]), in: .window(.root)) + let alertController = textAlertController( + context: component.context, + title: environment.strings.Passkeys_DeleteAlert_Title, + text: environment.strings.Passkeys_DeleteAlert_Text, + actions: [ + TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), + TextAlertAction(type: .destructiveAction, title: environment.strings.Passkeys_DeleteAlert_Action, action: { [weak self] in + guard let self else { + return + } + self.deletePasskey(id: id) + }) + ] + ) + controller.present(alertController, in: .window(.root)) } private func deletePasskey(id: String) { diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift index c9306e02..6c3100f8 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift @@ -370,7 +370,7 @@ public final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { self.maskNode = ASImageNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.containerNode) } diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/BackButton.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/BackButton.swift index 271cae3d..ff04a480 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/BackButton.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/BackButton.swift @@ -149,7 +149,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { case .back: text = presentationData.strings.Common_Back accessibilityText = presentationData.strings.Common_Back - icon = NavigationBar.backArrowImage(color: .white) + icon = navigationBarBackArrowImage(color: .white) case .edit: text = presentationData.strings.Common_Edit accessibilityText = text diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index 22d93e09..25a60b74 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -39,6 +39,7 @@ import Markdown import GroupStickerPackSetupController import PeerNameColorItem import EmojiActionIconComponent +import EdgeEffect final class ChannelAppearanceScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -169,13 +170,13 @@ final class ChannelAppearanceScreenComponent: Component { } final class View: UIView, UIScrollViewDelegate { + private let edgeEffectView: EdgeEffectView private let topOverscrollLayer = SimpleLayer() private let scrollView: ScrollView private let actionButton = ComponentView() private let bottomPanelBackgroundView: BlurredBackgroundView private let bottomPanelSeparator: SimpleLayer - private let backButton = PeerInfoHeaderNavigationButton() private let navigationTitle = ComponentView() private let previewSection = ComponentView() @@ -227,6 +228,8 @@ final class ChannelAppearanceScreenComponent: Component { private weak var emojiStatusSelectionController: ViewController? override init(frame: CGRect) { + self.edgeEffectView = EdgeEffectView() + self.scrollView = ScrollView() self.scrollView.showsVerticalScrollIndicator = true self.scrollView.showsHorizontalScrollIndicator = false @@ -249,14 +252,10 @@ final class ChannelAppearanceScreenComponent: Component { self.scrollView.layer.addSublayer(self.topOverscrollLayer) + self.addSubview(self.edgeEffectView) + self.addSubview(self.bottomPanelBackgroundView) self.layer.addSublayer(self.bottomPanelSeparator) - - self.backButton.action = { [weak self] _, _ in - if let self, let controller = self.environment?.controller() { - controller.navigationController?.popViewController(animated: true) - } - } } required init?(coder: NSCoder) { @@ -291,20 +290,26 @@ final class ChannelAppearanceScreenComponent: Component { } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertTitle, text: presentationData.strings.Channel_Appearance_UnsavedChangesAlertText, actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertDiscard, action: { [weak self] in - guard let self else { - return - } - self.environment?.controller()?.dismiss() - }), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertApply, action: { [weak self] in - guard let self else { - return - } - self.applySettings() - }) - ]), in: .window(.root)) + let alertController = textAlertController( + context: component.context, + title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertTitle, + text: presentationData.strings.Channel_Appearance_UnsavedChangesAlertText, + actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertDiscard, action: { [weak self] in + guard let self else { + return + } + self.environment?.controller()?.dismiss() + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertApply, action: { [weak self] in + guard let self else { + return + } + self.applySettings() + }) + ] + ) + self.environment?.controller()?.present(alertController, in: .window(.root)) return false } @@ -343,6 +348,8 @@ final class ChannelAppearanceScreenComponent: Component { transition.setAlpha(view: navigationTitleView, alpha: navigationAlpha) } + transition.setAlpha(view: self.edgeEffectView, alpha: navigationAlpha) + let bottomNavigationAlphaDistance: CGFloat = 16.0 let bottomNavigationAlpha: CGFloat = max(0.0, min(1.0, (self.scrollView.contentSize.height - self.scrollView.bounds.maxY) / bottomNavigationAlphaDistance)) @@ -534,7 +541,15 @@ final class ChannelAppearanceScreenComponent: Component { } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + let alertController = textAlertController( + context: component.context, + title: nil, + text: presentationData.strings.Login_UnknownError, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) + ] + ) + self.environment?.controller()?.present(alertController, in: .window(.root)) self.isApplyingSettings = false self.state?.updated(transition: .immediate) @@ -992,6 +1007,10 @@ final class ChannelAppearanceScreenComponent: Component { } self.requiredBoostSubject = requiredBoostSubject + let edgeEffectHeight: CGFloat = environment.navigationHeight + 8.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, blur: true, rect: edgeEffectFrame, edge: .top, edgeSize: min(30, edgeEffectFrame.height), transition: transition) let headerColor: UIColor if let profileColor { @@ -1002,7 +1021,6 @@ final class ChannelAppearanceScreenComponent: Component { } self.topOverscrollLayer.backgroundColor = headerColor.cgColor - let backSize = self.backButton.update(key: .back, presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, height: 44.0) var scrolledUp = self.scrolledUp if profileColor == nil { scrolledUp = false @@ -1011,14 +1029,6 @@ final class ChannelAppearanceScreenComponent: Component { if let controller = self.environment?.controller() as? ChannelAppearanceScreen { controller.statusBar.updateStatusBarStyle(scrolledUp ? .White : .Ignore, animated: true) } - - self.backButton.updateContentsColor(backgroundColor: scrolledUp ? UIColor(white: 0.0, alpha: 0.1) : .clear, contentsColor: scrolledUp ? .white : environment.theme.rootController.navigationBar.accentTextColor, canBeExpanded: !scrolledUp, transition: .animated(duration: 0.2, curve: .easeInOut)) - self.backButton.frame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.navigationHeight - 44.0), size: backSize) - if self.backButton.view.superview == nil { - if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { - navigationBar.view.addSubview(self.backButton.view) - } - } let navigationTitleSize = self.navigationTitle.update( transition: transition, @@ -1897,12 +1907,11 @@ public class ChannelAppearanceScreen: ViewControllerComponentContainer { context: context, peerId: peerId, boostStatus: boostStatus - ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: updatedPresentationData) + ), navigationBarAppearance: .transparent, theme: .default, updatedPresentationData: updatedPresentationData) let presentationData = context.sharedContext.currentPresentationData.with { $0 } self.title = "" self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) self.ready.set(.never()) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift index e4f962bb..ac2ea87e 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift @@ -99,7 +99,7 @@ final class EmojiPickerItemNode: ListViewItemNode { self.maskNode = ASImageNode() self.maskNode.isUserInteractionEnabled = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.clipsToBounds = true } diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/GiftListItemComponent.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/GiftListItemComponent.swift index 3db8c9a3..4f3676d0 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/GiftListItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/GiftListItemComponent.swift @@ -218,7 +218,7 @@ final class GiftListItemComponent: Component { )) for gift in component.starGifts { - guard case let .generic(gift) = gift, let title = gift.title else { + guard case let .generic(gift) = gift, let title = gift.title, let resale = gift.availability?.resale, resale > 0 else { continue } tabSelectorItems.append(TabSelectorComponent.Item( diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift index f305e4f5..1c58240b 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift @@ -186,7 +186,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode { self.containerNode = ASDisplayNode() self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.clipsToBounds = true self.isUserInteractionEnabled = false diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift index 47518758..dd7f7564 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift @@ -142,7 +142,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode { let avatarFont = avatarPlaceholderFont(size: floor(100.0 * 16.0 / 37.0)) self.avatarNode = AvatarNode(font: avatarFont) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.clipsToBounds = true self.isUserInteractionEnabled = false diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift index f3918590..f1f1f24b 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift @@ -55,17 +55,17 @@ final class UserAppearanceScreenComponent: Component { } let context: AccountContext + let overNavigationContainer: UIView init( - context: AccountContext + context: AccountContext, + overNavigationContainer: UIView ) { self.context = context + self.overNavigationContainer = overNavigationContainer } static func ==(lhs: UserAppearanceScreenComponent, rhs: UserAppearanceScreenComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } return true } @@ -166,8 +166,6 @@ final class UserAppearanceScreenComponent: Component { private let actionButton = ComponentView() private let edgeEffectView: EdgeEffectView - private let backButton = PeerInfoHeaderNavigationButton() - private let tabSelector = ComponentView() enum Section: Int32 { case profile @@ -263,12 +261,6 @@ final class UserAppearanceScreenComponent: Component { self.containerView.addSubview(self.previewShadowView) self.addSubview(self.edgeEffectView) - - self.backButton.action = { [weak self] _, _ in - if let self, let controller = self.environment?.controller() as? UserAppearanceScreen { - controller.backPressed() - } - } } required init?(coder: NSCoder) { @@ -298,21 +290,28 @@ final class UserAppearanceScreenComponent: Component { if !resolvedState.changes.isEmpty { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertTitle, text: presentationData.strings.Channel_Appearance_UnsavedChangesAlertText, actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertDiscard, action: { [weak self] in - guard let self else { - return - } - self.environment?.controller()?.dismiss() - }), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertApply, action: { [weak self] in - guard let self else { - return - } - self.applySettings() - }) - ]), in: .window(.root)) + let alertController = textAlertController( + context: component.context, + title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertTitle, + text: presentationData.strings.Channel_Appearance_UnsavedChangesAlertText, + actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertDiscard, action: { [weak self] in + guard let self else { + return + } + self.environment?.controller()?.dismiss() + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertApply, action: { [weak self] in + guard let self else { + return + } + self.applySettings() + }) + ] + ) + self.environment?.controller()?.present(alertController, in: .window(.root)) + return false } @@ -564,7 +563,7 @@ final class UserAppearanceScreenComponent: Component { self.isApplyingSettings = false self.applySettings() - Queue.mainQueue().after(0.5) { + Queue.mainQueue().after(2.5) { switch finalPrice.currency { case .stars: component.context.starsContext?.load(force: true) @@ -800,7 +799,16 @@ final class UserAppearanceScreenComponent: Component { } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + + let alertController = textAlertController( + context: component.context, + title: nil, + text: presentationData.strings.Login_UnknownError, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) + ] + ) + self.environment?.controller()?.present(alertController, in: .window(.root)) self.isApplyingSettings = false self.state?.updated(transition: .immediate) @@ -963,6 +971,8 @@ final class UserAppearanceScreenComponent: Component { self.component = component self.state = state + transition.setFrame(view: component.overNavigationContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight))) + let theme = environment.theme var animateTabChange = false @@ -1072,16 +1082,6 @@ final class UserAppearanceScreenComponent: Component { .withUpdatedProfileBackgroundEmojiId(resolvedState.backgroundFileId) ) } - - let backSize = self.backButton.update(key: .back, presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, height: 44.0) - - self.backButton.updateContentsColor(backgroundColor: .clear, contentsColor: environment.theme.rootController.navigationBar.accentTextColor, canBeExpanded: true, transition: .animated(duration: 0.2, curve: .easeInOut)) - self.backButton.frame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.navigationHeight - 44.0), size: backSize) - if self.backButton.view.superview == nil { - if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { - navigationBar.view.addSubview(self.backButton.view) - } - } var previewTransition = transition let transitionScale = (availableSize.height - 3.0) / availableSize.height @@ -1148,10 +1148,10 @@ final class UserAppearanceScreenComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width, height: 44.0) ) - let tabSelectorFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - tabSelectorSize.width) / 2.0), y: environment.statusBarHeight + floorToScreenPixels((environment.navigationHeight - environment.statusBarHeight - tabSelectorSize.height) / 2.0)), size: tabSelectorSize) + let tabSelectorFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - tabSelectorSize.width) / 2.0), y: environment.statusBarHeight + 2.0 + floorToScreenPixels((environment.navigationHeight - environment.statusBarHeight - tabSelectorSize.height) / 2.0)), size: tabSelectorSize) if let tabSelectorView = self.tabSelector.view { if tabSelectorView.superview == nil { - self.addSubview(tabSelectorView) + component.overNavigationContainer.addSubview(tabSelectorView) } transition.setFrame(view: tabSelectorView, frame: tabSelectorFrame) } @@ -1932,6 +1932,8 @@ final class UserAppearanceScreenComponent: Component { public class UserAppearanceScreen: ViewControllerComponentContainer { private let context: AccountContext + private let overNavigationContainer: UIView + private var didSetReady: Bool = false public init( @@ -1940,8 +1942,11 @@ public class UserAppearanceScreen: ViewControllerComponentContainer { ) { self.context = context + self.overNavigationContainer = SparseContainerView() + super.init(context: context, component: UserAppearanceScreenComponent( - context: context + context: context, + overNavigationContainer: self.overNavigationContainer ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: updatedPresentationData) self.automaticallyControlPresentationContextLayout = false @@ -1951,7 +1956,6 @@ public class UserAppearanceScreen: ViewControllerComponentContainer { let presentationData = context.sharedContext.currentPresentationData.with { $0 } self.title = "" self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) self.ready.set(.never()) @@ -1969,6 +1973,10 @@ public class UserAppearanceScreen: ViewControllerComponentContainer { return componentView.attemptNavigation(complete: complete) } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift b/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift index b99ea5e3..f41221d7 100644 --- a/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift @@ -218,7 +218,7 @@ final class PeerSelectionScreenComponent: Component { private var channels: [PeerSelectionScreen.ChannelInfo]? private var channelsDisposable: Disposable? - private var isSearchDisplayControllerActive: Bool = false + private var isSearchDisplayControllerActive: ChatListNavigationBar.ActiveSearch? private var searchQuery: String = "" private let searchQueryComponentSeparationCharacterSet: CharacterSet @@ -298,8 +298,7 @@ final class PeerSelectionScreenComponent: Component { } ))) : nil, rightButtons: rightButtons, - backTitle: isModal ? nil : strings.Common_Back, - backPressed: { [weak self] in + backPressed: isModal ? nil : { [weak self] in guard let self else { return } @@ -318,14 +317,15 @@ final class PeerSelectionScreenComponent: Component { strings: strings, statusBarHeight: statusBarHeight, sideInset: insets.left, - isSearchActive: self.isSearchDisplayControllerActive, - isSearchEnabled: true, + search: ChatListNavigationBar.Search(isEnabled: true), + activeSearch: self.isSearchDisplayControllerActive, primaryContent: headerContent, secondaryContent: nil, secondaryTransition: 0.0, storySubscriptions: nil, storiesIncludeHidden: false, uploadProgress: [:], + headerPanels: nil, tabsNode: nil, tabsNodeIsSearch: false, accessoryPanelContainer: nil, @@ -335,7 +335,7 @@ final class PeerSelectionScreenComponent: Component { return } - self.isSearchDisplayControllerActive = true + self.isSearchDisplayControllerActive = ChatListNavigationBar.ActiveSearch(isExternal: false) self.state?.updated(transition: .spring(duration: 0.4)) }, openStatusSetup: { _ in @@ -385,7 +385,7 @@ final class PeerSelectionScreenComponent: Component { let resultingOffset = mainOffset var offset = resultingOffset - if self.isSearchDisplayControllerActive { + if self.isSearchDisplayControllerActive != nil { offset = 0.0 } @@ -468,7 +468,7 @@ final class PeerSelectionScreenComponent: Component { self.navigationHeight = navigationHeight var removedSearchBar: SearchBarNode? - if self.isSearchDisplayControllerActive { + if self.isSearchDisplayControllerActive != nil { let searchBarNode: SearchBarNode var searchBarTransition = transition if let current = self.searchBarNode { @@ -478,8 +478,9 @@ final class PeerSelectionScreenComponent: Component { let searchBarTheme = SearchBarNodeTheme(theme: environment.theme, hasSeparator: false) searchBarNode = SearchBarNode( theme: searchBarTheme, + presentationTheme: environment.theme, strings: environment.strings, - fieldStyle: .modern, + fieldStyle: .glass, displayBackground: false ) searchBarNode.placeholderString = NSAttributedString(string: environment.strings.Common_Search, font: Font.regular(17.0), textColor: searchBarTheme.placeholder) @@ -488,7 +489,7 @@ final class PeerSelectionScreenComponent: Component { guard let self else { return } - self.isSearchDisplayControllerActive = false + self.isSearchDisplayControllerActive = nil self.state?.updated(transition: .spring(duration: 0.4)) } searchBarNode.textUpdated = { [weak self] query, _ in diff --git a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift index 523d0247..a155b1c6 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift @@ -151,7 +151,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.titleNode) self.view.addSubview(self.iconView) @@ -372,7 +372,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { if let animationContent = animationContent { let iconBoundingSize = CGSize(width: 28.0, height: 28.0) - let iconOffsetX: CGFloat = 0.0 + let iconOffsetX: CGFloat = -6.0 let iconSize = strongSelf.iconView.update( transition: .immediate, component: AnyComponent(EmojiStatusComponent( diff --git a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift index e7257534..f8ebea85 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift @@ -116,7 +116,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode { self.containerNode = ASDisplayNode() self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.clippingNode) self.clippingNode.addSubnode(self.containerNode) diff --git a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD index a9f96d66..99a8b1bb 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD +++ b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD @@ -21,6 +21,8 @@ swift_library( "//submodules/Components/MultilineTextComponent", "//submodules/Components/BalancedTextComponent", "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift index 9629f3f3..7966d579 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift @@ -11,6 +11,8 @@ import ComponentFlow import MultilineTextComponent import BalancedTextComponent import EmojiStatusComponent +import AlertComponent +import AlertInputFieldComponent private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { private var theme: PresentationTheme @@ -500,6 +502,100 @@ public enum PromptControllerTitleFont { } public func quickReplyNameAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, text: String, subtext: String, titleFont: PromptControllerTitleFont = .regular, value: String?, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController { +// let presentationData = context.sharedContext.currentPresentationData.with { $0 } +// let strings = presentationData.strings +// +// let inputState = AlertInputFieldComponent.ExternalState() +// +// let doneIsEnabled: Signal = inputState.valueSignal +// |> map { value in +// return !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty +// } +// +// let doneInProgressValuePromise = ValuePromise(false) +// let doneInProgress = doneInProgressValuePromise.get() +// +// var content: [AnyComponentWithIdentity] = [] +// content.append(AnyComponentWithIdentity( +// id: "title", +// component: AnyComponent( +// AlertTitleComponent(title: strings.WebBrowser_Exceptions_Create_Title) +// ) +// )) +// content.append(AnyComponentWithIdentity( +// id: "text", +// component: AnyComponent( +// AlertTextComponent(content: .plain(strings.WebBrowser_Exceptions_Create_Text)) +// ) +// )) +// +// let domainRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.?)*([a-zA-Z]*)?(:)?(/)?$", options: []) +// let pathRegex = try? NSRegularExpression(pattern: "^(https?://)?([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}/", options: []) +// var applyImpl: (() -> Void)? +// content.append(AnyComponentWithIdentity( +// id: "input", +// component: AnyComponent( +// AlertInputFieldComponent( +// context: context, +// initialValue: nil, +// placeholder: strings.QuickReply_ShortcutPlaceholder, +// characterLimit: characterLimit, +// hasClearButton: false, +// keyboardType: .URL, +// autocapitalizationType: .none, +// autocorrectionType: .no, +// isInitiallyFocused: true, +// externalState: inputState, +// shouldChangeText: { updatedText in +// guard let domainRegex, let pathRegex else { +// return true +// } +// let domainMatches = domainRegex.matches(in: updatedText, options: [], range: NSRange(location: 0, length: updatedText.utf16.count)) +// let pathMatches = pathRegex.matches(in: updatedText, options: [], range: NSRange(location: 0, length: updatedText.utf16.count)) +// if domainMatches.count > 0, pathMatches.count == 0 { +// return true +// } else { +// return false +// } +// }, +// returnKeyAction: { +// applyImpl?() +// } +// ) +// ) +// )) +// +// var effectiveUpdatedPresentationData: (PresentationData, Signal) +// if let updatedPresentationData { +// effectiveUpdatedPresentationData = updatedPresentationData +// } else { +// effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) +// } +// +// let alertController = AlertScreen( +// configuration: AlertScreen.Configuration(allowInputInset: true), +// content: content, +// actions: [ +// .init(title: strings.Common_Cancel, action: { +// apply(nil) +// }), +// .init(title: strings.Common_Done, type: .default, action: { +// applyImpl?() +// }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgress) +// ], +// updatedPresentationData: effectiveUpdatedPresentationData +// ) +// applyImpl = { +// let updatedLink = explicitUrl(inputState.value) +// if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true]) { +// doneInProgressValuePromise.set(true) +// apply(updatedLink) +// } else { +// inputState.animateError() +// } +// } +// return alertController + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } var dismissImpl: ((Bool) -> Void)? diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift index f4c58a78..762ea62d 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift @@ -374,7 +374,7 @@ public final class ThemeAccentColorController: ViewController { }, ready: self._ready) self.controllerNode.themeUpdated = { [weak self] theme in if let strongSelf = self { - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: theme, presentationStrings: strongSelf.presentationData.strings)) + strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: theme, presentationStrings: strongSelf.presentationData.strings), transition: .immediate) strongSelf.segmentedTitleView.theme = theme } } diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index 9f766877..b4b90573 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -871,7 +871,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { diff --git a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift index a34c56bc..7f8a5804 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift @@ -259,7 +259,7 @@ private final class ThemeCarouselThemeItemIconNode: ListViewItemNode { self.activateAreaNode = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.imageNode) @@ -725,7 +725,7 @@ public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode { self.listNode = ListView() self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.containerNode) self.addSubnode(self.listNode) diff --git a/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/Sources/ThemeSettingsThemeItem.swift b/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/Sources/ThemeSettingsThemeItem.swift index 7fd01443..4108dd42 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/Sources/ThemeSettingsThemeItem.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/Sources/ThemeSettingsThemeItem.swift @@ -229,7 +229,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { self.activateAreaNode = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) self.containerNode.addSubnode(self.imageNode) @@ -549,7 +549,7 @@ public class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { self.listNode = ListView() self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.containerNode) self.addSubnode(self.listNode) diff --git a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreen.swift b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreen.swift index d83ae3e0..1b8e8a4e 100644 --- a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreen.swift +++ b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreen.swift @@ -35,7 +35,9 @@ public class TimezoneSelectionScreen: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + + self._hasGlassStyle = true self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -82,7 +84,7 @@ public class TimezoneSelectionScreen: ViewController { private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) self.title = self.presentationData.strings.Settings_AppLanguage self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) diff --git a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift index e20cc58d..7db20975 100644 --- a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift +++ b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift @@ -109,7 +109,7 @@ private final class TimezoneListSearchContainerNode: SearchDisplayControllerCont self.presentationDataPromise = Promise(self.presentationData) self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) + self.dimNode.backgroundColor = .clear self.listNode = ListView() self.listNode.accessibilityPageScrolledString = { row, count in @@ -523,7 +523,7 @@ final class TimezoneSelectionScreenNode: ViewControllerTracingNode { self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: TimezoneListSearchContainerNode(context: self.context, timeZoneList: timeZoneList, action: self.action), inline: true, cancel: { [weak self] in self?.requestDeactivateSearch() - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift index 0d63c6d7..64cae777 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift @@ -396,7 +396,7 @@ public class WallpaperGalleryController: ViewController { self.title = self.presentationData.strings.WallpaperPreview_Title } self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData), transition: .immediate) self.toolbarNode?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) self.patternPanelNode?.updateTheme(self.presentationData.theme) diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperPatternPanelNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperPatternPanelNode.swift index c767e899..4e6e46b1 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperPatternPanelNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperPatternPanelNode.swift @@ -131,7 +131,7 @@ private final class WallpaperPatternItemNode : ListViewItemNode { init() { self.wallpaperNode = SettingsThemeWallpaperNode(displayLoading: true) - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.wallpaperNode) } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift index 77ed39bb..c59e3649 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift @@ -158,7 +158,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina self.mode = mode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -216,7 +216,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina private func updateThemeAndStrings() { self.title = self.presentationData.strings.WallpaperColors_Title self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) if self.isNodeLoaded { self.controllerNode.updatePresentationData(self.presentationData) diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift index bd601204..af7ea2f9 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift @@ -45,8 +45,6 @@ public final class ThemeGridController: ViewController { private let presentationDataPromise = Promise() private var presentationDataDisposable: Disposable? - private var searchContentNode: NavigationBarSearchContentNode? - private var isEmpty: Bool? private var editingMode: Bool = false @@ -67,7 +65,7 @@ public final class ThemeGridController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationDataPromise.set(.single(self.presentationData)) - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) switch mode { case .generic: @@ -81,9 +79,6 @@ public final class ThemeGridController: ViewController { self.scrollToTop = { [weak self] in if let strongSelf = self { - if let searchContentNode = strongSelf.searchContentNode { - searchContentNode.updateExpansionProgress(1.0, animated: true) - } strongSelf.controllerNode.scrollToTop() } } @@ -128,8 +123,7 @@ public final class ThemeGridController: ViewController { } self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) - self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Wallpaper_Search) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) if self.isNodeLoaded { self.controllerNode.updatePresentationData(self.presentationData) @@ -182,7 +176,6 @@ public final class ThemeGridController: ViewController { } else { uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in if let strongSelf = self { - strongSelf.deactivateSearch(animated: false) strongSelf.controllerNode.scrollToTop(animated: false) } if let controller = controller { @@ -392,9 +385,6 @@ public final class ThemeGridController: ViewController { } }) self.controllerNode.navigationBar = self.navigationBar - self.controllerNode.requestDeactivateSearch = { [weak self] in - self?.deactivateSearch(animated: true) - } self.controllerNode.requestWallpaperRemoval = { [weak self] in if let self { self.completion(.remove) @@ -403,10 +393,6 @@ public final class ThemeGridController: ViewController { } self.controllerNode.gridNode.visibleContentOffsetChanged = { [weak self] offset in if let strongSelf = self { - if let searchContentNode = strongSelf.searchContentNode { - searchContentNode.updateGridVisibleContentOffset(offset) - } - var previousContentOffsetValue: CGFloat? if let previousContentOffset = strongSelf.previousContentOffset, case let .known(value) = previousContentOffset { previousContentOffsetValue = value @@ -427,12 +413,6 @@ public final class ThemeGridController: ViewController { strongSelf.previousContentOffset = offset } } - - self.controllerNode.gridNode.scrollingCompleted = { [weak self] in - if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { - let _ = strongSelf.controllerNode.fixNavigationSearchableGridNodeScrolling(searchNode: searchContentNode) - } - } self._ready.set(self.controllerNode.ready.get()) @@ -492,38 +472,9 @@ public final class ThemeGridController: ViewController { self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, transition: transition) } - func activateSearch() { - if self.displayNavigationBar { - let _ = (self.controllerNode.ready.get() - |> take(1) - |> deliverOnMainQueue).start(completed: { [weak self] in - guard let strongSelf = self else { - return - } - if let scrollToTop = strongSelf.scrollToTop { - scrollToTop() - } - if let searchContentNode = strongSelf.searchContentNode { - strongSelf.controllerNode.activateSearch(placeholderNode: searchContentNode.placeholderNode) - } - strongSelf.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring)) - }) - } - } - - func deactivateSearch(animated: Bool) { - if !self.displayNavigationBar { - self.setDisplayNavigationBar(true, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) - if let searchContentNode = self.searchContentNode { - self.controllerNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated) - } - } - } - @objc func editPressed() { self.editingMode = true self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) - self.searchContentNode?.setIsEnabled(false, animated: true) self.controllerNode.updateState { state in var state = state state.editing = true @@ -534,7 +485,6 @@ public final class ThemeGridController: ViewController { @objc func donePressed() { self.editingMode = false self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) - self.searchContentNode?.setIsEnabled(true, animated: true) self.controllerNode.updateState { state in var state = state state.editing = false diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift index 039b41bb..5d59dd66 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift @@ -162,7 +162,6 @@ final class ThemeGridControllerNode: ASDisplayNode { private let emptyStateUpdated: (Bool) -> Void private let resetWallpapers: () -> Void - var requestDeactivateSearch: (() -> Void)? var requestWallpaperRemoval: (() -> Void)? let ready = ValuePromise() @@ -958,75 +957,6 @@ final class ThemeGridControllerNode: ASDisplayNode { } } - func activateSearch(placeholderNode: SearchBarPlaceholderNode) { - guard let (containerLayout, navigationBarHeight) = self.validLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else { - return - } - - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ThemeGridSearchContentNode(context: context, openResult: { [weak self] result in - if let strongSelf = self { - strongSelf.presentPreviewController(.contextResult(result)) - } - }), cancel: { [weak self] in - self?.requestDeactivateSearch?() - }) - - self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) - self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in - if let strongSelf = self, let strongPlaceholderNode = placeholderNode { - if isSearchBar { - strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode) - } else { - strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) - } - } - }, placeholder: placeholderNode) - } - - func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) { - if let searchDisplayController = self.searchDisplayController { - searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated) - self.searchDisplayController = nil - } - } - - func fixNavigationSearchableGridNodeScrolling(searchNode: NavigationBarSearchContentNode) -> Bool { - if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 { - let scrollToItem: GridNodeScrollToItem - let targetProgress: CGFloat - - let duration: Double = 0.3 - let curve = ContainedViewLayoutTransitionCurve.slide - let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: curve) - let timingFunction = curve.timingFunction - let mediaTimingFunction = curve.mediaTimingFunction - - if searchNode.expansionProgress < 0.6 { - scrollToItem = GridNodeScrollToItem(index: 0, position: .top(navigationBarSearchContentHeight), transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true) - targetProgress = 0.0 - } else { - scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: transition, directionHint: .up, adjustForSection: true, adjustForTopInset: true) - targetProgress = 1.0 - } - - let previousOffset = (self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top) - searchNode.updateExpansionProgress(targetProgress, animated: true) - - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { _ in }) - - let offset = (self.gridNode.scrollView.contentOffset.y + self.gridNode.scrollView.contentInset.top) - previousOffset - - self.backgroundNode.layer.animatePosition(from: self.backgroundNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.backgroundNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction) - self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.separatorNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction) - self.colorItemNode.layer.animatePosition(from: self.colorItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.colorItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction) - self.galleryItemNode.layer.animatePosition(from: self.galleryItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.galleryItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction) - self.descriptionItemNode.layer.animatePosition(from: self.descriptionItemNode.layer.position.offsetBy(dx: 0.0, dy: offset), to: self.descriptionItemNode.layer.position, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction) - - return true - } - return false - } - func scrollToTop(animated: Bool = true) { if let searchDisplayController = self.searchDisplayController { searchDisplayController.contentNode.scrollToTop() diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchColorsItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchColorsItem.swift index 2056b25c..842c9003 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchColorsItem.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchColorsItem.swift @@ -180,7 +180,7 @@ class ThemeGridSearchColorsItemNode: ListViewItemNode { self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.separatorNode) diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift index 3d36ecf2..d1ccccce 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift @@ -883,13 +883,13 @@ final class LiveStreamSettingsScreenComponent: Component { transition: transition, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: environment.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 8bedc66e..44e173df 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -992,7 +992,7 @@ final class ShareWithPeersScreenComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) let promptController = promptController( - sharedContext: component.context.sharedContext, + context: component.context, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)), text: presentationData.strings.Stories_CreateAlbum_Title, titleFont: .bold, @@ -2772,7 +2772,7 @@ final class ShareWithPeersScreenComponent: Component { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in diff --git a/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/BUILD b/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/BUILD index 1f4cce9b..d348f0bd 100644 --- a/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/BUILD @@ -23,6 +23,8 @@ swift_library( "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramCore", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/Sources/BalanceNeededScreen.swift b/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/Sources/BalanceNeededScreen.swift index d712e9ea..56e8473c 100644 --- a/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/Sources/BalanceNeededScreen.swift +++ b/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/Sources/BalanceNeededScreen.swift @@ -14,6 +14,7 @@ import TelegramStringFormatting import BundleIconComponent import TelegramCore import TelegramPresentationData +import GlassBarButtonComponent private final class BalanceNeededSheetContentComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -49,9 +50,7 @@ private final class BalanceNeededSheetContentComponent: Component { private var component: BalanceNeededSheetContentComponent? private weak var state: EmptyComponentState? - - private var cachedCloseImage: (UIImage, PresentationTheme)? - + override init(frame: CGRect) { super.init(frame: frame) } @@ -71,28 +70,27 @@ private final class BalanceNeededSheetContentComponent: Component { let sideInset: CGFloat = 16.0 - let closeImage: UIImage - if let (image, theme) = self.cachedCloseImage, theme === environment.theme { - closeImage = image - } else { - closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: environment.theme.actionSheet.inputClearButtonColor)! - self.cachedCloseImage = (closeImage, environment.theme) - } let closeButtonSize = self.closeButton.update( transition: .immediate, - component: AnyComponent(Button( - content: AnyComponent(Image(image: closeImage)), - action: { [weak self] in - guard let self, let component = self.component else { - return - } + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in component.dismiss() } )), environment: {}, - containerSize: CGSize(width: 30.0, height: 30.0) + containerSize: CGSize(width: 40.0, height: 40.0) ) - let closeButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - closeButtonSize.width - 16.0, y: 12.0), size: closeButtonSize) + let closeButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: closeButtonSize) if let closeButtonView = self.closeButton.view { if closeButtonView.superview == nil { self.addSubview(closeButtonView) @@ -164,10 +162,12 @@ private final class BalanceNeededSheetContentComponent: Component { contentHeight += textSize.height contentHeight += 24.0 + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let buttonSize = self.button.update( transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: environment.theme.list.itemCheckColors.fillColor, foreground: environment.theme.list.itemCheckColors.foregroundColor, pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) @@ -187,9 +187,9 @@ private final class BalanceNeededSheetContentComponent: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0) ) - let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: buttonSize) if let buttonView = self.button.view { if buttonView.superview == nil { self.addSubview(buttonView) @@ -197,13 +197,8 @@ private final class BalanceNeededSheetContentComponent: Component { transition.setFrame(view: buttonView, frame: buttonFrame) } contentHeight += buttonSize.height - - if environment.safeInsets.bottom.isZero { - contentHeight += 16.0 - } else { - contentHeight += environment.safeInsets.bottom + 8.0 - } - + contentHeight += buttonInsets.bottom + return CGSize(width: availableSize.width, height: contentHeight) } } @@ -307,6 +302,7 @@ private final class BalanceNeededScreenComponent: Component { }) } )), + style: .glass, backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), animateOut: self.sheetAnimateOut )), diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index b0a69117..a733e61b 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -504,6 +504,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let starsContext: StarsContext let options: [Any] let purpose: StarsPurchasePurpose @@ -515,6 +516,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { init( context: AccountContext, + overNavigationContainer: UIView, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, @@ -525,6 +527,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { completion: @escaping (Int64) -> Void ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.starsContext = starsContext self.options = options self.purpose = purpose @@ -759,8 +762,6 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { let scrollContent = Child(ScrollComponent.self) let star = Child(PremiumStarComponent.self) let avatar = Child(GiftAvatarComponent.self) - let topPanel = Child(BlurredBackgroundComponent.self) - let topSeparator = Child(Rectangle.self) let title = Child(MultilineTextComponent.self) let balanceTitle = Child(MultilineTextComponent.self) let balanceValue = Child(MultilineTextComponent.self) @@ -816,29 +817,13 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { UIColor(rgb: 0xfdd219) ], particleColor: UIColor(rgb: 0xf9b004), - backgroundColor: environment.theme.list.blocksBackgroundColor + backgroundColor: nil ), availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), transition: context.transition ) } - let topPanel = topPanel.update( - component: BlurredBackgroundComponent( - color: environment.theme.rootController.navigationBar.blurredBackgroundColor - ), - availableSize: CGSize(width: context.availableSize.width, height: environment.navigationHeight), - transition: context.transition - ) - - let topSeparator = topSeparator.update( - component: Rectangle( - color: environment.theme.rootController.navigationBar.separatorColor - ), - availableSize: CGSize(width: context.availableSize.width, height: UIScreenPixel), - transition: context.transition - ) - let titleText: String switch context.component.purpose { case .generic: @@ -948,14 +933,12 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) ) - let topPanelAlpha: CGFloat let titleOffset: CGFloat let titleScale: CGFloat let titleOffsetDelta = (topInset + 160.0) - (environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0) let titleAlpha: CGFloat if let topContentOffset = state.topContentOffset { - topPanelAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0 let topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0 titleOffset = topContentOffset let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta)) @@ -963,42 +946,37 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { titleAlpha = 1.0 } else { - topPanelAlpha = 0.0 titleScale = 1.0 titleOffset = 0.0 titleAlpha = 1.0 } - context.add(header + context.addWithExternalContainer(header .position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + header.size.height / 2.0 - 30.0 - titleOffset * titleScale)) - .scale(titleScale) + .scale(titleScale), + container: context.component.overNavigationContainer ) - context.add(topPanel - .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height / 2.0)) - .opacity(topPanelAlpha) - ) - context.add(topSeparator - .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height)) - .opacity(topPanelAlpha) - ) - - context.add(title + context.addWithExternalContainer(title .position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0))) .scale(titleScale) - .opacity(titleAlpha) + .opacity(titleAlpha), + container: context.component.overNavigationContainer ) let navigationHeight = environment.navigationHeight - environment.statusBarHeight let topBalanceOriginY = environment.statusBarHeight + (navigationHeight - balanceTitle.size.height - balanceValue.size.height) / 2.0 - context.add(balanceTitle - .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceTitle.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height / 2.0)) + context.addWithExternalContainer(balanceTitle + .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceTitle.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height / 2.0)), + container: context.component.overNavigationContainer ) - context.add(balanceValue - .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceValue.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0)) + context.addWithExternalContainer(balanceValue + .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceValue.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0)), + container: context.component.overNavigationContainer ) - context.add(balanceIcon - .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceValue.size.width - balanceIcon.size.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 - UIScreenPixel)) + context.addWithExternalContainer(balanceIcon + .position(CGPoint(x: context.availableSize.width - 16.0 - environment.safeInsets.right - balanceValue.size.width - balanceIcon.size.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 - UIScreenPixel)), + container: context.component.overNavigationContainer ) return context.availableSize @@ -1010,6 +988,8 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { fileprivate let context: AccountContext fileprivate let starsContext: StarsContext + private let overNavigationContainer: UIView + private var didSetReady = false private let _ready = Promise() public override var ready: Promise { @@ -1027,6 +1007,8 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { ) { self.context = context self.starsContext = starsContext + + self.overNavigationContainer = SparseContainerView() var openAppExamplesImpl: (() -> Void)? var updateInProgressImpl: ((Bool) -> Void)? @@ -1034,6 +1016,7 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { var completionImpl: ((Int64) -> Void)? super.init(context: context, component: StarsPurchaseScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, starsContext: starsContext, options: options, purpose: purpose, @@ -1050,7 +1033,7 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { completion: { stars in completionImpl?(stars) } - ), navigationBarAppearance: .transparent, presentationMode: .modal, theme: customTheme.flatMap { .custom($0) } ?? .default) + ), navigationBarAppearance: .default, presentationMode: .modal, theme: customTheme.flatMap { .custom($0) } ?? .default) let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -1090,6 +1073,10 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { completion(stars) } } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { @@ -1129,10 +1116,10 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { super.containerLayoutUpdated(layout, transition: transition) if !self.didSetReady { - if let view = self.node.hostView.findTaggedView(tag: PremiumStarComponent.View.Tag()) as? PremiumStarComponent.View { + if let view = findTaggedComponentViewImpl(view: self.node.view, tag: PremiumStarComponent.View.Tag()) as? PremiumStarComponent.View { self.didSetReady = true self._ready.set(view.ready) - } else if let view = self.node.hostView.findTaggedView(tag: GiftAvatarComponent.View.Tag()) as? GiftAvatarComponent.View { + } else if let view = findTaggedComponentViewImpl(view: self.node.view, tag: GiftAvatarComponent.View.Tag()) as? GiftAvatarComponent.View { self.didSetReady = true self._ready.set(view.ready) } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD index 48c14fca..647e3f90 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/BUILD @@ -39,6 +39,8 @@ swift_library( "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", "//submodules/TelegramUI/Components/Gifts/GiftAnimationComponent", "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/Gifts/TableComponent", + "//submodules/TelegramUI/Components/Gifts/PeerTableCellComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index c396f492..7677a5af 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -28,6 +28,8 @@ import MiniAppListScreen import PremiumStarComponent import GiftAnimationComponent import GlassBarButtonComponent +import TableComponent +import PeerTableCellComponent private final class StarsTransactionSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -677,7 +679,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in @@ -936,9 +938,10 @@ private final class StarsTransactionSheetContent: CombinedComponent { component: AnyComponent( Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, + strings: strings, peer: nil ) ), @@ -983,9 +986,10 @@ private final class StarsTransactionSheetContent: CombinedComponent { id: AnyHashable(0), component: AnyComponent(Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, + strings: strings, peer: toPeer ) ), @@ -1026,9 +1030,10 @@ private final class StarsTransactionSheetContent: CombinedComponent { toComponent = AnyComponent( Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, + strings: strings, peer: toPeer ) ), @@ -1164,9 +1169,10 @@ private final class StarsTransactionSheetContent: CombinedComponent { component: AnyComponent( Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, + strings: strings, peer: toPeer ) ), @@ -1196,9 +1202,10 @@ private final class StarsTransactionSheetContent: CombinedComponent { component: AnyComponent( Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, + strings: strings, peer: starRefPeer ) ), @@ -1227,9 +1234,10 @@ private final class StarsTransactionSheetContent: CombinedComponent { component: AnyComponent( Button( content: AnyComponent( - PeerCellComponent( + PeerTableCellComponent( context: component.context, theme: theme, + strings: strings, peer: toPeer ) ), @@ -1638,7 +1646,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { style: .glass, color: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.1), foreground: theme.list.itemCheckColors.fillColor, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8), + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) ), content: AnyComponentWithIdentity( id: AnyHashable(0), @@ -2146,241 +2154,6 @@ public class StarsTransactionScreen: ViewControllerComponentContainer { } } -private final class TableComponent: CombinedComponent { - class Item: Equatable { - public let id: AnyHashable - public let title: String - public let component: AnyComponent - public let insets: UIEdgeInsets? - - public init(id: IdType, title: String, component: AnyComponent, insets: UIEdgeInsets? = nil) { - self.id = AnyHashable(id) - self.title = title - self.component = component - self.insets = insets - } - - public static func == (lhs: Item, rhs: Item) -> Bool { - if lhs.id != rhs.id { - return false - } - if lhs.title != rhs.title { - return false - } - if lhs.component != rhs.component { - return false - } - if lhs.insets != rhs.insets { - return false - } - return true - } - } - - private let theme: PresentationTheme - private let items: [Item] - - public init(theme: PresentationTheme, items: [Item]) { - self.theme = theme - self.items = items - } - - public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool { - if lhs.theme !== rhs.theme { - return false - } - if lhs.items != rhs.items { - return false - } - return true - } - - final class State: ComponentState { - var cachedLeftColumnImage: (UIImage, PresentationTheme)? - var cachedBorderImage: (UIImage, PresentationTheme)? - } - - func makeState() -> State { - return State() - } - - public static var body: Body { - let leftColumnBackground = Child(Image.self) - let verticalBorder = Child(Rectangle.self) - let titleChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) - let valueChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) - let borderChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) - let outerBorder = Child(Image.self) - - return { context in - let verticalPadding: CGFloat = 11.0 - let horizontalPadding: CGFloat = 12.0 - let borderWidth: CGFloat = 1.0 - let borderRadius: CGFloat = 14.0 - - let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor - let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6) - let secondaryBackgroundColor = context.component.theme.overallDarkAppearance ? context.component.theme.list.itemModalBlocksBackgroundColor : context.component.theme.list.itemInputField.backgroundColor - - var leftColumnWidth: CGFloat = 0.0 - - var updatedTitleChildren: [_UpdatedChildComponent] = [] - var updatedValueChildren: [(_UpdatedChildComponent, UIEdgeInsets)] = [] - var updatedBorderChildren: [_UpdatedChildComponent] = [] - - for item in context.component.items { - let titleChild = titleChildren[item.id].update( - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: item.title, font: Font.regular(15.0), textColor: context.component.theme.list.itemPrimaryTextColor)) - )), - availableSize: context.availableSize, - transition: context.transition - ) - updatedTitleChildren.append(titleChild) - - if titleChild.size.width > leftColumnWidth { - leftColumnWidth = titleChild.size.width - } - } - - leftColumnWidth = max(100.0, leftColumnWidth + horizontalPadding * 2.0) - let rightColumnWidth = context.availableSize.width - leftColumnWidth - - var i = 0 - var rowHeights: [Int: CGFloat] = [:] - var totalHeight: CGFloat = 0.0 - - for item in context.component.items { - let titleChild = updatedTitleChildren[i] - - let insets: UIEdgeInsets - if let customInsets = item.insets { - insets = customInsets - } else { - insets = UIEdgeInsets(top: 0.0, left: horizontalPadding, bottom: 0.0, right: horizontalPadding) - } - let valueChild = valueChildren[item.id].update( - component: item.component, - availableSize: CGSize(width: rightColumnWidth - insets.left - insets.right, height: context.availableSize.height), - transition: context.transition - ) - updatedValueChildren.append((valueChild, insets)) - - let rowHeight = max(40.0, max(titleChild.size.height, valueChild.size.height) + verticalPadding * 2.0) - rowHeights[i] = rowHeight - totalHeight += rowHeight - - if i < context.component.items.count - 1 { - let borderChild = borderChildren[item.id].update( - component: AnyComponent(Rectangle(color: borderColor)), - availableSize: CGSize(width: context.availableSize.width, height: borderWidth), - transition: context.transition - ) - updatedBorderChildren.append(borderChild) - } - - i += 1 - } - - let leftColumnImage: UIImage - if let (currentImage, theme) = context.state.cachedLeftColumnImage, theme === context.component.theme { - leftColumnImage = currentImage - } else { - leftColumnImage = generateImage(CGSize(width: borderRadius * 2.0 + 4.0, height: borderRadius * 2.0 + 4.0), rotatedContext: { size, context in - let bounds = CGRect(origin: .zero, size: CGSize(width: size.width + borderRadius, height: size.height)) - context.clear(bounds) - - let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil) - context.setFillColor(secondaryBackgroundColor.cgColor) - context.addPath(path) - context.fillPath() - })!.stretchableImage(withLeftCapWidth: Int(borderRadius), topCapHeight: Int(borderRadius)) - context.state.cachedLeftColumnImage = (leftColumnImage, context.component.theme) - } - - let leftColumnBackground = leftColumnBackground.update( - component: Image(image: leftColumnImage), - availableSize: CGSize(width: leftColumnWidth, height: totalHeight), - transition: context.transition - ) - context.add( - leftColumnBackground - .position(CGPoint(x: leftColumnWidth / 2.0, y: totalHeight / 2.0)) - ) - - let borderImage: UIImage - if let (currentImage, theme) = context.state.cachedBorderImage, theme === context.component.theme { - borderImage = currentImage - } else { - borderImage = generateImage(CGSize(width: borderRadius * 2.0 + 4.0, height: borderRadius * 2.0 + 4.0), rotatedContext: { size, context in - let bounds = CGRect(origin: .zero, size: size) - context.clear(bounds) - - let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil) - context.setBlendMode(.clear) - context.addPath(path) - context.fillPath() - - context.setBlendMode(.normal) - context.setStrokeColor(borderColor.cgColor) - context.setLineWidth(borderWidth) - context.addPath(path) - context.strokePath() - })!.stretchableImage(withLeftCapWidth: Int(borderRadius), topCapHeight: Int(borderRadius)) - context.state.cachedBorderImage = (borderImage, context.component.theme) - } - - let outerBorder = outerBorder.update( - component: Image(image: borderImage), - availableSize: CGSize(width: context.availableSize.width, height: totalHeight), - transition: context.transition - ) - context.add(outerBorder - .position(CGPoint(x: context.availableSize.width / 2.0, y: totalHeight / 2.0)) - ) - - let verticalBorder = verticalBorder.update( - component: Rectangle(color: borderColor), - availableSize: CGSize(width: borderWidth, height: totalHeight), - transition: context.transition - ) - context.add( - verticalBorder - .position(CGPoint(x: leftColumnWidth - borderWidth / 2.0, y: totalHeight / 2.0)) - ) - - i = 0 - var originY: CGFloat = 0.0 - for (titleChild, (valueChild, valueInsets)) in zip(updatedTitleChildren, updatedValueChildren) { - let rowHeight = rowHeights[i] ?? 0.0 - - let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size) - let valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + valueInsets.left, y: originY + verticalPadding), size: valueChild.size) - - context.add(titleChild - .position(titleFrame.center) - ) - - context.add(valueChild - .position(valueFrame.center) - ) - - if i < updatedBorderChildren.count { - let borderChild = updatedBorderChildren[i] - context.add(borderChild - .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + rowHeight - borderWidth / 2.0)) - ) - } - - originY += rowHeight - i += 1 - } - - return CGSize(width: context.availableSize.width, height: totalHeight) - } - } -} - private final class PeerCellComponent: Component { let context: AccountContext let theme: PresentationTheme diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift index e07614b1..c4207b6e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift @@ -26,6 +26,7 @@ final class StarsStatisticsScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let peerId: EnginePeer.Id let revenueContext: StarsRevenueStatsContext let openTransaction: (StarsContext.State.Transaction) -> Void @@ -36,6 +37,7 @@ final class StarsStatisticsScreenComponent: Component { init( context: AccountContext, + overNavigationContainer: UIView, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext, openTransaction: @escaping (StarsContext.State.Transaction) -> Void, @@ -45,6 +47,7 @@ final class StarsStatisticsScreenComponent: Component { buyAds: @escaping () -> Void ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.peerId = peerId self.revenueContext = revenueContext self.openTransaction = openTransaction @@ -125,10 +128,6 @@ final class StarsStatisticsScreenComponent: Component { private let scrollView: ScrollViewImpl private var currentSelectedPanelId: AnyHashable? - - private let navigationBackgroundView: BlurredBackgroundView - private let navigationSeparatorLayer: SimpleLayer - private let navigationSeparatorLayerContainer: SimpleLayer private let headerView = ComponentView() private let headerOffsetContainer: UIView @@ -175,14 +174,6 @@ final class StarsStatisticsScreenComponent: Component { self.headerOffsetContainer = UIView() self.headerOffsetContainer.isUserInteractionEnabled = false - self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true) - self.navigationBackgroundView.alpha = 0.0 - - self.navigationSeparatorLayer = SimpleLayer() - self.navigationSeparatorLayer.opacity = 0.0 - self.navigationSeparatorLayerContainer = SimpleLayer() - self.navigationSeparatorLayerContainer.opacity = 0.0 - self.scrollContainerView = UIView() self.scrollView = ScrollViewImpl() @@ -208,11 +199,6 @@ final class StarsStatisticsScreenComponent: Component { self.scrollView.addSubview(self.scrollContainerView) self.scrollContainerView.addSubview(self.transactionsBackground) - self.addSubview(self.navigationBackgroundView) - - self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer) - self.layer.addSublayer(self.navigationSeparatorLayerContainer) - self.addSubview(self.headerOffsetContainer) } @@ -307,18 +293,10 @@ final class StarsStatisticsScreenComponent: Component { let isLockedAtPanels = scrollBounds.maxY == self.scrollView.contentSize.height if let _ = self.navigationMetrics { - let topContentOffset = self.scrollView.contentOffset.y - let navigationBackgroundAlpha = min(20.0, max(0.0, topContentOffset)) / 20.0 - - let animatedTransition = ComponentTransition(animation: .curve(duration: 0.18, curve: .easeInOut)) - animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) - animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) - let expansionDistance: CGFloat = 32.0 var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) - transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View { panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition) } @@ -417,19 +395,7 @@ final class StarsStatisticsScreenComponent: Component { self.controller = environment.controller self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight) - - self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - - let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight)) - self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame) - - let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)) - - transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame) - transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size)) - + self.backgroundColor = environment.theme.list.blocksBackgroundColor var contentHeight: CGFloat = 0.0 @@ -454,7 +420,7 @@ final class StarsStatisticsScreenComponent: Component { ) if let titleView = self.titleView.view { if titleView.superview == nil { - self.addSubview(titleView) + component.overNavigationContainer.addSubview(titleView) } let titlePosition = CGPoint(x: availableSize.width / 2.0, y: environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0) transition.setPosition(view: titleView, position: titlePosition) @@ -847,6 +813,8 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { private let peerId: EnginePeer.Id private let revenueContext: StarsRevenueStatsContext + private let overNavigationContainer: UIView + private weak var tooltipScreen: UndoOverlayController? private var timer: Foundation.Timer? @@ -857,6 +825,8 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { self.peerId = peerId self.revenueContext = revenueContext + self.overNavigationContainer = SparseContainerView() + var buyImpl: (() -> Void)? var withdrawImpl: (() -> Void)? var buyAdsImpl: (() -> Void)? @@ -864,6 +834,7 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { var openTransactionImpl: ((StarsContext.State.Transaction) -> Void)? super.init(context: context, component: StarsStatisticsScreenComponent( context: context, + overNavigationContainer: self.overNavigationContainer, peerId: peerId, revenueContext: revenueContext, openTransaction: { transaction in @@ -881,7 +852,7 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { buyAds: { buyAdsImpl?() } - ), navigationBarAppearance: .transparent) + ), navigationBarAppearance: .default) self.navigationPresentation = .modalInLargeLayout @@ -1060,6 +1031,10 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { } componentView.scrollToTop() } + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 07bed879..91d7256e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -102,10 +102,6 @@ final class StarsTransactionsScreenComponent: Component { private let scrollView: ScrollViewImpl private var currentSelectedPanelId: AnyHashable? - - private let navigationBackgroundView: BlurredBackgroundView - private let navigationSeparatorLayer: SimpleLayer - private let navigationSeparatorLayerContainer: SimpleLayer private let scrollContainerView: UIView @@ -160,14 +156,6 @@ final class StarsTransactionsScreenComponent: Component { private var cachedChevronImage: (UIImage, PresentationTheme)? override init(frame: CGRect) { - self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true) - self.navigationBackgroundView.alpha = 0.0 - - self.navigationSeparatorLayer = SimpleLayer() - self.navigationSeparatorLayer.opacity = 0.0 - self.navigationSeparatorLayerContainer = SimpleLayer() - self.navigationSeparatorLayerContainer.opacity = 0.0 - self.scrollContainerView = UIView() self.scrollView = ScrollViewImpl() @@ -191,11 +179,6 @@ final class StarsTransactionsScreenComponent: Component { self.addSubview(self.scrollView) self.scrollView.addSubview(self.scrollContainerView) - - self.addSubview(self.navigationBackgroundView) - - self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer) - self.layer.addSublayer(self.navigationSeparatorLayerContainer) } required init?(coder: NSCoder) { @@ -296,7 +279,6 @@ final class StarsTransactionsScreenComponent: Component { var topContentOffset = self.scrollView.contentOffset.y - let navigationBackgroundAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0 topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0 titleOffset = topContentOffset let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta)) @@ -317,15 +299,10 @@ final class StarsTransactionsScreenComponent: Component { headerTransition.setScale(view: titleView, scale: titleScale) } - let animatedTransition = ComponentTransition(animation: .curve(duration: 0.18, curve: .easeInOut)) - animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) - animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) - let expansionDistance: CGFloat = 32.0 var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) - transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View { panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition) } @@ -435,18 +412,6 @@ final class StarsTransactionsScreenComponent: Component { self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight) - self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - - let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight)) - self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame) - - let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)) - - transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame) - transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size)) - self.backgroundColor = environment.theme.list.blocksBackgroundColor var contentHeight: CGFloat = 0.0 @@ -519,7 +484,7 @@ final class StarsTransactionsScreenComponent: Component { UIColor(rgb: 0xfdd219) ], particleColor: UIColor(rgb: 0xf9b004), - backgroundColor: environment.theme.list.blocksBackgroundColor + backgroundColor: nil )) } @@ -532,7 +497,13 @@ final class StarsTransactionsScreenComponent: Component { let starFrame = CGRect(origin: .zero, size: starSize) if let starView = self.starView.view { if starView.superview == nil { - self.insertSubview(starView, aboveSubview: self.scrollView) + if let navigationBar = environment.controller()?.navigationBar { + if let titleView = self.titleView.view, titleView.superview != nil { + navigationBar.view.insertSubview(starView, belowSubview: titleView) + } else { + navigationBar.view.insertSubview(starView, aboveSubview: navigationBar.backgroundView) + } + } } starTransition.setBounds(view: starView, bounds: starFrame) } @@ -562,7 +533,9 @@ final class StarsTransactionsScreenComponent: Component { ) if let titleView = self.titleView.view { if titleView.superview == nil { - self.addSubview(titleView) + if let navigationBar = environment.controller()?.navigationBar { + navigationBar.view.insertSubview(titleView, aboveSubview: navigationBar.backgroundView) + } } starTransition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleSize)) } @@ -617,7 +590,13 @@ final class StarsTransactionsScreenComponent: Component { if let topBalanceTitleView = self.topBalanceTitleView.view { if topBalanceTitleView.superview == nil { topBalanceTitleView.alpha = 0.0 - self.addSubview(topBalanceTitleView) + if let navigationBar = environment.controller()?.navigationBar { + if let titleView = self.titleView.view, titleView.superview != nil { + navigationBar.view.insertSubview(topBalanceTitleView, aboveSubview: titleView) + } else { + navigationBar.view.insertSubview(topBalanceTitleView, aboveSubview: navigationBar.backgroundView) + } + } } starTransition.setFrame(view: topBalanceTitleView, frame: topBalanceTitleFrame) } @@ -626,7 +605,13 @@ final class StarsTransactionsScreenComponent: Component { if let topBalanceValueView = self.topBalanceValueView.view { if topBalanceValueView.superview == nil { topBalanceValueView.alpha = 0.0 - self.addSubview(topBalanceValueView) + if let navigationBar = environment.controller()?.navigationBar { + if let titleView = self.titleView.view, titleView.superview != nil { + navigationBar.view.insertSubview(topBalanceValueView, aboveSubview: titleView) + } else { + navigationBar.view.insertSubview(topBalanceValueView, aboveSubview: navigationBar.backgroundView) + } + } } starTransition.setFrame(view: topBalanceValueView, frame: topBalanceValueFrame) } @@ -638,7 +623,13 @@ final class StarsTransactionsScreenComponent: Component { if let topBalanceIconView = self.topBalanceIconView.view { if topBalanceIconView.superview == nil { topBalanceIconView.alpha = 0.0 - self.addSubview(topBalanceIconView) + if let navigationBar = environment.controller()?.navigationBar { + if let titleView = self.titleView.view, titleView.superview != nil { + navigationBar.view.insertSubview(topBalanceIconView, aboveSubview: titleView) + } else { + navigationBar.view.insertSubview(topBalanceIconView, aboveSubview: navigationBar.backgroundView) + } + } } starTransition.setFrame(view: topBalanceIconView, frame: topBalanceIconFrame) } @@ -844,7 +835,7 @@ final class StarsTransactionsScreenComponent: Component { footer: nil, items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( - itemGenerator: ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), icon: PresentationResourcesSettings.earnStars, title: environment.strings.Monetization_EarnStarsInfo_Title, titleBadge: presentationData.strings.Settings_New, label: environment.strings.Monetization_EarnStarsInfo_Text, labelStyle: .multilineDetailText, sectionId: 0, style: .blocks, action: { + itemGenerator: ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), icon: PresentationResourcesSettings.earnStars, title: environment.strings.Monetization_EarnStarsInfo_Title, titleBadge: nil, label: environment.strings.Monetization_EarnStarsInfo_Text, labelStyle: .multilineDetailText, sectionId: 0, style: .blocks, action: { }), params: ListViewItemLayoutParams(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true), action: { [weak self] in @@ -1261,7 +1252,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { gift: { giftImpl?() } - ), navigationBarAppearance: .transparent) + ), navigationBarAppearance: .default) self.navigationPresentation = .modalInLargeLayout diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index 050dccbc..d5157826 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -348,7 +348,7 @@ private final class SheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in @@ -655,7 +655,9 @@ private final class SheetContent: CombinedComponent { controller?.complete(paid: success) controller?.dismissAnimated() - starsContext.load(force: true) + Queue.mainQueue().after(2.5) { + starsContext.load(force: true) + } }) } ), diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD index 4e7000f2..01c08ce4 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD @@ -42,6 +42,8 @@ swift_library( "//submodules/TelegramUI/Components/Stars/BalanceNeededScreen", "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsRevenueWithdrawalController.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsRevenueWithdrawalController.swift index cc62907d..41b92175 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsRevenueWithdrawalController.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsRevenueWithdrawalController.swift @@ -6,106 +6,141 @@ import TelegramPresentationData import PresentationDataUtils import AccountContext import PasswordSetupUI -import Markdown -import OwnershipTransferController +import ComponentFlow +import AlertComponent +import AlertInputFieldComponent public func confirmStarsRevenueWithdrawalController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, amount: Int64, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (String) -> Void) -> ViewController { - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + let inputState = AlertInputFieldComponent.ExternalState() + + let doneIsEnabled: Signal = inputState.valueSignal + |> map { value in + return !value.isEmpty + } + + let doneInProgressPromise = ValuePromise(false) + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Monetization_Withdraw_EnterPassword_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.Monetization_Withdraw_EnterPassword_Text)) + ) + )) + + var applyImpl: (() -> Void)? + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + placeholder: strings.Channel_OwnershipTransfer_PasswordPlaceholder, + isSecureTextEntry: true, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) + + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) + } var dismissImpl: (() -> Void)? - var proceedImpl: (() -> Void)? - - let disposable = MetaDisposable() - - let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.Monetization_Withdraw_EnterPassword_Title, text: presentationData.strings.Monetization_Withdraw_EnterPassword_Text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?() - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Monetization_Withdraw_EnterPassword_Done, action: { - proceedImpl?() - })]) - - contentNode.complete = { - proceedImpl?() - } - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in - controller?.theme = AlertControllerTheme(presentationData: presentationData) - contentNode?.theme = presentationData.theme - }) - controller.dismissed = { _ in - presentationDataDisposable.dispose() - disposable.dispose() - } - dismissImpl = { [weak controller, weak contentNode] in - contentNode?.dismissInput() - controller?.dismissAnimated() - } - proceedImpl = { [weak contentNode] in - guard let contentNode = contentNode else { - return - } - contentNode.updateIsChecking(true) - - let signal = context.engine.peers.requestStarsRevenueWithdrawalUrl(peerId: peerId, ton: false, amount: amount, password: contentNode.password) - disposable.set((signal |> deliverOnMainQueue).start(next: { url in + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.Monetization_Withdraw_EnterPassword_Done, type: .default, action: { + applyImpl?() + }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get()) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + applyImpl = { + doneInProgressPromise.set(true) + + let _ = (context.engine.peers.requestStarsRevenueWithdrawalUrl(peerId: peerId, ton: false, amount: amount, password: inputState.value) + |> deliverOnMainQueue).start(next: { url in dismissImpl?() completion(url) - }, error: { [weak contentNode] error in + }, error: { error in var errorTextAndActions: (String, [TextAlertAction])? switch error { - case .invalidPassword: - contentNode?.animateError() - case .limitExceeded: - errorTextAndActions = (presentationData.strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - default: - errorTextAndActions = (presentationData.strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + case .invalidPassword: + inputState.animateError() + case .limitExceeded: + errorTextAndActions = (strings.TwoStepAuth_FloodError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) + default: + errorTextAndActions = (strings.Login_UnknownError, [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})]) } - contentNode?.updateIsChecking(false) - + doneInProgressPromise.set(false) + if let (text, actions) = errorTextAndActions { dismissImpl?() present(textAlertController(context: context, title: nil, text: text, actions: actions), nil) } - })) + }) } - - return controller + dismissImpl = { [weak alertController] in + alertController?.dismiss(completion: nil) + } + return alertController } public func starsRevenueWithdrawalController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, amount: Int64, initialError: RequestStarsRevenueWithdrawalError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (String) -> Void) -> ViewController { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - let theme = AlertControllerTheme(presentationData: presentationData) + let strings = presentationData.strings - var title: NSAttributedString? = NSAttributedString(string: presentationData.strings.OwnershipTransfer_SecurityCheck, font: Font.semibold(presentationData.listsFontSize.itemListBaseFontSize), textColor: theme.primaryColor, paragraphAlignment: .center) - - var text = presentationData.strings.Monetization_Withdraw_SecurityRequirements - let textFontSize = presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0 - - var actions: [TextAlertAction] = [] + var title: String? = strings.OwnershipTransfer_SecurityCheck + var text = strings.Monetization_Withdraw_SecurityRequirements + + var actions: [AlertScreen.Action] = [ + .init(title: strings.Common_OK, type: .default) + ] switch initialError { - case .requestPassword: + case .requestPassword: return confirmStarsRevenueWithdrawalController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, amount: amount, present: present, completion: completion) - case .twoStepAuthTooFresh, .authSessionTooFresh: - text = text + presentationData.strings.Monetization_Withdraw_ComeBackLater - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] - case .twoStepAuthMissing: - actions = [TextAlertAction(type: .genericAction, title: presentationData.strings.OwnershipTransfer_SetupTwoStepAuth, action: { + case .twoStepAuthTooFresh, .authSessionTooFresh: + text = text + strings.Monetization_Withdraw_ComeBackLater + case .twoStepAuthMissing: + actions = [ + .init(title: strings.OwnershipTransfer_SetupTwoStepAuth, type: .default, action: { let controller = SetupTwoStepVerificationController(context: context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in if shouldDismiss { controller.dismiss() } }) present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})] - default: - title = nil - text = presentationData.strings.Login_UnknownError - actions = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})] + }), + .init(title: strings.Common_Cancel) + ] + default: + title = nil + text = strings.Login_UnknownError } - let body = MarkdownAttributeSet(font: Font.regular(textFontSize), textColor: theme.primaryColor) - let bold = MarkdownAttributeSet(font: Font.semibold(textFontSize), textColor: theme.primaryColor) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - return richTextAlertController(context: context, title: title, text: attributedText, actions: actions) + return AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + title: title, + text: text, + actions: actions + ) } diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 17df8810..c8dbc216 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -95,7 +95,7 @@ private final class SheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in @@ -167,7 +167,11 @@ private final class SheetContent: CombinedComponent { maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxStarsAmount, nanos: 0) case .ton: amountTitle = environment.strings.Stars_SellGift_TonAmountTitle + #if DEBUG + minAmount = StarsAmount(value: 48000000000, nanos: 0) + #else minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinTonAmount, nanos: 0) + #endif maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxTonAmount, nanos: 0) } case let .paidMessages(_, minAmountValue, _, _, _): @@ -618,7 +622,14 @@ private final class SheetContent: CombinedComponent { } if state.currency == .stars { if let amount = state.amount, let tonUsdRate = withdrawConfiguration.tonUsdRate, let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate { - state.amount = StarsAmount(value: max(min(convertStarsToTon(amount, tonUsdRate: tonUsdRate, starsUsdRate: usdWithdrawRate), resaleConfiguration.starGiftResaleMaxTonAmount), resaleConfiguration.starGiftResaleMinTonAmount), nanos: 0) + var convertedValue = min(convertStarsToTon(amount, tonUsdRate: tonUsdRate, starsUsdRate: usdWithdrawRate), resaleConfiguration.starGiftResaleMaxTonAmount) + if convertedValue < resaleConfiguration.starGiftResaleMinTonAmount { + convertedValue = resaleConfiguration.starGiftResaleMinTonAmount + if case .starGiftResell = component.mode, let controller = controller() as? StarsWithdrawScreen { + controller.presentMinAmountTooltip(convertedValue, currency: .ton) + } + } + state.amount = StarsAmount(value: convertedValue, nanos: 0) } else { state.amount = StarsAmount(value: 0, nanos: 0) } @@ -1255,6 +1266,8 @@ private final class StarsWithdrawSheetComponent: CombinedComponent { let sheet = Child(SheetComponent<(EnvironmentType)>.self) let animateOut = StoredActionSlot(Action.self) + let sheetExternalState = SheetComponent.ExternalState() + return { context in let environment = context.environment[EnvironmentType.self] @@ -1281,6 +1294,7 @@ private final class StarsWithdrawSheetComponent: CombinedComponent { followContentSizeChanges: false, clipsContent: true, isScrollEnabled: false, + externalState: sheetExternalState, animateOut: animateOut ), environment: { @@ -1313,6 +1327,29 @@ private final class StarsWithdrawSheetComponent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) ) + if let controller = controller(), !controller.automaticallyControlPresentationContextLayout { + var sideInset: CGFloat = 0.0 + var bottomInset: CGFloat = max(environment.safeInsets.bottom, sheetExternalState.contentHeight) + if case .regular = environment.metrics.widthClass { + sideInset = floor((context.availableSize.width - 430.0) / 2.0) - 12.0 + bottomInset = (context.availableSize.height - sheetExternalState.contentHeight) / 2.0 + sheetExternalState.contentHeight + } + + let layout = ContainerViewLayout( + size: context.availableSize, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0), + safeInsets: UIEdgeInsets(top: 0.0, left: max(sideInset, environment.safeInsets.left), bottom: 0.0, right: max(sideInset, environment.safeInsets.right)), + additionalInsets: .zero, + statusBarHeight: environment.statusBarHeight, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ) + controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition) + } + return context.availableSize } } @@ -1357,6 +1394,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { ) self.navigationPresentation = .flatModal + self.automaticallyControlPresentationContextLayout = false } required public init(coder aDecoder: NSCoder) { @@ -1422,10 +1460,8 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { round: false, undoText: nil ), - elevatedLayout: false, - position: .top, action: { _ in return true}) - self.present(resultController, in: .window(.root)) + self.present(resultController, in: .current) if let view = self.node.hostView.findTaggedView(tag: amountTag) as? AmountFieldComponent.View { view.animateError() @@ -1769,8 +1805,15 @@ public final class AmountFieldComponent: Component { guard let component = self.component, let value = component.value else { return } - self.textField.text = "\(value)" - self.placeholderView.view?.isHidden = self.textField.text?.isEmpty ?? false + var text = "" + switch component.currency { + case .stars: + text = "\(value)" + case .ton: + text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), maxDecimalPositions: nil))" + } + self.textField.text = text + self.placeholderView.view?.isHidden = !(self.textField.text ?? "").isEmpty } func update(component: AmountFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { @@ -1790,7 +1833,7 @@ public final class AmountFieldComponent: Component { text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), maxDecimalPositions: nil))" } self.textField.text = text - self.placeholderView.view?.isHidden = text.isEmpty + self.placeholderView.view?.isHidden = !text.isEmpty } else { self.textField.text = "" } diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD index 096451d9..4b984b59 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD +++ b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD @@ -45,6 +45,7 @@ swift_library( "//submodules/GalleryData", "//submodules/SegmentedControlNode", "//submodules/TelegramUIPreferences", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift index bac4438d..50ed55b1 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift @@ -24,22 +24,26 @@ import GalleryData import AnimatedTextComponent import TelegramUIPreferences import SegmentControlComponent +import GlassBackgroundComponent final class DataUsageScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let statsSet: StatsSet let mediaAutoDownloadSettings: MediaAutoDownloadSettings let makeAutodownloadSettingsController: (Bool) -> ViewController init( context: AccountContext, + overNavigationContainer: UIView, statsSet: StatsSet, mediaAutoDownloadSettings: MediaAutoDownloadSettings, makeAutodownloadSettingsController: @escaping (Bool) -> ViewController ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.statsSet = statsSet self.mediaAutoDownloadSettings = mediaAutoDownloadSettings self.makeAutodownloadSettingsController = makeAutodownloadSettingsController @@ -313,12 +317,9 @@ final class DataUsageScreenComponent: Component { private var mediaAutoDownloadSettings: MediaAutoDownloadSettings = .defaultSettings private var mediaAutoDownloadSettingsDisposable: Disposable? - private let navigationBackgroundView: BlurredBackgroundView - private let navigationSeparatorLayer: SimpleLayer - private let navigationSeparatorLayerContainer: SimpleLayer - private let headerView = ComponentView() private let headerOffsetContainer: HeaderContainer + private let headerContentContainer: HeaderContainer private let headerDescriptionView = ComponentView() private var doneLabel: ComponentView? @@ -356,14 +357,7 @@ final class DataUsageScreenComponent: Component { override init(frame: CGRect) { self.headerOffsetContainer = HeaderContainer() - - self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true) - self.navigationBackgroundView.alpha = 0.0 - - self.navigationSeparatorLayer = SimpleLayer() - self.navigationSeparatorLayer.opacity = 1.0 - self.navigationSeparatorLayerContainer = SimpleLayer() - self.navigationSeparatorLayerContainer.opacity = 0.0 + self.headerContentContainer = HeaderContainer() self.scrollContainerView = UIView() @@ -396,12 +390,7 @@ final class DataUsageScreenComponent: Component { self.scrollContainerView.addSubview(self.autoDownloadSettingsContainerView) - self.addSubview(self.navigationBackgroundView) - - self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer) - self.layer.addSublayer(self.navigationSeparatorLayerContainer) - - self.addSubview(self.headerOffsetContainer) + self.headerOffsetContainer.addSubview(self.headerContentContainer) } required init?(coder: NSCoder) { @@ -447,41 +436,19 @@ final class DataUsageScreenComponent: Component { headerOffset = min(headerOffset, minOffset) let animatedTransition = ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)) - let navigationBackgroundAlpha: CGFloat = abs(headerOffset - minOffset) < 4.0 ? 1.0 : 0.0 let navigationButtonAlpha: CGFloat = scrollBounds.minY >= navigationMetrics.navigationHeight ? 0.0 : 1.0 - animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) - animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) - - /*let expansionDistance: CGFloat = 32.0 - var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance - expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) - - transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor)*/ - - /*var offsetFraction: CGFloat = abs(headerOffset - minOffset) / 60.0 - offsetFraction = min(1.0, max(0.0, offsetFraction)) - transition.setScale(view: headerView, scale: 1.0 * offsetFraction + 0.8 * (1.0 - offsetFraction))*/ - transition.setBounds(view: self.headerOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: headerOffset), size: self.headerOffsetContainer.bounds.size)) + let headerContentsAlpha: CGFloat + let offsetFraction = abs(headerOffset - minOffset) / 60.0 + headerContentsAlpha = min(1.0, max(0.0, offsetFraction)) + + transition.setAlpha(view: self.headerContentContainer, alpha: headerContentsAlpha) + if let controller = self.controller?(), let backButtonNode = controller.navigationBar?.backButtonNode { backButtonNode.updateManualAlpha(alpha: navigationButtonAlpha, transition: animatedTransition.containedViewLayoutTransition) - - /*if backButtonNode.alpha != navigationButtonAlpha { - if backButtonNode.isHidden { - backButtonNode.alpha = 0.0 - backButtonNode.isHidden = false - } - animatedTransition.setAlpha(layer: backButtonNode.layer, alpha: navigationButtonAlpha, completion: { [weak backButtonNode] completed in - if let backButtonNode, completed { - if navigationButtonAlpha.isZero { - backButtonNode.isHidden = true - } - } - }) - }*/ } } } @@ -539,17 +506,9 @@ final class DataUsageScreenComponent: Component { self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight) - self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - - let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight)) - self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame) - - let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)) - - transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame) - transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size)) + if self.headerOffsetContainer.superview == nil { + component.overNavigationContainer.addSubview(self.headerOffsetContainer) + } self.backgroundColor = environment.theme.list.blocksBackgroundColor @@ -709,7 +668,7 @@ final class DataUsageScreenComponent: Component { let pieChartFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: pieChartSize) if let pieChartComponentView = self.pieChartView.view { if pieChartComponentView.superview == nil { - self.scrollView.addSubview(pieChartComponentView) + self.headerContentContainer.addSubview(pieChartComponentView) } pieChartTransition.setFrame(view: pieChartComponentView, frame: pieChartFrame) @@ -740,7 +699,7 @@ final class DataUsageScreenComponent: Component { if let doneLabelView = doneLabel.view { var animateIn = false if doneLabelView.superview == nil { - self.scrollView.addSubview(doneLabelView) + self.headerContentContainer.addSubview(doneLabelView) animateIn = true } doneLabelTransition.setFrame(view: doneLabelView, frame: doneLabelFrame) @@ -755,7 +714,7 @@ final class DataUsageScreenComponent: Component { if let doneSupLabelView = doneSupLabel.view { var animateIn = false if doneSupLabelView.superview == nil { - self.scrollView.addSubview(doneSupLabelView) + self.headerContentContainer.addSubview(doneSupLabelView) animateIn = true } doneLabelTransition.setFrame(view: doneSupLabelView, frame: doneSupLabelFrame) @@ -796,7 +755,7 @@ final class DataUsageScreenComponent: Component { let headerViewFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - headerViewSize.width) / 2.0), y: contentHeight), size: headerViewSize) if let headerComponentView = self.headerView.view { if headerComponentView.superview == nil { - self.scrollContainerView.addSubview(headerComponentView) + self.headerOffsetContainer.addSubview(headerComponentView) } transition.setPosition(view: headerComponentView, position: headerViewFrame.center) headerComponentView.bounds = CGRect(origin: CGPoint(), size: headerViewFrame.size) @@ -838,7 +797,7 @@ final class DataUsageScreenComponent: Component { let headerDescriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - headerDescriptionSize.width) / 2.0), y: contentHeight), size: headerDescriptionSize) if let headerDescriptionComponentView = self.headerDescriptionView.view { if headerDescriptionComponentView.superview == nil { - self.scrollContainerView.addSubview(headerDescriptionComponentView) + self.headerContentContainer.addSubview(headerDescriptionComponentView) } transition.setPosition(view: headerDescriptionComponentView, position: headerDescriptionFrame.center) headerDescriptionComponentView.bounds = CGRect(origin: CGPoint(), size: headerDescriptionFrame.size) @@ -892,7 +851,7 @@ final class DataUsageScreenComponent: Component { ) if let chartTotalLabelView = self.chartTotalLabel.view { if chartTotalLabelView.superview == nil { - self.scrollContainerView.addSubview(chartTotalLabelView) + self.headerContentContainer.addSubview(chartTotalLabelView) } let chartAreaHeight: CGFloat @@ -926,7 +885,7 @@ final class DataUsageScreenComponent: Component { self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(value: .modeChanged))) })), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + containerSize: CGSize(width: availableSize.width - (environment.safeInsets.left + 16.0 + 44.0 + 10.0) * 2.0, height: 100.0) ) let segmentedControlFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - segmentedSize.width) * 0.5), y: contentHeight), size: segmentedSize) if let segmentedControlComponentView = self.segmentedControlView.view { @@ -1252,6 +1211,8 @@ final class DataUsageScreenComponent: Component { public final class DataUsageScreen: ViewControllerComponentContainer { private let context: AccountContext + private let overNavigationContainer: UIView + private let readyValue = Promise() override public var ready: Promise { return self.readyValue @@ -1260,8 +1221,14 @@ public final class DataUsageScreen: ViewControllerComponentContainer { public init(context: AccountContext, stats: NetworkUsageStats, mediaAutoDownloadSettings: MediaAutoDownloadSettings, makeAutodownloadSettingsController: @escaping (Bool) -> ViewController) { self.context = context + self.overNavigationContainer = SparseContainerView() + //let componentReady = Promise() - super.init(context: context, component: DataUsageScreenComponent(context: context, statsSet: DataUsageScreenComponent.StatsSet(stats: stats), mediaAutoDownloadSettings: mediaAutoDownloadSettings, makeAutodownloadSettingsController: makeAutodownloadSettingsController), navigationBarAppearance: .transparent) + super.init(context: context, component: DataUsageScreenComponent(context: context, overNavigationContainer: self.overNavigationContainer, statsSet: DataUsageScreenComponent.StatsSet(stats: stats), mediaAutoDownloadSettings: mediaAutoDownloadSettings, makeAutodownloadSettingsController: makeAutodownloadSettingsController), navigationBarAppearance: .default) + + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } //self.readyValue.set(componentReady.get() |> timeout(0.3, queue: .mainQueue(), alternate: .single(true))) self.readyValue.set(.single(true)) diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index b6629a6d..16098f33 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -24,6 +24,7 @@ import TelegramStringFormatting import GalleryData import AnimatedTextComponent import BottomButtonPanelComponent +import GlassBackgroundComponent #if DEBUG import os.signpost @@ -116,17 +117,20 @@ final class StorageUsageScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let overNavigationContainer: UIView let makeStorageUsageExceptionsScreen: (CacheStorageSettings.PeerStorageCategory) -> ViewController? let peer: EnginePeer? let ready: Promise init( context: AccountContext, + overNavigationContainer: UIView, makeStorageUsageExceptionsScreen: @escaping (CacheStorageSettings.PeerStorageCategory) -> ViewController?, peer: EnginePeer?, ready: Promise ) { self.context = context + self.overNavigationContainer = overNavigationContainer self.makeStorageUsageExceptionsScreen = makeStorageUsageExceptionsScreen self.peer = peer self.ready = ready @@ -740,9 +744,7 @@ final class StorageUsageScreenComponent: Component { private var isOtherCategoryExpanded: Bool = false - private let navigationBackgroundView: BlurredBackgroundView - private let navigationSeparatorLayer: SimpleLayer - private let navigationSeparatorLayerContainer: SimpleLayer + private let navigationRightButtonsBackground: GlassBackgroundView private let navigationEditButton = ComponentView() private let navigationDoneButton = ComponentView() @@ -799,16 +801,7 @@ final class StorageUsageScreenComponent: Component { private var keepScreenActiveDisposable: Disposable? override init(frame: CGRect) { - self.headerOffsetContainer = UIView() - self.headerOffsetContainer.isUserInteractionEnabled = false - - self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true) - self.navigationBackgroundView.alpha = 0.0 - - self.navigationSeparatorLayer = SimpleLayer() - self.navigationSeparatorLayer.opacity = 0.0 - self.navigationSeparatorLayerContainer = SimpleLayer() - self.navigationSeparatorLayerContainer.opacity = 0.0 + self.headerOffsetContainer = SparseContainerView() self.scrollContainerView = UIView() @@ -821,6 +814,8 @@ final class StorageUsageScreenComponent: Component { self.headerProgressBackgroundLayer = SimpleLayer() self.headerProgressForegroundLayer = SimpleLayer() + self.navigationRightButtonsBackground = GlassBackgroundView() + super.init(frame: frame) self.scrollView.delaysContentTouches = true @@ -846,13 +841,6 @@ final class StorageUsageScreenComponent: Component { self.scrollView.layer.addSublayer(self.headerProgressBackgroundLayer) self.scrollView.layer.addSublayer(self.headerProgressForegroundLayer) - - self.addSubview(self.navigationBackgroundView) - - self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer) - self.layer.addSublayer(self.navigationSeparatorLayerContainer) - - self.addSubview(self.headerOffsetContainer) } required init?(coder: NSCoder) { @@ -946,9 +934,6 @@ final class StorageUsageScreenComponent: Component { let animatedTransition = ComponentTransition(animation: .curve(duration: 0.18, curve: .easeInOut)) let navigationBackgroundAlpha: CGFloat = abs(headerOffset - minOffset) < 4.0 ? 1.0 : 0.0 - animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha) - animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha) - var buttonsMasterAlpha: CGFloat = 1.0 if let component = self.component, component.peer != nil { buttonsMasterAlpha = 0.0 @@ -960,20 +945,12 @@ final class StorageUsageScreenComponent: Component { } } - let isSelectingPeers = self.aggregatedData?.isSelectingPeers ?? false - - if let navigationEditButtonView = self.navigationEditButton.view { - animatedTransition.setAlpha(view: navigationEditButtonView, alpha: (isSelectingPeers ? 0.0 : 1.0) * buttonsMasterAlpha * navigationBackgroundAlpha) - } - if let navigationDoneButtonView = self.navigationDoneButton.view { - animatedTransition.setAlpha(view: navigationDoneButtonView, alpha: (isSelectingPeers ? 1.0 : 0.0) * buttonsMasterAlpha * navigationBackgroundAlpha) - } + animatedTransition.setAlpha(view: self.navigationRightButtonsBackground, alpha: buttonsMasterAlpha * navigationBackgroundAlpha) let expansionDistance: CGFloat = 32.0 var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor)) - transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor) if let panelContainerView = self.panelContainer.view as? StorageUsagePanelContainerComponent.View { panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition) } @@ -983,6 +960,17 @@ final class StorageUsageScreenComponent: Component { transition.setScale(view: headerView, scale: 1.0 * offsetFraction + 0.8 * (1.0 - offsetFraction)) transition.setBounds(view: self.headerOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: headerOffset), size: self.headerOffsetContainer.bounds.size)) + + let headerContentsAlpha = offsetFraction + if let chartAvatarNode = self.chartAvatarNode { + transition.setAlpha(view: chartAvatarNode.view, alpha: headerContentsAlpha) + } + if let pieChartComponentView = self.pieChartView.view { + transition.setAlpha(view: pieChartComponentView, alpha: headerContentsAlpha) + } + if let chartTotalLabelView = self.chartTotalLabel.view { + transition.setAlpha(view: chartTotalLabelView, alpha: headerContentsAlpha) + } } let _ = self.panelContainer.updateEnvironment( @@ -1109,6 +1097,13 @@ final class StorageUsageScreenComponent: Component { self.reloadStats(firstTime: true, completion: {}) } + if self.headerOffsetContainer.superview == nil { + component.overNavigationContainer.addSubview(self.headerOffsetContainer) + } + if self.navigationRightButtonsBackground.superview == nil { + component.overNavigationContainer.addSubview(self.navigationRightButtonsBackground) + } + var wasLockedAtPanels = false if let panelContainerView = self.panelContainer.view, let navigationMetrics = self.navigationMetrics { if self.scrollView.bounds.minY > 0.0 && abs(self.scrollView.bounds.minY - (panelContainerView.frame.minY - navigationMetrics.navigationHeight)) <= UIScreenPixel { @@ -1147,22 +1142,11 @@ final class StorageUsageScreenComponent: Component { self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight) - self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - - let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight)) - self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame) - - let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)) - - transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame) - transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size)) - let navigationEditButtonSize = self.navigationEditButton.update( transition: transition, component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Edit, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)), + content: AnyComponent(Text(text: environment.strings.Common_Edit, font: Font.regular(17.0), color: environment.theme.chat.inputPanel.panelControlColor)), + contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 6.0), action: { [weak self] in guard let self else { return @@ -1172,21 +1156,22 @@ final class StorageUsageScreenComponent: Component { self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))) } } - ).minSize(CGSize(width: 16.0, height: environment.navigationHeight - environment.statusBarHeight))), + ).minSize(CGSize(width: 44.0, height: 44.0))), environment: {}, - containerSize: CGSize(width: 150.0, height: environment.navigationHeight - environment.statusBarHeight) + containerSize: CGSize(width: 150.0, height: 44.0) ) if let navigationEditButtonView = self.navigationEditButton.view { if navigationEditButtonView.superview == nil { - self.addSubview(navigationEditButtonView) + self.navigationRightButtonsBackground.contentView.addSubview(navigationEditButtonView) } - transition.setFrame(view: navigationEditButtonView, frame: CGRect(origin: CGPoint(x: availableSize.width - 12.0 - environment.safeInsets.right - navigationEditButtonSize.width, y: environment.statusBarHeight), size: navigationEditButtonSize)) + transition.setFrame(view: navigationEditButtonView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: navigationEditButtonSize)) } let navigationDoneButtonSize = self.navigationDoneButton.update( transition: transition, component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Done, font: Font.semibold(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)), + content: AnyComponent(Text(text: environment.strings.Common_Done, font: Font.semibold(17.0), color: environment.theme.chat.inputPanel.panelControlColor)), + contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 6.0), action: { [weak self] in guard let self, let aggregatedData = self.aggregatedData else { return @@ -1195,19 +1180,39 @@ final class StorageUsageScreenComponent: Component { aggregatedData.clearPeerSelection() self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))) } - ).minSize(CGSize(width: 16.0, height: environment.navigationHeight - environment.statusBarHeight))), + ).minSize(CGSize(width: 44.0, height: 44.0))), environment: {}, - containerSize: CGSize(width: 150.0, height: environment.navigationHeight - environment.statusBarHeight) + containerSize: CGSize(width: 150.0, height: 44.0) ) if let navigationDoneButtonView = self.navigationDoneButton.view { if navigationDoneButtonView.superview == nil { - self.addSubview(navigationDoneButtonView) + self.navigationRightButtonsBackground.contentView.addSubview(navigationDoneButtonView) } - transition.setFrame(view: navigationDoneButtonView, frame: CGRect(origin: CGPoint(x: availableSize.width - 12.0 - environment.safeInsets.right - navigationDoneButtonSize.width, y: environment.statusBarHeight), size: navigationDoneButtonSize)) + transition.setFrame(view: navigationDoneButtonView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: navigationDoneButtonSize)) } let navigationRightButtonMaxWidth: CGFloat = max(navigationEditButtonSize.width, navigationDoneButtonSize.width) + var rightButtonsWidth: CGFloat = 0.0 + let isSelectingPeers = self.aggregatedData?.isSelectingPeers ?? false + if let navigationEditButtonView = self.navigationEditButton.view { + if !isSelectingPeers { + rightButtonsWidth += navigationEditButtonSize.width + } + transition.setAlpha(view: navigationEditButtonView, alpha: isSelectingPeers ? 0.0 : 1.0) + } + if let navigationDoneButtonView = self.navigationDoneButton.view { + if isSelectingPeers { + rightButtonsWidth += navigationDoneButtonSize.width + } + transition.setAlpha(view: navigationDoneButtonView, alpha: isSelectingPeers ? 1.0 : 0.0) + } + + let navigationRightButtonsBackgroundSize = CGSize(width: max(44.0, rightButtonsWidth), height: 44.0) + self.navigationRightButtonsBackground.update(size: navigationRightButtonsBackgroundSize, cornerRadius: 44.0 * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: environment.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + let navigationRightButtonsBackgroundFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - navigationRightButtonsBackgroundSize.width, y: environment.statusBarHeight + 2.0 + floor((environment.navigationHeight - environment.statusBarHeight - 44.0) * 0.5)), size: navigationRightButtonsBackgroundSize) + transition.setFrame(view: self.navigationRightButtonsBackground, frame: navigationRightButtonsBackgroundFrame) + self.backgroundColor = environment.theme.list.blocksBackgroundColor var contentHeight: CGFloat = 0.0 @@ -1436,7 +1441,7 @@ final class StorageUsageScreenComponent: Component { let pieChartFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: pieChartSize) if let pieChartComponentView = self.pieChartView.view { if pieChartComponentView.superview == nil { - self.scrollView.addSubview(pieChartComponentView) + self.headerOffsetContainer.addSubview(pieChartComponentView) } pieChartTransition.setFrame(view: pieChartComponentView, frame: pieChartFrame) @@ -1637,7 +1642,7 @@ final class StorageUsageScreenComponent: Component { } else { chartAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 17.0)) self.chartAvatarNode = chartAvatarNode - self.scrollContainerView.addSubview(chartAvatarNode.view) + self.headerOffsetContainer.addSubview(chartAvatarNode.view) chartAvatarNode.frame = avatarFrame if peer.id == component.context.account.peerId { @@ -1681,7 +1686,7 @@ final class StorageUsageScreenComponent: Component { ) if let chartTotalLabelView = self.chartTotalLabel.view { if chartTotalLabelView.superview == nil { - self.scrollContainerView.addSubview(chartTotalLabelView) + self.headerOffsetContainer.addSubview(chartTotalLabelView) } let totalLabelFrame = CGRect(origin: CGPoint(x: pieChartFrame.minX + floor((pieChartFrame.width - chartTotalLabelSize.width) / 2.0), y: pieChartFrame.minY + floor((pieChartFrame.height - chartTotalLabelSize.height) / 2.0)), size: chartTotalLabelSize) transition.setFrame(view: chartTotalLabelView, frame: totalLabelFrame) @@ -3303,6 +3308,8 @@ final class StorageUsageScreenComponent: Component { public final class StorageUsageScreen: ViewControllerComponentContainer { private let context: AccountContext + private let overNavigationContainer: UIView + private let readyValue = Promise() override public var ready: Promise { return self.readyValue @@ -3313,13 +3320,19 @@ public final class StorageUsageScreen: ViewControllerComponentContainer { public init(context: AccountContext, makeStorageUsageExceptionsScreen: @escaping (CacheStorageSettings.PeerStorageCategory) -> ViewController?, peer: EnginePeer? = nil) { self.context = context + self.overNavigationContainer = SparseContainerView() + let componentReady = Promise() - super.init(context: context, component: StorageUsageScreenComponent(context: context, makeStorageUsageExceptionsScreen: makeStorageUsageExceptionsScreen, peer: peer, ready: componentReady), navigationBarAppearance: .transparent) + super.init(context: context, component: StorageUsageScreenComponent(context: context, overNavigationContainer: self.overNavigationContainer, makeStorageUsageExceptionsScreen: makeStorageUsageExceptionsScreen, peer: peer, ready: componentReady), navigationBarAppearance: .default) if peer != nil { self.navigationPresentation = .modal } + if let navigationBar = self.navigationBar { + navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + } + self.readyValue.set(componentReady.get() |> timeout(0.3, queue: .mainQueue(), alternate: .single(true))) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 54ef6207..797719a4 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -114,6 +114,7 @@ swift_library( "//submodules/Components/HierarchyTrackingLayer", "//submodules/Utils/LokiRng", "//submodules/TelegramUI/Components/PeerNameTextComponent", + "//submodules/TelegramUI/Components/AlertComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 75fe51d2..02d96ffa 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -357,7 +357,8 @@ final class StoryItemContentComponent: Component { useLargeThumbnail: false, autoFetchFullSizeThumbnail: false, tempFilePath: nil, - captureProtected: component.item.isForwardingDisabled, + // MISC: Bypass story screenshot protection + captureProtected: MiscSettingsManager.shared.shouldBypassScreenshotProtection ? false : component.item.isForwardingDisabled, hintDimensions: file.dimensions?.cgSize, storeAfterDownload: nil, displayImage: false, @@ -777,7 +778,8 @@ final class StoryItemContentComponent: Component { availableReactions: component.availableReactions, entityFiles: component.entityFiles, size: size, - isCaptureProtected: component.item.isForwardingDisabled, + // MISC: Bypass story screenshot protection + isCaptureProtected: MiscSettingsManager.shared.shouldBypassScreenshotProtection ? false : component.item.isForwardingDisabled, attemptSynchronous: synchronousLoad, isActive: self.progressMode.mode == .play, transition: transition diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift index a5dcac69..fff8de95 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift @@ -67,7 +67,10 @@ final class StoryItemImageView: UIView { } func update(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, storyId: Int32, media: EngineMedia, size: CGSize, isCaptureProtected: Bool, attemptSynchronous: Bool, transition: ComponentTransition) { - self.backgroundColor = isCaptureProtected ? UIColor(rgb: 0x181818) : nil + // MISC: Bypass story screenshot protection + let effectiveCaptureProtected = MiscSettingsManager.shared.shouldBypassScreenshotProtection ? false : isCaptureProtected + + self.backgroundColor = effectiveCaptureProtected ? UIColor(rgb: 0x181818) : nil var dimensions: CGSize? @@ -87,11 +90,11 @@ final class StoryItemImageView: UIView { if attemptSynchronous, let path = context.account.postbox.mediaBox.completedResourcePath(id: representation.resource.id, pathExtension: nil) { if #available(iOS 15.0, *) { if let image = UIImage(contentsOfFile: path)?.preparingForDisplay() { - self.updateImage(image: image, isCaptureProtected: isCaptureProtected) + self.updateImage(image: image, isCaptureProtected: effectiveCaptureProtected) } } else { if let image = UIImage(contentsOfFile: path)?.precomposed() { - self.updateImage(image: image, isCaptureProtected: isCaptureProtected) + self.updateImage(image: image, isCaptureProtected: effectiveCaptureProtected) } } self.isContentLoaded = true @@ -99,7 +102,7 @@ final class StoryItemImageView: UIView { } else { if let thumbnailData = image.immediateThumbnailData.flatMap(decodeTinyThumbnail), let thumbnailImage = UIImage(data: thumbnailData) { if let image = blurredImage(thumbnailImage, radius: 10.0, iterations: 3) { - self.updateImage(image: image, isCaptureProtected: isCaptureProtected) + self.updateImage(image: image, isCaptureProtected: effectiveCaptureProtected) } } @@ -131,7 +134,7 @@ final class StoryItemImageView: UIView { return } if let image { - self.updateImage(image: image, isCaptureProtected: isCaptureProtected) + self.updateImage(image: image, isCaptureProtected: effectiveCaptureProtected) self.isContentLoaded = true self.didLoadContents?() } @@ -148,11 +151,11 @@ final class StoryItemImageView: UIView { if attemptSynchronous, FileManager.default.fileExists(atPath: cachedPath) { if #available(iOS 15.0, *) { if let image = UIImage(contentsOfFile: cachedPath)?.preparingForDisplay() { - self.updateImage(image: image, isCaptureProtected: isCaptureProtected) + self.updateImage(image: image, isCaptureProtected: effectiveCaptureProtected) } } else { if let image = UIImage(contentsOfFile: cachedPath)?.precomposed() { - self.updateImage(image: image, isCaptureProtected: isCaptureProtected) + self.updateImage(image: image, isCaptureProtected: effectiveCaptureProtected) } } self.isContentLoaded = true @@ -160,7 +163,7 @@ final class StoryItemImageView: UIView { } else { if let thumbnailData = file.immediateThumbnailData.flatMap(decodeTinyThumbnail), let thumbnailImage = UIImage(data: thumbnailData) { if let image = blurredImage(thumbnailImage, radius: 10.0, iterations: 3) { - self.updateImage(image: image, isCaptureProtected: isCaptureProtected) + self.updateImage(image: image, isCaptureProtected: effectiveCaptureProtected) } } @@ -213,7 +216,7 @@ final class StoryItemImageView: UIView { return } if let image { - self.updateImage(image: image, isCaptureProtected: isCaptureProtected) + self.updateImage(image: image, isCaptureProtected: effectiveCaptureProtected) self.isContentLoaded = true self.didLoadContents?() } @@ -225,7 +228,7 @@ final class StoryItemImageView: UIView { if isMediaUpdated, let thumbnailData = peer.smallProfileImage?.immediateThumbnailData { if let thumbnailData = decodeTinyThumbnail(data: thumbnailData), let thumbnailImage = UIImage(data: thumbnailData) { if let image = blurredImage(thumbnailImage, radius: 10.0, iterations: 3) { - self.updateImage(image: image, isCaptureProtected: isCaptureProtected) + self.updateImage(image: image, isCaptureProtected: effectiveCaptureProtected) } } } @@ -246,7 +249,7 @@ final class StoryItemImageView: UIView { } } - if isCaptureProtected { + if effectiveCaptureProtected { let captureProtectedInfo: ComponentView var captureProtectedInfoTransition = transition if let current = self.captureProtectedInfo { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 57c21f08..a037a504 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -7575,7 +7575,7 @@ public final class StoryItemSetContainerComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let promptController = promptController( - sharedContext: component.context.sharedContext, + context: component.context, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)), text: presentationData.strings.Stories_CreateAlbum_Title, titleFont: .bold, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index e5bd1753..619f13e4 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -54,6 +54,7 @@ import ChatSendStarsScreen import AnimatedTextComponent import ChatSendAsContextMenu import ShareWithPeersScreen +import AlertComponent private var ObjCKey_DeinitWatcher: Int? @@ -359,7 +360,6 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, - importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, @@ -637,17 +637,16 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let theme = component.theme let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) - let alertController = textAlertController( - context: component.context, - updatedPresentationData: updatedPresentationData, + let alertController = AlertScreen( title: component.strings.Story_AlertStealthModeActiveTitle, text: component.strings.Story_AlertStealthModeActiveText, actions: [ - TextAlertAction(type: .defaultAction, title: component.strings.Common_Cancel, action: {}), - TextAlertAction(type: .genericAction, title: component.strings.Story_AlertStealthModeActiveAction, action: { + .init(title: component.strings.Common_Cancel, type: .default), + .init(title: component.strings.Story_AlertStealthModeActiveAction, type: .generic, action: { action() }) - ] + ], + updatedPresentationData: updatedPresentationData ) alertController.dismissed = { [weak self, weak view] _ in guard let self, let view else { @@ -761,7 +760,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let absMaxEmojiCount = paramSets.paramSets.max(by: { $0.minStars < $1.minStars })?.maxEmojiCount ?? 10 if emojiCount > absMaxEmojiCount { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - view.component?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.LiveStream_ErrorMaxAllowedEmoji_Text(Int32(absMaxEmojiCount)), actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + view.component?.controller()?.present(textAlertController(context: component.context, updatedPresentationData: (presentationData, .single(presentationData)), title: nil, text: presentationData.strings.LiveStream_ErrorMaxAllowedEmoji_Text(Int32(absMaxEmojiCount)), actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return } @@ -2410,18 +2409,16 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { component.controller()?.push(controller) } }, presentSelectionLimitExceeded: { [weak view] in - guard let view else { + guard let view, let component = view.component else { return } - let text: String if slowModeEnabled { text = presentationData.strings.Chat_SlowmodeAttachmentLimitReached } else { text = presentationData.strings.Chat_AttachmentLimitReached } - - view.component?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + component.controller()?.present(textAlertController(context: component.context, updatedPresentationData: (presentationData, .single(presentationData)), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, presentSchedulePicker: { [weak view] media, done in if let strongSelf = self, let view { strongSelf.presentScheduleTimePicker(view: view, peer: peer, style: media ? .media : .default, completion: { time, repeatPeriod in diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 544deae7..6512a4d5 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -761,7 +761,7 @@ public final class StoryPeerListComponent: Component { let collapsedItemWidth: CGFloat = 24.0 let collapsedItemDistance: CGFloat = 14.0 - let collapsedItemOffsetY: CGFloat = -54.0 + let collapsedItemOffsetY: CGFloat = -66.0 let titleContentSpacing: CGFloat = 8.0 let collapsedItemCount: CGFloat = CGFloat(min(self.sortedItems.count - collapseStartIndex, 3)) @@ -1409,7 +1409,7 @@ public final class StoryPeerListComponent: Component { } if let titleIndicatorSize, let titleIndicatorView = self.titleIndicatorView?.view { - let titleIndicatorFrame = CGRect(origin: CGPoint(x: titleContentOffset - titleIndicatorSize.width - 9.0, y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleIndicatorSize.height) * 0.5)), size: titleIndicatorSize) + let titleIndicatorFrame = CGRect(origin: CGPoint(x: titleContentOffset - titleIndicatorSize.width - 9.0, y: collapsedItemOffsetY + 14.0 + floor((56.0 - titleIndicatorSize.height) * 0.5)), size: titleIndicatorSize) if titleIndicatorView.superview == nil { self.addSubview(titleIndicatorView) } @@ -1427,7 +1427,7 @@ public final class StoryPeerListComponent: Component { titleIndicatorView.alpha = indicatorAlpha } - let titleFrame = CGRect(origin: CGPoint(x: titleContentOffset + titleLockOffset, y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleSize.height) * 0.5)), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: titleContentOffset + titleLockOffset, y: collapsedItemOffsetY + 14.0 + floor((56.0 - titleSize.height) * 0.5)), size: titleSize) if let image = self.titleView.image { self.titleView.center = CGPoint(x: titleFrame.minX, y: titleFrame.midY) self.titleView.bounds = CGRect(origin: CGPoint(), size: image.size) @@ -1494,7 +1494,7 @@ public final class StoryPeerListComponent: Component { if let titleIconSize, let titleIconView = self.titleIconView?.view { titleContentOffset += titleIconSpacing - let titleIconFrame = CGRect(origin: CGPoint(x: titleContentOffset - 3.0 + titleIconSpacing + (collapsedState.titleWidth - (titleIconSpacing + titleIconSize.width)) * (1.0 - collapsedState.activityFraction), y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleIconSize.height) * 0.5)), size: titleIconSize) + let titleIconFrame = CGRect(origin: CGPoint(x: titleContentOffset - 3.0 + titleIconSpacing + (collapsedState.titleWidth - (titleIconSpacing + titleIconSize.width)) * (1.0 - collapsedState.activityFraction), y: collapsedItemOffsetY + 14.0 + floor((56.0 - titleIconSize.height) * 0.5)), size: titleIconSize) if titleIconView.superview == nil { self.addSubview(titleIconView) @@ -1729,7 +1729,7 @@ public final class StoryPeerListComponent: Component { let itemLayout = ItemLayout( containerSize: availableSize, - containerInsets: UIEdgeInsets(top: 4.0, left: component.sideInset - 4.0, bottom: 0.0, right: component.sideInset - 4.0), + containerInsets: UIEdgeInsets(top: 16.0, left: component.sideInset - 4.0, bottom: 0.0, right: component.sideInset - 4.0), itemSize: CGSize(width: 60.0, height: 77.0), itemSpacing: 14.0, itemCount: self.sortedItems.count diff --git a/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/Sources/StoryStealthModeSheetScreen.swift b/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/Sources/StoryStealthModeSheetScreen.swift index cfb2959f..d7ab2478 100644 --- a/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/Sources/StoryStealthModeSheetScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/Sources/StoryStealthModeSheetScreen.swift @@ -227,7 +227,7 @@ private final class StoryStealthModeSheetContentComponent: Component { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: environment.theme.chat.inputPanel.panelControlColor ) )), action: { _ in diff --git a/submodules/TelegramUI/Components/SuggestedPostApproveAlert/Sources/SuggestedPostApproveAlert.swift b/submodules/TelegramUI/Components/SuggestedPostApproveAlert/Sources/SuggestedPostApproveAlert.swift index 8a39ab81..97a677e5 100644 --- a/submodules/TelegramUI/Components/SuggestedPostApproveAlert/Sources/SuggestedPostApproveAlert.swift +++ b/submodules/TelegramUI/Components/SuggestedPostApproveAlert/Sources/SuggestedPostApproveAlert.swift @@ -410,7 +410,18 @@ private final class SuggestedPostAlertImpl: AlertController { } } -public func SuggestedPostApproveAlert(presentationData: PresentationData, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false, dismissOnOutsideTap: Bool = true, linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil, toastText: String?) -> AlertController { +public func SuggestedPostApproveAlert( + presentationData: PresentationData, + title: String?, + text: String, + actions: [TextAlertAction], + actionLayout: TextAlertContentActionLayout = .horizontal, + allowInputInset: Bool = true, + parseMarkdown: Bool = false, + dismissOnOutsideTap: Bool = true, + linkAction : (([NSAttributedString.Key: Any], Int) -> Void)? = nil, + toastText: String? +) -> AlertController { let theme = AlertControllerTheme(presentationData: presentationData) var dismissImpl: (() -> Void)? diff --git a/submodules/TelegramUI/Components/TabBarComponent/BUILD b/submodules/TelegramUI/Components/TabBarComponent/BUILD index bf1656c7..ad24d1f7 100644 --- a/submodules/TelegramUI/Components/TabBarComponent/BUILD +++ b/submodules/TelegramUI/Components/TabBarComponent/BUILD @@ -22,6 +22,8 @@ swift_library( "//submodules/UIKitRuntimeUtils", "//submodules/TelegramUI/Components/LiquidLens", "//submodules/AppBundle", + "//submodules/SearchBarNode", + "//submodules/TelegramUI/Components/TabSelectionRecognizer", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift index 585e15ed..9d97455e 100644 --- a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift +++ b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift @@ -12,92 +12,236 @@ import BundleIconComponent import TextBadgeComponent import LiquidLens import AppBundle +import SearchBarNode +import TabSelectionRecognizer -private final class TabSelectionRecognizer: UIGestureRecognizer { - private var initialLocation: CGPoint? - private var currentLocation: CGPoint? - - override init(target: Any?, action: Selector?) { - super.init(target: target, action: action) - - self.delaysTouchesBegan = false - self.delaysTouchesEnded = false - } - - override func reset() { - super.reset() - - self.initialLocation = nil - } - - override func touchesBegan(_ touches: Set, with event: UIEvent) { - super.touchesBegan(touches, with: event) - - if self.initialLocation == nil { - self.initialLocation = touches.first?.location(in: self.view) - } - self.currentLocation = self.initialLocation - - self.state = .began - } - - override func touchesEnded(_ touches: Set, with event: UIEvent) { - super.touchesEnded(touches, with: event) - - self.state = .ended - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent) { - super.touchesCancelled(touches, with: event) - - self.state = .cancelled - } - - override func touchesMoved(_ touches: Set, with event: UIEvent) { - super.touchesMoved(touches, with: event) - - self.currentLocation = touches.first?.location(in: self.view) - - self.state = .changed - } - - func translation(in: UIView?) -> CGPoint { - if let initialLocation = self.initialLocation, let currentLocation = self.currentLocation { - return CGPoint(x: currentLocation.x - initialLocation.x, y: currentLocation.y - initialLocation.y) - } - return CGPoint() - } -} +public final class NavigationSearchView: UIView { + private struct Params: Equatable { + let size: CGSize + let theme: PresentationTheme + let strings: PresentationStrings + let isActive: Bool + + init(size: CGSize, theme: PresentationTheme, strings: PresentationStrings, isActive: Bool) { + self.size = size + self.theme = theme + self.strings = strings + self.isActive = isActive + } + + static func ==(lhs: Params, rhs: Params) -> Bool { + if lhs.size != rhs.size { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.isActive != rhs.isActive { + return false + } + return true + } + } + + private let action: () -> Void + private let closeAction: () -> Void -public final class TabBarSearchView: UIView { private let backgroundView: GlassBackgroundView - private let iconView: GlassBackgroundView.ContentImageView + private let iconView: UIImageView + private(set) var searchBarNode: SearchBarNode? - override public init(frame: CGRect) { - self.backgroundView = GlassBackgroundView() - self.iconView = GlassBackgroundView.ContentImageView() + private var close: (background: GlassBackgroundView, icon: UIImageView)? - super.init(frame: frame) + private var params: Params? + + public init(action: @escaping () -> Void, closeAction: @escaping () -> Void) { + self.action = action + self.closeAction = closeAction + + self.backgroundView = GlassBackgroundView() + self.backgroundView.contentView.clipsToBounds = true + self.iconView = UIImageView() + + super.init(frame: CGRect()) self.addSubview(self.backgroundView) self.backgroundView.contentView.addSubview(self.iconView) + + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))) + } + + @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.action() + } + } + + @objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.closeAction() + } } required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - public func update(size: CGSize, isDark: Bool, tintColor: GlassBackgroundView.TintColor, iconColor: UIColor, transition: ComponentTransition) { - transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) - self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: isDark, tintColor: tintColor, transition: transition) + public func update(size: CGSize, theme: PresentationTheme, strings: PresentationStrings, isActive: Bool, transition: ComponentTransition) { + let params = Params(size: size, theme: theme, strings: strings, isActive: isActive) + if self.params == params { + return + } + self.params = params + self.update(params: params, transition: transition) + } + + private func update(params: Params, transition: ComponentTransition) { + let backgroundSize: CGSize + if params.isActive { + backgroundSize = CGSize(width: params.size.width - 48.0 - 8.0, height: params.size.height) + } else { + backgroundSize = CGSize(width: params.size.width, height: params.size.height) + } + + let previousBackgroundFrame = self.backgroundView.frame + + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: backgroundSize)) + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25) + + self.backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: params.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: params.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) if self.iconView.image == nil { self.iconView.image = UIImage(bundleImageName: "Navigation/Search")?.withRenderingMode(.alwaysTemplate) } - self.iconView.tintColor = iconColor + transition.setTintColor(view: self.iconView, color: params.isActive ? params.theme.rootController.navigationSearchBar.inputIconColor : params.theme.chat.inputPanel.panelControlColor) if let image = self.iconView.image { - transition.setFrame(view: self.iconView, frame: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size)) + let imageSize: CGSize + let iconFrame: CGRect + if params.isActive { + let iconFraction: CGFloat = 0.8 + imageSize = CGSize(width: image.size.width * iconFraction, height: image.size.height * iconFraction) + iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floor((params.size.height - imageSize.height) * 0.5)), size: imageSize) + } else { + iconFrame = CGRect(origin: CGPoint(x: floor((backgroundSize.width - image.size.width) * 0.5), y: floor((params.size.height - image.size.height) * 0.5)), size: image.size) + } + transition.setPosition(view: self.iconView, position: iconFrame.center) + transition.setBounds(view: self.iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + } + + if params.isActive { + let searchBarNode: SearchBarNode + var searchBarNodeTransition = transition + if let current = self.searchBarNode { + searchBarNode = current + } else { + searchBarNodeTransition = searchBarNodeTransition.withAnimation(.none) + searchBarNode = SearchBarNode( + theme: SearchBarNodeTheme( + background: .clear, + separator: .clear, + inputFill: .clear, + primaryText: params.theme.chat.inputPanel.panelControlColor, + placeholder: params.theme.chat.inputPanel.inputPlaceholderColor, + inputIcon: params.theme.chat.inputPanel.inputControlColor, + inputClear: params.theme.chat.inputPanel.panelControlColor, + accent: params.theme.chat.inputPanel.panelControlAccentColor, + keyboard: params.theme.rootController.keyboardColor + ), + presentationTheme: params.theme, + strings: params.strings, + fieldStyle: .inlineNavigation, + icon: .loupe, + forceSeparator: false, + displayBackground: false, + cancelText: nil + ) + searchBarNode.placeholderString = NSAttributedString(string: params.strings.Common_Search, font: Font.regular(17.0), textColor: params.theme.chat.inputPanel.inputPlaceholderColor) + self.searchBarNode = searchBarNode + self.backgroundView.contentView.addSubview(searchBarNode.view) + searchBarNode.view.alpha = 0.0 + } + let searchBarFrame = CGRect(origin: CGPoint(x: 36.0, y: 0.0), size: CGSize(width: backgroundSize.width - 36.0 - 4.0, height: params.size.height)) + transition.setFrame(view: searchBarNode.view, frame: searchBarFrame) + searchBarNode.updateLayout(boundingSize: searchBarFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition) + alphaTransition.setAlpha(view: searchBarNode.view, alpha: 1.0) + } else { + if let searchBarNode = self.searchBarNode { + self.searchBarNode = nil + let searchBarNodeView = searchBarNode.view + alphaTransition.setAlpha(view: searchBarNode.view, alpha: 0.0, completion: { [weak searchBarNodeView] _ in + searchBarNodeView?.removeFromSuperview() + }) + } + } + + if params.isActive { + let closeFrame = CGRect(origin: CGPoint(x: params.size.width - 48.0, y: 0.0), size: CGSize(width: 48.0, height: 48.0)) + + let close: (background: GlassBackgroundView, icon: UIImageView) + var closeTransition = transition + if let current = self.close { + close = current + } else { + closeTransition = closeTransition.withAnimation(.none) + close = (GlassBackgroundView(), UIImageView()) + self.close = close + + close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setStrokeColor(UIColor.white.cgColor) + + context.beginPath() + context.move(to: CGPoint(x: 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0)) + context.move(to: CGPoint(x: size.width - 12.0, y: 12.0)) + context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0)) + context.strokePath() + })?.withRenderingMode(.alwaysTemplate) + + close.background.contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:)))) + + close.background.contentView.addSubview(close.icon) + self.insertSubview(close.background, at: 0) + + if let image = close.icon.image { + close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size)) + } + + close.background.frame = closeFrame.size.centered(in: previousBackgroundFrame) + close.background.update(size: close.background.bounds.size, cornerRadius: close.background.bounds.height * 0.5, isDark: params.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: params.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: .immediate) + ComponentTransition.immediate.setScale(view: close.background, scale: 0.001) + } + + close.icon.tintColor = params.theme.chat.inputPanel.panelControlColor + + transition.setPosition(view: close.background, position: closeFrame.center) + transition.setBounds(view: close.background, bounds: CGRect(origin: CGPoint(), size: closeFrame.size)) + transition.setScale(view: close.background, scale: 1.0) + + if let image = close.icon.image { + transition.setFrame(view: close.icon, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size))) + } + + close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: params.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: params.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: closeTransition) + } else { + if let close = self.close { + self.close = nil + let closeBackground = close.background + let closeFrame = CGSize(width: 48.0, height: 48.0).centered(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: params.size)) + transition.setPosition(view: closeBackground, position: closeFrame.center) + transition.setBounds(view: closeBackground, bounds: CGRect(origin: CGPoint(), size: closeFrame.size)) + closeBackground.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: params.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: params.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition) + transition.setScale(view: closeBackground, scale: 0.001, completion: { [weak closeBackground] _ in + closeBackground?.removeFromSuperview() + }) + } } } } @@ -131,46 +275,81 @@ public final class TabBarComponent: Component { return true } } + + public final class Search: Equatable { + public let isActive: Bool + public let activate: () -> Void + public let deactivate: () -> Void + + public init(isActive: Bool, activate: @escaping () -> Void, deactivate: @escaping () -> Void) { + self.isActive = isActive + self.activate = activate + self.deactivate = deactivate + } + + public static func ==(lhs: Search, rhs: Search) -> Bool { + if lhs.isActive != rhs.isActive { + return false + } + return true + } + } public let theme: PresentationTheme + public let strings: PresentationStrings public let items: [Item] + public let search: Search? public let selectedId: AnyHashable? - public let isTablet: Bool + public let outerInsets: UIEdgeInsets public init( theme: PresentationTheme, + strings: PresentationStrings, items: [Item], + search: Search?, selectedId: AnyHashable?, - isTablet: Bool + outerInsets: UIEdgeInsets ) { self.theme = theme + self.strings = strings self.items = items + self.search = search self.selectedId = selectedId - self.isTablet = isTablet + self.outerInsets = outerInsets } public static func ==(lhs: TabBarComponent, rhs: TabBarComponent) -> Bool { if lhs.theme !== rhs.theme { return false } + if lhs.strings !== rhs.strings { + return false + } if lhs.items != rhs.items { return false } + if lhs.search != rhs.search { + return false + } if lhs.selectedId != rhs.selectedId { return false } - if lhs.isTablet != rhs.isTablet { + if lhs.outerInsets != rhs.outerInsets { return false } return true } public final class View: UIView, UITabBarDelegate, UIGestureRecognizerDelegate { + private let backgroundContainer: GlassBackgroundContainerView private let liquidLensView: LiquidLensView private let contextGestureContainerView: ContextControllerSourceView + private var measureItemViews: [AnyHashable: ComponentView] = [:] private var itemViews: [AnyHashable: ComponentView] = [:] private var selectedItemViews: [AnyHashable: ComponentView] = [:] + + private var searchView: NavigationSearchView? private var tabSelectionRecognizer: TabSelectionRecognizer? private var itemWithActiveContextGesture: AnyHashable? @@ -178,11 +357,16 @@ public final class TabBarComponent: Component { private var component: TabBarComponent? private weak var state: EmptyComponentState? - private var selectionGestureState: (startX: CGFloat, currentX: CGFloat)? + private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemWidth: CGFloat, itemId: AnyHashable)? private var overrideSelectedItemId: AnyHashable? + + public var currentSearchNode: ASDisplayNode? { + return self.searchView?.searchBarNode + } public override init(frame: CGRect) { - self.liquidLensView = LiquidLensView() + self.backgroundContainer = GlassBackgroundContainerView() + self.liquidLensView = LiquidLensView(kind: .externalContainer) self.contextGestureContainerView = ContextControllerSourceView() self.contextGestureContainerView.isGestureEnabled = true @@ -194,12 +378,13 @@ public final class TabBarComponent: Component { self.traitOverrides.horizontalSizeClass = .compact } - self.addSubview(self.contextGestureContainerView) + self.addSubview(self.backgroundContainer) + self.backgroundContainer.contentView.addSubview(self.contextGestureContainerView) self.contextGestureContainerView.addSubview(self.liquidLensView) let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:))) self.tabSelectionRecognizer = tabSelectionRecognizer - self.addGestureRecognizer(tabSelectionRecognizer) + self.contextGestureContainerView.addGestureRecognizer(tabSelectionRecognizer) self.contextGestureContainerView.shouldBegin = { [weak self] point in guard let self, let component = self.component else { @@ -281,29 +466,41 @@ public final class TabBarComponent: Component { } @objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) { + guard let component = self.component else { + return + } switch recognizer.state { case .began: - if let itemId = self.item(at: recognizer.location(in: self)), let itemView = self.itemViews[itemId]?.view { + if let search = component.search, search.isActive { + } else if let itemId = self.item(at: recognizer.location(in: self)), let itemView = self.itemViews[itemId]?.view { let startX = itemView.frame.minX - 4.0 - self.selectionGestureState = (startX, startX) + self.selectionGestureState = (startX, startX, itemView.bounds.width, itemId) self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) } case .changed: - if var selectionGestureState = self.selectionGestureState { + if let search = component.search, search.isActive { + } else if var selectionGestureState = self.selectionGestureState { selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x + if let itemId = self.item(at: recognizer.location(in: self)) { + selectionGestureState.itemId = itemId + } self.selectionGestureState = selectionGestureState self.state?.updated(transition: .immediate, isLocal: true) } case .ended, .cancelled: - self.selectionGestureState = nil - if let component = self.component, let itemId = self.item(at: recognizer.location(in: self)) { - guard let item = component.items.first(where: { $0.id == itemId }) else { - return + if let search = component.search, search.isActive { + search.deactivate() + } else if let selectionGestureState = self.selectionGestureState { + self.selectionGestureState = nil + if case .ended = recognizer.state, let component = self.component { + guard let item = component.items.first(where: { $0.id == selectionGestureState.itemId }) else { + return + } + self.overrideSelectedItemId = selectionGestureState.itemId + item.action(false) } - self.overrideSelectedItemId = itemId - item.action(false) + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) } - self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) default: break } @@ -355,29 +552,91 @@ public final class TabBarComponent: Component { } func update(component: TabBarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25) + let _ = alphaTransition + let innerInset: CGFloat = 4.0 let availableSize = CGSize(width: min(500.0, availableSize.width), height: availableSize.height) let previousComponent = self.component self.component = component self.state = state - - let _ = innerInset - let _ = availableSize - let _ = previousComponent self.overrideUserInterfaceStyle = component.theme.overallDarkAppearance ? .dark : .light - let itemSize = CGSize(width: floor((availableSize.width - innerInset * 2.0) / CGFloat(component.items.count)), height: 56.0) - let contentWidth: CGFloat = innerInset * 2.0 + CGFloat(component.items.count) * itemSize.width - let size = CGSize(width: min(availableSize.width, contentWidth), height: itemSize.height + innerInset * 2.0) + let barHeight: CGFloat = 56.0 + innerInset * 2.0 + var availableItemsWidth: CGFloat = availableSize.width - innerInset * 2.0 + if component.search != nil { + availableItemsWidth -= barHeight + 8.0 + } + + var unboundItemWidths: [CGFloat] = [] + var validIds: [AnyHashable] = [] - var selectionFrame: CGRect? + var unboundItemWidthSum: CGFloat = 0.0 for index in 0 ..< component.items.count { let item = component.items[index] validIds.append(item.id) + let measureItemView: ComponentView + if let current = self.measureItemViews[item.id] { + measureItemView = current + } else { + measureItemView = ComponentView() + self.measureItemViews[item.id] = measureItemView + } + + let itemSize = measureItemView.update( + transition: .immediate, + component: AnyComponent(ItemComponent( + item: item, + theme: component.theme, + isCompact: false, + isSelected: false, + isUnconstrained: true + )), + environment: {}, + containerSize: CGSize(width: 200.0, height: 56.0) + ) + + unboundItemWidths.append(itemSize.width) + unboundItemWidthSum += itemSize.width + } + + let itemWidths: [CGFloat] + let totalItemsWidth: CGFloat + + let equalWidth = floorToScreenPixels(availableItemsWidth / CGFloat(component.items.count)) + if unboundItemWidths.allSatisfy({ $0 <= equalWidth }) { + // All items fit in equal width โ€” use equal widths for optical alignment + itemWidths = Array(repeating: equalWidth, count: component.items.count) + totalItemsWidth = equalWidth * CGFloat(component.items.count) + } else { + // Some items need more space โ€” use weighted fit + let itemWeightNorm: CGFloat = availableItemsWidth / unboundItemWidthSum + var widths: [CGFloat] = [] + var total: CGFloat = 0.0 + for index in 0 ..< component.items.count { + let itemWidth = floorToScreenPixels(unboundItemWidths[index] * itemWeightNorm) + widths.append(itemWidth) + total += itemWidth + } + itemWidths = widths + totalItemsWidth = total + } + + let itemHeight: CGFloat = 56.0 + let contentWidth: CGFloat = innerInset * 2.0 + totalItemsWidth + let tabsSize = CGSize(width: min(availableSize.width, contentWidth), height: itemHeight + innerInset * 2.0) + + var selectionFrame: CGRect? + var nextItemX: CGFloat = innerInset + for index in 0 ..< component.items.count { + let item = component.items[index] + + let itemSize = CGSize(width: itemWidths[index], height: itemHeight) + let itemView: ComponentView var itemTransition = transition @@ -409,7 +668,9 @@ public final class TabBarComponent: Component { component: AnyComponent(ItemComponent( item: item, theme: component.theme, - isSelected: false + isCompact: component.search?.isActive == true, + isSelected: false, + isUnconstrained: false )), environment: {}, containerSize: itemSize @@ -419,14 +680,23 @@ public final class TabBarComponent: Component { component: AnyComponent(ItemComponent( item: item, theme: component.theme, - isSelected: true + isCompact: component.search?.isActive == true, + isSelected: true, + isUnconstrained: false )), environment: {}, containerSize: itemSize ) - let itemFrame = CGRect(origin: CGPoint(x: innerInset + CGFloat(index) * itemSize.width, y: floor((size.height - itemSize.height) * 0.5)), size: itemSize) + var itemFrame = CGRect(origin: CGPoint(x: nextItemX, y: floor((tabsSize.height - itemSize.height) * 0.5)), size: itemSize) + nextItemX += itemSize.width + if isItemSelected { + selectionFrame = itemFrame + } + if let itemComponentView = itemView.view as? ItemComponent.View, let selectedItemComponentView = selectedItemView.view as? ItemComponent.View { + let itemAlphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25) + if itemComponentView.superview == nil { itemComponentView.isUserInteractionEnabled = false selectedItemComponentView.isUserInteractionEnabled = false @@ -434,6 +704,27 @@ public final class TabBarComponent: Component { self.liquidLensView.contentView.addSubview(itemComponentView) self.liquidLensView.selectedContentView.addSubview(selectedItemComponentView) } + + if let search = component.search, search.isActive { + if isItemSelected { + itemFrame.origin.x = floor((48.0 - itemSize.width) * 0.5) + itemTransition.setAlpha(view: itemComponentView, alpha: 1.0) + itemAlphaTransition.setBlur(layer: itemComponentView.layer, radius: 0.0) + itemTransition.setAlpha(view: selectedItemComponentView, alpha: 1.0) + itemAlphaTransition.setBlur(layer: selectedItemComponentView.layer, radius: 0.0) + } else { + itemTransition.setAlpha(view: itemComponentView, alpha: 0.0) + itemAlphaTransition.setBlur(layer: itemComponentView.layer, radius: 10.0) + itemTransition.setAlpha(view: selectedItemComponentView, alpha: 0.0) + itemAlphaTransition.setBlur(layer: selectedItemComponentView.layer, radius: 10.0) + } + } else { + itemTransition.setAlpha(view: itemComponentView, alpha: 1.0) + itemAlphaTransition.setBlur(layer: itemComponentView.layer, radius: 0.0) + itemTransition.setAlpha(view: selectedItemComponentView, alpha: 1.0) + itemAlphaTransition.setBlur(layer: selectedItemComponentView.layer, radius: 0.0) + } + itemTransition.setFrame(view: itemComponentView, frame: itemFrame) itemTransition.setPosition(view: selectedItemComponentView, position: itemFrame.center) itemTransition.setBounds(view: selectedItemComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) @@ -444,9 +735,6 @@ public final class TabBarComponent: Component { selectedItemComponentView.playSelectionAnimation() } } - if isItemSelected { - selectionFrame = itemFrame - } } var removeIds: [AnyHashable] = [] @@ -460,22 +748,95 @@ public final class TabBarComponent: Component { for id in removeIds { self.itemViews.removeValue(forKey: id) self.selectedItemViews.removeValue(forKey: id) + self.measureItemViews.removeValue(forKey: id) } - - transition.setFrame(view: self.contextGestureContainerView, frame: CGRect(origin: CGPoint(), size: size)) - - transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(), size: size)) - let lensSelection: (x: CGFloat, width: CGFloat) - if let selectionGestureState = self.selectionGestureState { - lensSelection = (selectionGestureState.currentX, itemSize.width + innerInset * 2.0) - } else if let selectionFrame { - lensSelection = (selectionFrame.minX - innerInset, itemSize.width + innerInset * 2.0) - } else { - lensSelection = (0.0, itemSize.width) + var tabsFrame = CGRect(origin: CGPoint(), size: tabsSize) + if let search = component.search, search.isActive { + tabsFrame.size = CGSize(width: 48.0, height: 48.0) + tabsFrame.origin.y = tabsSize.height - 48.0 + tabsFrame.origin.x = -component.outerInsets.left - tabsFrame.width } - self.liquidLensView.update(size: size, selectionX: lensSelection.x, selectionWidth: lensSelection.width, isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, transition: transition) + transition.setFrame(view: self.contextGestureContainerView, frame: tabsFrame) + transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(), size: tabsSize)) + + var lensSelection: (x: CGFloat, width: CGFloat) + if let selectionGestureState = self.selectionGestureState { + lensSelection = (selectionGestureState.currentX, selectionGestureState.itemWidth + innerInset * 2.0) + } else if let selectionFrame { + lensSelection = (selectionFrame.minX - innerInset, selectionFrame.width + innerInset * 2.0) + } else { + lensSelection = (0.0, 56.0) + } + + var lensSize: CGSize = tabsSize + var isLensCollapsed = false + if let search = component.search, search.isActive { + isLensCollapsed = true + lensSize = CGSize(width: 48.0, height: 48.0) + lensSelection = (0.0, 48.0) + } + + lensSelection.x = max(0.0, min(lensSelection.x, lensSize.width - lensSelection.width)) + + self.liquidLensView.update(size: lensSize, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: lensSize.height), inset: 4.0, isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: isLensCollapsed, transition: transition) + + var size = tabsSize + + if let search = component.search { + let searchSize: CGSize + let searchFrame: CGRect + if search.isActive { + size.width = availableSize.width + searchSize = CGSize(width: availableSize.width, height: 48.0) + searchFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - searchSize.height), size: searchSize) + } else { + searchSize = CGSize(width: barHeight, height: barHeight) + size.width += barHeight + 8.0 + searchFrame = CGRect(origin: CGPoint(x: availableSize.width - searchSize.width, y: 0.0), size: searchSize) + } + + let searchView: NavigationSearchView + var searchViewTransition = transition + if let current = self.searchView { + searchView = current + } else { + searchViewTransition = searchViewTransition.withAnimation(.none) + searchView = NavigationSearchView( + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.search?.activate() + }, + closeAction: { [weak self] in + guard let self, let component = self.component else { + return + } + component.search?.deactivate() + } + ) + self.searchView = searchView + self.backgroundContainer.contentView.addSubview(searchView) + searchView.frame = CGRect(origin: CGPoint(x: availableSize.width + 50.0, y: 0.0), size: searchSize) + } + searchView.update(size: searchSize, theme: component.theme, strings: component.strings, isActive: search.isActive, transition: searchViewTransition) + transition.setFrame(view: searchView, frame: searchFrame) + } else { + if let searchView = self.searchView { + self.searchView = nil + transition.setFrame(view: searchView, frame: CGRect(origin: CGPoint(x: availableSize.width + 50.0, y: 0.0), size: searchView.bounds.size), completion: { [weak searchView] completed in + guard let searchView, completed else { + return + } + searchView.removeFromSuperview() + }) + } + } + + transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size)) + self.backgroundContainer.update(size: size, isDark: component.theme.overallDarkAppearance, transition: transition) return size } @@ -493,12 +854,16 @@ public final class TabBarComponent: Component { private final class ItemComponent: Component { let item: TabBarComponent.Item let theme: PresentationTheme + let isCompact: Bool let isSelected: Bool + let isUnconstrained: Bool - init(item: TabBarComponent.Item, theme: PresentationTheme, isSelected: Bool) { + init(item: TabBarComponent.Item, theme: PresentationTheme, isCompact: Bool, isSelected: Bool, isUnconstrained: Bool) { self.item = item self.theme = theme + self.isCompact = isCompact self.isSelected = isSelected + self.isUnconstrained = isUnconstrained } static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool { @@ -508,9 +873,15 @@ private final class ItemComponent: Component { if lhs.theme !== rhs.theme { return false } + if lhs.isCompact != rhs.isCompact { + return false + } if lhs.isSelected != rhs.isSelected { return false } + if lhs.isUnconstrained != rhs.isUnconstrained { + return false + } return true } @@ -562,6 +933,8 @@ private final class ItemComponent: Component { } func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25) + let previousComponent = self.component if previousComponent?.item.item !== component.item.item { @@ -619,7 +992,7 @@ private final class ItemComponent: Component { content: LottieComponent.AppBundleContent( name: animationName ), - color: component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor, + color: (component.isSelected && !component.isCompact) ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor, placeholderColor: nil, startingPosition: .end, size: CGSize(width: 48.0, height: 48.0), @@ -692,6 +1065,7 @@ private final class ItemComponent: Component { self.contextContainerView.contentView.addSubview(titleView) } titleView.frame = titleFrame + alphaTransition.setAlpha(view: titleView, alpha: component.isCompact ? 0.0 : 1.0) } if let badgeText = component.item.item.badgeValue, !badgeText.isEmpty { @@ -711,7 +1085,7 @@ private final class ItemComponent: Component { font: Font.regular(13.0), background: component.theme.rootController.tabBar.badgeBackgroundColor, foreground: component.theme.rootController.tabBar.badgeTextColor, - insets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 1.0, right: 6.0) + insets: UIEdgeInsets(top: 0.0, left: 5.0, bottom: 1.0, right: 5.0) )), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) @@ -723,6 +1097,7 @@ private final class ItemComponent: Component { self.contextContainerView.contentView.addSubview(badgeView) } badgeTransition.setFrame(view: badgeView, frame: badgeFrame) + alphaTransition.setAlpha(view: badgeView, alpha: component.isCompact ? 0.0 : 1.0) } } else if let badge = self.badge { self.badge = nil @@ -733,7 +1108,11 @@ private final class ItemComponent: Component { transition.setFrame(view: self.contextContainerView.contentView, frame: CGRect(origin: CGPoint(), size: availableSize)) self.contextContainerView.contentRect = CGRect(origin: CGPoint(), size: availableSize) - return availableSize + if component.isUnconstrained { + return CGSize(width: titleSize.width + 10.0 * 2.0, height: availableSize.height) + } else { + return availableSize + } } } diff --git a/submodules/TelegramUI/Components/TabSelectionRecognizer/BUILD b/submodules/TelegramUI/Components/TabSelectionRecognizer/BUILD new file mode 100644 index 00000000..b3d29529 --- /dev/null +++ b/submodules/TelegramUI/Components/TabSelectionRecognizer/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "TabSelectionRecognizer", + module_name = "TabSelectionRecognizer", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/TabSelectionRecognizer/Sources/TabSelectionRecognizer.swift b/submodules/TelegramUI/Components/TabSelectionRecognizer/Sources/TabSelectionRecognizer.swift new file mode 100644 index 00000000..e67f626e --- /dev/null +++ b/submodules/TelegramUI/Components/TabSelectionRecognizer/Sources/TabSelectionRecognizer.swift @@ -0,0 +1,58 @@ +import Foundation +import UIKit + +public final class TabSelectionRecognizer: UIGestureRecognizer { + private var initialLocation: CGPoint? + private var currentLocation: CGPoint? + + public override init(target: Any?, action: Selector?) { + super.init(target: target, action: action) + + self.delaysTouchesBegan = false + self.delaysTouchesEnded = false + } + + public override func reset() { + super.reset() + + self.initialLocation = nil + } + + public override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if self.initialLocation == nil { + self.initialLocation = touches.first?.location(in: self.view) + } + self.currentLocation = self.initialLocation + + self.state = .began + } + + public override func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + self.state = .ended + } + + public override func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + self.state = .cancelled + } + + public override func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + self.currentLocation = touches.first?.location(in: self.view) + + self.state = .changed + } + + public func translation(in: UIView?) -> CGPoint { + if let initialLocation = self.initialLocation, let currentLocation = self.currentLocation { + return CGPoint(x: currentLocation.x - initialLocation.x, y: currentLocation.y - initialLocation.y) + } + return CGPoint() + } +} diff --git a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift index 7d93cb30..242c4f2f 100644 --- a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift +++ b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift @@ -65,8 +65,9 @@ public final class TabSelectorComponent: Component { public var lineSelection: Bool public var verticalInset: CGFloat public var allowScroll: Bool + public var height: CGFloat? - public init(font: UIFont, spacing: CGFloat = 2.0, innerSpacing: CGFloat? = nil, fillWidth: Bool = false, lineSelection: Bool = false, verticalInset: CGFloat = 0.0, allowScroll: Bool = true) { + public init(font: UIFont, spacing: CGFloat = 2.0, innerSpacing: CGFloat? = nil, fillWidth: Bool = false, lineSelection: Bool = false, verticalInset: CGFloat = 0.0, allowScroll: Bool = true, height: CGFloat? = nil) { self.font = font self.spacing = spacing self.innerSpacing = innerSpacing @@ -74,6 +75,7 @@ public final class TabSelectorComponent: Component { self.lineSelection = lineSelection self.verticalInset = verticalInset self.allowScroll = allowScroll + self.height = height } } @@ -531,14 +533,17 @@ public final class TabSelectorComponent: Component { self.reorderRecognizer?.isEnabled = component.reorderItem != nil let baseHeight: CGFloat - switch component.style { - case .glass: - baseHeight = 32.0 - case .legacy: - baseHeight = 28.0 + if let customLayout = component.customLayout, let height = customLayout.height { + baseHeight = height + } else { + switch component.style { + case .glass: + baseHeight = 32.0 + case .legacy: + baseHeight = 28.0 + } } - var verticalInset: CGFloat = 0.0 if let customLayout = component.customLayout { verticalInset = customLayout.verticalInset * 2.0 @@ -632,7 +637,7 @@ public final class TabSelectorComponent: Component { if case .component = item.content { useSelectionFraction = true } - if let _ = component.colors.normal { + if let normal = component.colors.normal, normal != component.colors.foreground { useSelectionFraction = true } diff --git a/submodules/TelegramUI/Components/TextFieldComponent/BUILD b/submodules/TelegramUI/Components/TextFieldComponent/BUILD index 4d16bc5b..5a8eba3a 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/BUILD +++ b/submodules/TelegramUI/Components/TextFieldComponent/BUILD @@ -18,7 +18,6 @@ swift_library( "//submodules/AccountContext", "//submodules/InvisibleInkDustNode", "//submodules/TelegramUI/Components/EmojiTextAttachmentView", - "//submodules/ChatTextLinkEditUI", "//submodules/Pasteboard", "//submodules/ImageTransparency", "//submodules/TelegramUI/Components/Chat/ChatInputTextNode", diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 3b76429a..ac25591b 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -10,7 +10,6 @@ import EmojiTextAttachmentView import AccountContext import TextFormat import Pasteboard -import ChatTextLinkEditUI import MobileCoreServices import ImageTransparency import ChatInputTextNode @@ -161,6 +160,9 @@ public final class TextFieldComponent: Component { public let externalHandlingForMultilinePaste: Bool public let formatMenuAvailability: FormatMenuAvailability public let returnKeyType: UIReturnKeyType + public let keyboardType: UIKeyboardType + public let autocapitalizationType: UITextAutocapitalizationType + public let autocorrectionType: UITextAutocorrectionType public let lockedFormatAction: () -> Void public let present: (ViewController) -> Void public let paste: (PasteData) -> Void @@ -188,6 +190,9 @@ public final class TextFieldComponent: Component { externalHandlingForMultilinePaste: Bool = false, formatMenuAvailability: FormatMenuAvailability, returnKeyType: UIReturnKeyType = .default, + keyboardType: UIKeyboardType = .default, + autocapitalizationType: UITextAutocapitalizationType = .sentences, + autocorrectionType: UITextAutocorrectionType = .default, lockedFormatAction: @escaping () -> Void, present: @escaping (ViewController) -> Void, paste: @escaping (PasteData) -> Void, @@ -214,11 +219,14 @@ public final class TextFieldComponent: Component { self.externalHandlingForMultilinePaste = externalHandlingForMultilinePaste self.formatMenuAvailability = formatMenuAvailability self.returnKeyType = returnKeyType + self.keyboardType = keyboardType self.lockedFormatAction = lockedFormatAction self.present = present self.paste = paste self.returnKeyAction = returnKeyAction self.backspaceKeyAction = backspaceKeyAction + self.autocapitalizationType = autocapitalizationType + self.autocorrectionType = autocorrectionType } public static func ==(lhs: TextFieldComponent, rhs: TextFieldComponent) -> Bool { @@ -282,6 +290,15 @@ public final class TextFieldComponent: Component { if lhs.returnKeyType != rhs.returnKeyType { return false } + if lhs.keyboardType != rhs.keyboardType { + return false + } + if lhs.autocapitalizationType != rhs.autocapitalizationType { + return false + } + if lhs.autocorrectionType != rhs.autocorrectionType { + return false + } return true } @@ -409,7 +426,7 @@ public final class TextFieldComponent: Component { let inputState = f(self.inputState) let currentAttributedText = self.textView.attributedText - let updatedAttributedText = textAttributedStringForStateText(context: component.context, stateText: inputState.inputText, fontSize: component.fontSize, textColor: component.textColor, accentTextColor: component.accentColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + let updatedAttributedText = textAttributedStringForStateText(context: component.context, stateText: inputState.inputText, fontSize: component.fontSize, textColor: component.textColor, accentTextColor: component.accentColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) }) if currentAttributedText != updatedAttributedText { @@ -986,7 +1003,7 @@ public final class TextFieldComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: component.theme) let updatedPresentationData: (initial: PresentationData, signal: Signal) = (presentationData, .single(presentationData)) - let controller = chatTextLinkEditController(sharedContext: component.context.sharedContext, updatedPresentationData: updatedPresentationData, account: component.context.account, text: text.string, link: link, allowEmpty: true, apply: { [weak self] link in + let controller = component.context.sharedContext.makeLinkEditController(context: component.context, updatedPresentationData: updatedPresentationData, text: text.string, link: link, apply: { [weak self] link in if let self { if let link { if !link.isEmpty { @@ -1388,6 +1405,15 @@ public final class TextFieldComponent: Component { if self.textView.returnKeyType != component.returnKeyType { self.textView.returnKeyType = component.returnKeyType } + if self.textView.keyboardType != component.keyboardType { + self.textView.keyboardType = component.keyboardType + } + if self.textView.autocapitalizationType != component.autocapitalizationType { + self.textView.autocapitalizationType = component.autocapitalizationType + } + if self.textView.autocorrectionType != component.autocorrectionType { + self.textView.autocorrectionType = component.autocorrectionType + } if let initialText = component.externalState.initialText { component.externalState.initialText = nil diff --git a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift index 9622b268..cb5f59f8 100644 --- a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift +++ b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift @@ -106,7 +106,12 @@ public final class TextNodeWithEntities { public let textNode: TextNode private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:] - private var enableLooping: Bool = true + public var enableLooping: Bool = true + public var energySavingEnableLooping: Bool = true + + private var effectiveEnableLooping: Bool { + return self.enableLooping && self.energySavingEnableLooping + } public var resetEmojiToFirstFrameAutomatically: Bool = false @@ -124,7 +129,7 @@ public final class TextNodeWithEntities { } else { isItemVisible = false } - let isVisibleForAnimations = self.enableLooping && isItemVisible && itemLayer.enableAnimation + let isVisibleForAnimations = self.effectiveEnableLooping && isItemVisible && itemLayer.enableAnimation if itemLayer.isVisibleForAnimations != isVisibleForAnimations { itemLayer.isVisibleForAnimations = isVisibleForAnimations if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically { @@ -257,7 +262,7 @@ public final class TextNodeWithEntities { } private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, attemptSynchronousLoad: Bool, emojiOffset: CGPoint, fontSizeNorm: CGFloat) { - self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji + self.energySavingEnableLooping = context.sharedContext.energyUsageSettings.loopEmoji var nextIndexById: [Int64: Int] = [:] var validIds: [InlineStickerItemLayer.Key] = [] @@ -292,7 +297,7 @@ public final class TextNodeWithEntities { self.textNode.layer.addSublayer(itemLayer) } itemLayer.enableAnimation = stickerItem.enableAnimation - let isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame) && itemLayer.enableAnimation + let isVisibleForAnimations = self.effectiveEnableLooping && self.isItemVisible(itemRect: itemFrame) && itemLayer.enableAnimation if itemLayer.isVisibleForAnimations != isVisibleForAnimations { if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically { itemLayer.reloadAnimation() @@ -410,7 +415,12 @@ public class ImmediateTextNodeWithEntities: TextNode { public var spoilerColor: UIColor = .black public var balancedTextLayout: Bool = false - private var enableLooping: Bool = true + public var enableLooping: Bool = true + public var energySavingEnableLooping: Bool = true + + private var effectiveEnableLooping: Bool { + return self.enableLooping && self.energySavingEnableLooping + } public var arguments: TextNodeWithEntities.Arguments? @@ -423,7 +433,7 @@ public class ImmediateTextNodeWithEntities: TextNode { didSet { if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibility { for (_, itemLayer) in self.inlineStickerItemLayers { - let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation + let isVisibleForAnimations = self.effectiveEnableLooping && self.visibility && itemLayer.enableAnimation if itemLayer.isVisibleForAnimations != isVisibleForAnimations { itemLayer.isVisibleForAnimations = isVisibleForAnimations if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically { @@ -575,7 +585,7 @@ public class ImmediateTextNodeWithEntities: TextNode { } private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, fontSizeNorm: CGFloat) { - self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji + self.energySavingEnableLooping = context.sharedContext.energyUsageSettings.loopEmoji var nextIndexById: [Int64: Int] = [:] var validIds: [InlineStickerItemLayer.Key] = [] @@ -613,7 +623,7 @@ public class ImmediateTextNodeWithEntities: TextNode { } itemLayer.enableAnimation = stickerItem.enableAnimation - let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation + let isVisibleForAnimations = self.effectiveEnableLooping && self.visibility && itemLayer.enableAnimation if itemLayer.isVisibleForAnimations != isVisibleForAnimations { itemLayer.isVisibleForAnimations = isVisibleForAnimations if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically { diff --git a/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift b/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift index 1b0f2749..03e2207b 100644 --- a/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift +++ b/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift @@ -339,8 +339,6 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { self.scrollNode.addSubnode(self.placeholderNode) self.scrollNode.addSubnode(self.textFieldScrollNode) self.textFieldScrollNode.addSubnode(self.textFieldNode) - //self.scrollNode.addSubnode(self.caretIndicatorNode) - self.clipsToBounds = true self.textFieldNode.textField.delegate = self self.textFieldNode.textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) @@ -383,7 +381,6 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { let sideInset: CGFloat = 12.0 + leftInset let verticalInset: CGFloat = 6.0 - var animationDelay = 0.0 var currentOffset = CGPoint(x: sideInset, y: verticalInset) for token in tokens { diff --git a/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/BUILD new file mode 100644 index 00000000..852662d5 --- /dev/null +++ b/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/BUILD @@ -0,0 +1,34 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "TranslateHeaderPanelComponent", + module_name = "TranslateHeaderPanelComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/LocalizedPeerData", + "//submodules/TelegramStringFormatting", + "//submodules/TextFormat", + "//submodules/Markdown", + "//submodules/MoreButtonNode", + "//submodules/ContextUI", + "//submodules/TranslateUI", + "//submodules/TelegramUIPreferences", + "//submodules/TelegramNotices", + "//submodules/PremiumUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/ChatTranslationPanelNode.swift b/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/ChatTranslationPanelNode.swift new file mode 100644 index 00000000..42b05c81 --- /dev/null +++ b/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/ChatTranslationPanelNode.swift @@ -0,0 +1,1112 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import LocalizedPeerData +import TelegramStringFormatting +import TextFormat +import Markdown +import AccountContext +import MoreButtonNode +import ContextUI +import TranslateUI +import TelegramUIPreferences +import TelegramNotices +import PremiumUI +import ComponentFlow +import ComponentDisplayAdapters +import LocalMediaResources +import AppBundle + +final class ChatTranslationPanelNode: ASDisplayNode { + private let context: AccountContext + private let close: () -> Void + private let toggle: () -> Void + private let controller: () -> ViewController? + private let changeLanguage: (String) -> Void + private let addDoNotTranslateLanguage: (String) -> Void + + private let button: HighlightableButtonNode + private let buttonIconNode: ASImageNode + private let buttonTextNode: ImmediateTextNode + private let moreButton: MoreButtonNode + private let closeButton: HighlightableButtonNode + + private var theme: PresentationTheme? + + private var currentInfo: TranslateHeaderPanelComponent.Info? + + init(context: AccountContext, close: @escaping () -> Void, toggle: @escaping () -> Void, changeLanguage: @escaping (String) -> Void, addDoNotTranslateLanguage: @escaping (String) -> Void, controller: @escaping () -> ViewController?) { + self.context = context + self.close = close + self.toggle = toggle + self.changeLanguage = changeLanguage + self.addDoNotTranslateLanguage = addDoNotTranslateLanguage + self.controller = controller + + self.button = HighlightableButtonNode() + self.buttonIconNode = ASImageNode() + self.buttonIconNode.displaysAsynchronously = false + + self.buttonTextNode = ImmediateTextNode() + self.buttonTextNode.displaysAsynchronously = false + + let theme: PresentationTheme = context.sharedContext.currentPresentationData.with { $0 }.theme + self.moreButton = MoreButtonNode(theme: theme) + self.moreButton.updateColor(theme.chat.inputPanel.panelControlColor, transition: .immediate) + self.moreButton.iconNode.enqueueState(.more, animated: false) + self.moreButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) + + self.closeButton = HighlightableButtonNode() + self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) + self.closeButton.displaysAsynchronously = false + + super.init() + + self.clipsToBounds = true + + self.addSubnode(self.button) + self.addSubnode(self.moreButton) + + self.button.addSubnode(self.buttonIconNode) + self.button.addSubnode(self.buttonTextNode) + + self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: [.touchUpInside]) + self.moreButton.action = { [weak self] _, gesture in + if let strongSelf = self { + strongSelf.morePressed(node: strongSelf.moreButton.contextSourceNode, gesture: gesture) + } + } + + self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside]) + self.addSubnode(self.closeButton) + } + + func animateOut() { + self.layer.animateBounds(from: self.bounds, to: self.bounds.offsetBy(dx: 0.0, dy: self.bounds.size.height), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + } + + func updateLayout( + width: CGFloat, + info: TranslateHeaderPanelComponent.Info, + theme: PresentationTheme, + strings: PresentationStrings, + transition: ContainedViewLayoutTransition + ) -> CGFloat { + let leftInset: CGFloat = 0.0 + let rightInset: CGFloat = 0.0 + + let previousInfo = self.currentInfo + self.currentInfo = info + + var themeUpdated = false + if theme !== self.theme { + themeUpdated = true + self.theme = theme + } + + if themeUpdated { + self.buttonIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/Translate"), color: theme.chat.inputPanel.panelControlColor) + self.moreButton.theme = theme + self.moreButton.updateColor(theme.chat.inputPanel.panelControlColor, transition: .immediate) + self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(theme.chat.inputPanel.panelControlColor.cgColor) + context.setLineWidth(1.33) + context.setLineCap(.round) + context.move(to: CGPoint(x: 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0)) + context.strokePath() + }), for: []) + } + + var textUpdated = false + if themeUpdated || previousInfo?.isActive != info.isActive { + var languageCode = strings.baseLanguageCode + let rawSuffix = "-raw" + if languageCode.hasSuffix(rawSuffix) { + languageCode = String(languageCode.dropLast(rawSuffix.count)) + } + + let toLang = info.toLang ?? languageCode + let key = "Translation.Language.\(toLang)" + let translateTitle: String + if let string = strings.primaryComponent.dict[key] { + translateTitle = strings.Conversation_Translation_TranslateTo(string).string + } else { + let languageLocale = Locale(identifier: languageCode) + let toLanguage = languageLocale.localizedString(forLanguageCode: toLang) ?? "" + translateTitle = strings.Conversation_Translation_TranslateToOther(toLanguage).string + } + + let buttonText = info.isActive ? strings.Conversation_Translation_ShowOriginal : translateTitle + if self.buttonTextNode.attributedText?.string != buttonText { + textUpdated = true + } + self.buttonTextNode.attributedText = NSAttributedString(string: buttonText, font: Font.regular(17.0), textColor: theme.chat.inputPanel.panelControlColor) + } + + let panelHeight: CGFloat = 40.0 + + let contentRightInset: CGFloat = 11.0 + rightInset + + var copyTextView: UIView? + if textUpdated, transition.isAnimated { + if let copyView = self.buttonTextNode.layer.snapshotContentTreeAsView(unhide: false) { + copyTextView = copyView + self.buttonTextNode.view.superview?.insertSubview(copyView, belowSubview: self.buttonTextNode.view) + transition.updateAlpha(layer: copyView.layer, alpha: 0.0, completion: { [weak copyView] _ in + copyView?.removeFromSuperview() + }) + ComponentTransition(transition).setBlur(layer: copyView.layer, radius: 8.0) + + ComponentTransition(transition).animateBlur(layer: self.buttonTextNode.layer, fromRadius: 8.0, toRadius: 0.0) + self.buttonTextNode.alpha = 0.0 + transition.updateAlpha(layer: self.buttonTextNode.layer, alpha: 1.0) + } + } + + let moreButtonSize = self.moreButton.measure(CGSize(width: 100.0, height: panelHeight)) + transition.updateFrame(node: self.moreButton, frame: CGRect(origin: CGPoint(x: width - contentRightInset - moreButtonSize.width, y: floorToScreenPixels((panelHeight - moreButtonSize.height) / 2.0) - 1.0), size: moreButtonSize)) + + let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) + self.closeButton.frame = CGRect(origin: CGPoint(x: width - contentRightInset - closeButtonSize.width, y: floorToScreenPixels((panelHeight - closeButtonSize.height) / 2.0)), size: closeButtonSize) + + if info.isPremium { + self.moreButton.isHidden = false + self.closeButton.isHidden = true + } else { + self.moreButton.isHidden = true + self.closeButton.isHidden = false + } + + let buttonPadding: CGFloat = 10.0 + let buttonSpacing: CGFloat = 10.0 + let buttonTextSize = self.buttonTextNode.updateLayout(CGSize(width: width - contentRightInset - moreButtonSize.width, height: panelHeight)) + if let icon = self.buttonIconNode.image { + let buttonSize = CGSize(width: buttonTextSize.width + icon.size.width + buttonSpacing + buttonPadding * 2.0, height: panelHeight) + transition.updateFrame(node: self.button, frame: CGRect(origin: CGPoint(x: leftInset + floorToScreenPixels((width - leftInset - rightInset - buttonSize.width) / 2.0), y: 0.0), size: buttonSize)) + + transition.updateFrame(node: self.buttonIconNode, frame: CGRect(origin: CGPoint(x: buttonPadding, y: floorToScreenPixels((buttonSize.height - icon.size.height) / 2.0)), size: icon.size)) + + let buttonTextFrame = CGRect(origin: CGPoint(x: buttonPadding + icon.size.width + buttonSpacing, y: floorToScreenPixels((buttonSize.height - buttonTextSize.height) / 2.0)), size: buttonTextSize) + transition.updatePosition(node: self.buttonTextNode, position: buttonTextFrame.center) + if let copyTextView { + transition.updatePosition(layer: copyTextView.layer, position: buttonTextFrame.center) + } + self.buttonTextNode.bounds = CGRect(origin: CGPoint(), size: buttonTextFrame.size) + } + + return panelHeight + } + + @objc private func closePressed() { + guard let info = self.currentInfo else { + return + } + let isPremium = info.isPremium + + var translationAvailable = isPremium + if case let .channel(channel) = info.peer, channel.flags.contains(.autoTranslateEnabled) { + translationAvailable = true + } + + if translationAvailable { + self.close() + } else if !isPremium { + let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: self.context.sharedContext.accountManager, count: -100, timestamp: Int32(Date().timeIntervalSince1970) + 60 * 60 * 24 * 7).startStandalone() + } + } + + @objc private func buttonPressed() { + guard let info = self.currentInfo else { + return + } + + let isPremium = info.isPremium + + var translationAvailable = isPremium + if case let .channel(channel) = info.peer, channel.flags.contains(.autoTranslateEnabled) { + translationAvailable = true + } + + if translationAvailable { + self.toggle() + } else if !info.isActive { + if !isPremium { + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: context, subject: .translation, action: { + let controller = PremiumIntroScreen(context: context, source: .translation) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.controller()?.push(controller) + } + } + } + + @objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) { + guard let info = self.currentInfo else { + return + } + + let context = self.context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + var languageCode = presentationData.strings.baseLanguageCode + let rawSuffix = "-raw" + if languageCode.hasSuffix(rawSuffix) { + languageCode = String(languageCode.dropLast(rawSuffix.count)) + } + + let doNotTranslateTitle: String + let fromLang = info.fromLang + let key = "Translation.Language.\(fromLang)" + if let string = presentationData.strings.primaryComponent.dict[key] { + doNotTranslateTitle = presentationData.strings.Conversation_Translation_DoNotTranslate(string).string + } else { + let languageLocale = Locale(identifier: languageCode) + let fromLanguage = languageLocale.localizedString(forLanguageCode: fromLang) ?? "" + doNotTranslateTitle = presentationData.strings.Conversation_Translation_DoNotTranslateOther(fromLanguage).string + } + + let items: Signal = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) + |> take(1) + |> map { sharedData -> ContextController.Items in + let settings: TranslationSettings + if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { + settings = current + } else { + settings = TranslationSettings.defaultSettings + } + + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_Translation_ChooseLanguage, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + guard let self else { + return + } + + var addedLanguages = Set() + + var topLanguages: [String] = [] + let langCode = normalizeTranslationLanguage(languageCode) + + var selectedLanguages: Set + if let ignoredLanguages = settings.ignoredLanguages { + selectedLanguages = Set(ignoredLanguages) + } else { + selectedLanguages = Set([langCode]) + for language in systemLanguageCodes() { + selectedLanguages.insert(language) + } + } + for code in supportedTranslationLanguages { + if selectedLanguages.contains(code) { + topLanguages.append(code) + } + } + + topLanguages.append("") + + var languages: [(String, String)] = [] + let languageLocale = Locale(identifier: langCode) + + for code in topLanguages { + if !addedLanguages.contains(code) { + let displayTitle = languageLocale.localizedString(forLanguageCode: code) ?? "" + let value = (code, displayTitle) + if code == languageCode { + languages.insert(value, at: 0) + } else { + languages.append(value) + } + addedLanguages.insert(code) + } + } + + for code in supportedTranslationLanguages { + if !addedLanguages.contains(code) { + let displayTitle = languageLocale.localizedString(forLanguageCode: code) ?? "" + let value = (code, displayTitle) + if code == languageCode { + languages.insert(value, at: 0) + } else { + languages.append(value) + } + addedLanguages.insert(code) + } + } + + c?.pushItems(items: .single(ContextController.Items( + content: .custom( + TranslationLanguagesContextMenuContent( + context: self.context, + languages: languages, back: { [weak c] in + c?.popItems() + }, selectLanguage: { [weak self, weak c] language in + c?.dismiss(completion: { + guard let self else { + return + } + self.changeLanguage(language) + }) + } + ) + ) + ))) + }))) + + items.append(.separator) + + items.append(.action(ContextMenuActionItem(text: doNotTranslateTitle, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + c?.dismiss(completion: nil) + + guard let self, let info = self.currentInfo else { + return + } + self.addDoNotTranslateLanguage(info.fromLang) + }))) + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_Translation_Hide, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + c?.dismiss(completion: nil) + + self?.close() + }))) + + items.append(.separator) + + let cocoonPath = getAppBundle().url(forResource: "Cocoon", withExtension: "tgs")?.path ?? "" + let cocoonFile = TelegramMediaFile( + fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: -123456789), + partialReference: nil, + resource: BundleResource(name: "Cocoon", path: cocoonPath), + previewRepresentations: [], + videoThumbnails: [], + immediateThumbnailData: nil, + mimeType: "application/x-tgsticker", + size: nil, + attributes: [ + .FileName(fileName: "sticker.tgs"), + .CustomEmoji(isPremium: false, isSingleColor: true, alt: "", packReference: .animatedEmojiAnimations) + ], + alternativeRepresentations: [] + ) + + let (cocoonText, entities) = parseCocoonMenuTextEntities(presentationData.strings.Conversation_Translation_CocoonInfo, emojiFileId: cocoonFile.fileId.id) + items.append(.action(ContextMenuActionItem(text: cocoonText, entities: entities, entityFiles: [cocoonFile.fileId.id: cocoonFile], enableEntityAnimations: true, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: { [weak self] c, _ in + c?.dismiss(completion: nil) + + if let controller = self?.controller() { + let infoController = context.sharedContext.makeCocoonInfoScreen(context: context) + controller.push(infoController) + } + }))) + + return ContextController.Items(content: .list(items)) + } + + if let controller = self.controller() { + let contextController = ContextController(context: context, presentationData: presentationData, source: .reference(TranslationContextReferenceContentSource(controller: controller, sourceNode: node)), items: items, gesture: gesture) + controller.presentInGlobalOverlay(contextController) + } + } +} + +private final class TranslationContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceNode: ContextReferenceContentNode + + var keepInPlace: Bool { + return true + } + + init(controller: ViewController, sourceNode: ContextReferenceContentNode) { + self.controller = controller + self.sourceNode = sourceNode + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} + +private let separatorHeight: CGFloat = 7.0 + +private final class TranslationLanguagesContextMenuContent: ContextControllerItemsContent { + private final class BackButtonNode: HighlightTrackingButtonNode { + let highlightBackgroundNode: ASDisplayNode + let titleLabelNode: ImmediateTextNode + let separatorNode: ASDisplayNode + let iconNode: ASImageNode + + var action: (() -> Void)? + + private var theme: PresentationTheme? + + init() { + self.highlightBackgroundNode = ASDisplayNode() + self.highlightBackgroundNode.isAccessibilityElement = false + self.highlightBackgroundNode.alpha = 0.0 + + self.titleLabelNode = ImmediateTextNode() + self.titleLabelNode.isAccessibilityElement = false + self.titleLabelNode.maximumNumberOfLines = 1 + self.titleLabelNode.isUserInteractionEnabled = false + + self.iconNode = ASImageNode() + self.iconNode.isAccessibilityElement = false + + self.separatorNode = ASDisplayNode() + self.separatorNode.isAccessibilityElement = false + + super.init() + + self.addSubnode(self.separatorNode) + self.addSubnode(self.highlightBackgroundNode) + self.addSubnode(self.titleLabelNode) + self.addSubnode(self.iconNode) + + self.isAccessibilityElement = true + + self.highligthedChanged = { [weak self] highlighted in + guard let strongSelf = self else { + return + } + if highlighted { + strongSelf.highlightBackgroundNode.alpha = 1.0 + } else { + let previousAlpha = strongSelf.highlightBackgroundNode.alpha + strongSelf.highlightBackgroundNode.alpha = 0.0 + strongSelf.highlightBackgroundNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2) + } + } + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + } + + @objc private func pressed() { + self.action?() + } + + func update(size: CGSize, presentationData: PresentationData, isLast: Bool) { + let standardIconWidth: CGFloat = 32.0 + let sideInset: CGFloat = 16.0 + let iconSideInset: CGFloat = 12.0 + + if self.theme !== presentationData.theme { + self.theme = presentationData.theme + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: presentationData.theme.contextMenu.primaryColor) + + self.accessibilityLabel = presentationData.strings.Common_Back + } + + self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor + self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor + + self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size) + + self.titleLabelNode.attributedText = NSAttributedString(string: presentationData.strings.Common_Back, font: Font.regular(17.0), textColor: presentationData.theme.contextMenu.primaryColor) + let titleSize = self.titleLabelNode.updateLayout(CGSize(width: size.width - sideInset - standardIconWidth, height: 100.0)) + self.titleLabelNode.frame = CGRect(origin: CGPoint(x: sideInset + 36.0, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) + + if let iconImage = self.iconNode.image { + let iconFrame = CGRect(origin: CGPoint(x: iconSideInset, y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size) + self.iconNode.frame = iconFrame + } + + self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)) + self.separatorNode.isHidden = isLast + } + } + + private final class LanguagesListNode: ASDisplayNode, ASScrollViewDelegate { + private final class ItemNode: HighlightTrackingButtonNode { + let context: AccountContext + let highlightBackgroundNode: ASDisplayNode + let titleLabelNode: ImmediateTextNode + let separatorNode: ASDisplayNode + + let action: () -> Void + + private var language: String? + + init(context: AccountContext, action: @escaping () -> Void) { + self.action = action + self.context = context + + self.highlightBackgroundNode = ASDisplayNode() + self.highlightBackgroundNode.isAccessibilityElement = false + self.highlightBackgroundNode.alpha = 0.0 + + self.titleLabelNode = ImmediateTextNode() + self.titleLabelNode.isAccessibilityElement = false + self.titleLabelNode.maximumNumberOfLines = 1 + self.titleLabelNode.isUserInteractionEnabled = false + + self.separatorNode = ASDisplayNode() + self.separatorNode.isAccessibilityElement = false + + super.init() + + self.isAccessibilityElement = true + + self.addSubnode(self.separatorNode) + self.addSubnode(self.highlightBackgroundNode) + self.addSubnode(self.titleLabelNode) + + self.highligthedChanged = { [weak self] highlighted in + guard let strongSelf = self, let language = strongSelf.language, !language.isEmpty else { + return + } + if highlighted { + strongSelf.highlightBackgroundNode.alpha = 1.0 + } else { + let previousAlpha = strongSelf.highlightBackgroundNode.alpha + strongSelf.highlightBackgroundNode.alpha = 0.0 + strongSelf.highlightBackgroundNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2) + } + } + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + } + + @objc private func pressed() { + guard let language = self.language, !language.isEmpty else { + return + } + self.action() + } + + private var displayTitle: String? + func update(size: CGSize, presentationData: PresentationData, language: String, displayTitle: String, isLast: Bool, syncronousLoad: Bool) { + let sideInset: CGFloat = 16.0 + + if self.language != language { + self.language = language + self.displayTitle = displayTitle + + self.accessibilityLabel = "\(displayTitle)" + } + + self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor + + self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size) + + self.titleLabelNode.attributedText = NSAttributedString(string: self.displayTitle ?? "", font: Font.regular(17.0), textColor: presentationData.theme.contextMenu.primaryColor) + let maxTextWidth: CGFloat = size.width - sideInset + + let titleSize = self.titleLabelNode.updateLayout(CGSize(width: maxTextWidth, height: 100.0)) + let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) + self.titleLabelNode.frame = titleFrame + + if language == "" { + self.separatorNode.backgroundColor = presentationData.theme.contextMenu.sectionSeparatorColor + self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: separatorHeight)) + self.separatorNode.isHidden = false + } else { + self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor + self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: size.width, height: UIScreenPixel)) + self.separatorNode.isHidden = isLast + } + } + } + + private let context: AccountContext + private let languages: [(String, String)] + private let requestUpdate: (LanguagesListNode, ContainedViewLayoutTransition) -> Void + private let requestUpdateApparentHeight: (LanguagesListNode, ContainedViewLayoutTransition) -> Void + private let selectLanguage: (String) -> Void + + private let scrollNode: ASScrollNode + private var ignoreScrolling: Bool = false + private var animateIn: Bool = false + private var bottomScrollInset: CGFloat = 0.0 + + private var presentationData: PresentationData? + private var currentSize: CGSize? + private var apparentHeight: CGFloat = 0.0 + + private var itemNodes: [Int: ItemNode] = [:] + + init( + context: AccountContext, + languages: [(String, String)], + requestUpdate: @escaping (LanguagesListNode, ContainedViewLayoutTransition) -> Void, + requestUpdateApparentHeight: @escaping (LanguagesListNode, ContainedViewLayoutTransition) -> Void, + selectLanguage: @escaping (String) -> Void + ) { + self.context = context + self.languages = languages + self.requestUpdate = requestUpdate + self.requestUpdateApparentHeight = requestUpdateApparentHeight + self.selectLanguage = selectLanguage + + self.scrollNode = ASScrollNode() + self.scrollNode.canCancelAllTouchesInViews = true + self.scrollNode.view.delaysContentTouches = false + self.scrollNode.view.showsVerticalScrollIndicator = false + if #available(iOS 11.0, *) { + self.scrollNode.view.contentInsetAdjustmentBehavior = .never + } + self.scrollNode.clipsToBounds = false + + super.init() + + self.addSubnode(self.scrollNode) + self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + + self.clipsToBounds = true + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if self.ignoreScrolling { + return + } + self.updateVisibleItems(animated: false, syncronousLoad: false) + + if let size = self.currentSize { + var apparentHeight = -self.scrollNode.view.contentOffset.y + self.scrollNode.view.contentSize.height + apparentHeight = max(apparentHeight, 44.0) + apparentHeight = min(apparentHeight, size.height) + if self.apparentHeight != apparentHeight { + self.apparentHeight = apparentHeight + + self.requestUpdateApparentHeight(self, .immediate) + } + } + } + + private func updateVisibleItems(animated: Bool, syncronousLoad: Bool) { + guard let size = self.currentSize else { + return + } + guard let presentationData = self.presentationData else { + return + } + let itemHeight: CGFloat = 44.0 + let visibleBounds = self.scrollNode.bounds.insetBy(dx: 0.0, dy: -180.0) + + var validIds = Set() + + let minVisibleIndex = max(0, Int(floor(visibleBounds.minY / itemHeight))) + let maxVisibleIndex = Int(ceil(visibleBounds.maxY / itemHeight)) + + var separatorIndex = 0 + for i in 0 ..< self.languages.count { + if self.languages[i].0.isEmpty { + separatorIndex = i + break + } + } + + if minVisibleIndex <= maxVisibleIndex { + for index in minVisibleIndex ... maxVisibleIndex { + if index < self.languages.count { + let height = self.languages[index].0.isEmpty ? separatorHeight : itemHeight + var itemFrame = CGRect(origin: CGPoint(x: 0.0, y: CGFloat(index) * itemHeight), size: CGSize(width: size.width, height: height)) + if index > separatorIndex { + itemFrame.origin.y += separatorHeight - itemHeight + } + + let (languageCode, displayTitle) = self.languages[index] + validIds.insert(index) + + let itemNode: ItemNode + if let current = self.itemNodes[index] { + itemNode = current + } else { + let selectLanguage = self.selectLanguage + itemNode = ItemNode(context: self.context, action: { + selectLanguage(languageCode) + }) + self.itemNodes[index] = itemNode + self.scrollNode.addSubnode(itemNode) + } + + itemNode.update(size: itemFrame.size, presentationData: presentationData, language: languageCode, displayTitle: displayTitle, isLast: index == self.languages.count - 1 || index == separatorIndex - 1, syncronousLoad: syncronousLoad) + itemNode.frame = itemFrame + } + } + } + + var removeIds: [Int] = [] + for (id, itemNode) in self.itemNodes { + if !validIds.contains(id) { + removeIds.append(id) + itemNode.removeFromSupernode() + } + } + for id in removeIds { + self.itemNodes.removeValue(forKey: id) + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + var extendedScrollNodeFrame = self.scrollNode.frame + extendedScrollNodeFrame.size.height += self.bottomScrollInset + + if extendedScrollNodeFrame.contains(point) { + return self.scrollNode.view.hitTest(self.view.convert(point, to: self.scrollNode.view), with: event) + } + + return super.hitTest(point, with: event) + } + + func update(presentationData: PresentationData, constrainedSize: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> (height: CGFloat, apparentHeight: CGFloat) { + let itemHeight: CGFloat = 44.0 + + self.presentationData = presentationData + + var separatorIndex = 0 + for i in 0 ..< self.languages.count { + if self.languages[i].0.isEmpty { + separatorIndex = i + break + } + } + + var contentHeight: CGFloat + if separatorIndex != 0 { + contentHeight = CGFloat(self.languages.count - 1) * itemHeight + separatorHeight + } else { + contentHeight = CGFloat(self.languages.count) * itemHeight + } + let size = CGSize(width: constrainedSize.width, height: contentHeight) + + let containerSize = CGSize(width: size.width, height: min(constrainedSize.height, size.height)) + self.currentSize = containerSize + + self.ignoreScrolling = true + + if self.scrollNode.frame != CGRect(origin: CGPoint(), size: containerSize) { + self.scrollNode.frame = CGRect(origin: CGPoint(), size: containerSize) + } + if self.scrollNode.view.contentInset.bottom != bottomInset { + self.scrollNode.view.contentInset.bottom = bottomInset + } + self.bottomScrollInset = bottomInset + let scrollContentSize = CGSize(width: size.width, height: size.height) + if self.scrollNode.view.contentSize != scrollContentSize { + self.scrollNode.view.contentSize = scrollContentSize + } + self.ignoreScrolling = false + + self.updateVisibleItems(animated: transition.isAnimated, syncronousLoad: !transition.isAnimated) + + self.animateIn = false + + var apparentHeight = -self.scrollNode.view.contentOffset.y + self.scrollNode.view.contentSize.height + apparentHeight = max(apparentHeight, 44.0) + apparentHeight = min(apparentHeight, containerSize.height) + self.apparentHeight = apparentHeight + + return (containerSize.height, apparentHeight) + } + } + + final class ItemsNode: ASDisplayNode, ContextControllerItemsNode { + private let context: AccountContext + private let languages: [(String, String)] + private let requestUpdate: (ContainedViewLayoutTransition) -> Void + private let requestUpdateApparentHeight: (ContainedViewLayoutTransition) -> Void + + private var presentationData: PresentationData + + private var backButtonNode: BackButtonNode? + private var separatorNode: ASDisplayNode? + + private let currentTabIndex: Int = 0 + private var visibleTabNodes: [Int: LanguagesListNode] = [:] + + private let selectLanguage: (String) -> Void + + private(set) var apparentHeight: CGFloat = 0.0 + + init( + context: AccountContext, + languages: [(String, String)], + requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void, + requestUpdateApparentHeight: @escaping (ContainedViewLayoutTransition) -> Void, + back: (() -> Void)?, + selectLanguage: @escaping (String) -> Void + ) { + self.context = context + self.languages = languages + self.selectLanguage = selectLanguage + self.presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + + self.requestUpdate = requestUpdate + self.requestUpdateApparentHeight = requestUpdateApparentHeight + + if let back = back { + self.backButtonNode = BackButtonNode() + self.backButtonNode?.action = { + back() + } + } + + super.init() + + if self.backButtonNode != nil { + self.separatorNode = ASDisplayNode() + } + + if let backButtonNode = self.backButtonNode { + self.addSubnode(backButtonNode) + } + if let separatorNode = self.separatorNode { + self.addSubnode(separatorNode) + } + } + + func update(presentationData: PresentationData, constrainedWidth: CGFloat, maxHeight: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> (cleanSize: CGSize, apparentHeight: CGFloat) { + let constrainedSize = CGSize(width: min(220.0, constrainedWidth), height: min(604.0, maxHeight)) + + var topContentHeight: CGFloat = 0.0 + if let backButtonNode = self.backButtonNode { + let backButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: 44.0)) + backButtonNode.update(size: backButtonFrame.size, presentationData: self.presentationData, isLast: true) + transition.updateFrame(node: backButtonNode, frame: backButtonFrame) + topContentHeight += backButtonFrame.height + } + if let separatorNode = self.separatorNode { + let separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: separatorHeight)) + separatorNode.backgroundColor = self.presentationData.theme.contextMenu.sectionSeparatorColor + transition.updateFrame(node: separatorNode, frame: separatorFrame) + topContentHeight += separatorFrame.height + } + + var tabLayouts: [Int: (height: CGFloat, apparentHeight: CGFloat)] = [:] + + var visibleIndices: [Int] = [] + visibleIndices.append(self.currentTabIndex) + + let previousVisibleTabFrames: [(Int, CGRect)] = self.visibleTabNodes.map { key, value -> (Int, CGRect) in + return (key, value.frame) + } + + for index in visibleIndices { + var tabTransition = transition + let tabNode: LanguagesListNode + var initialReferenceFrame: CGRect? + if let current = self.visibleTabNodes[index] { + tabNode = current + } else { + for (previousIndex, previousFrame) in previousVisibleTabFrames { + if index > previousIndex { + initialReferenceFrame = previousFrame.offsetBy(dx: constrainedSize.width, dy: 0.0) + } else { + initialReferenceFrame = previousFrame.offsetBy(dx: -constrainedSize.width, dy: 0.0) + } + break + } + + tabNode = LanguagesListNode( + context: self.context, + languages: self.languages, + requestUpdate: { [weak self] tab, transition in + guard let strongSelf = self else { + return + } + if strongSelf.visibleTabNodes.contains(where: { $0.value === tab }) { + strongSelf.requestUpdate(transition) + } + }, + requestUpdateApparentHeight: { [weak self] tab, transition in + guard let strongSelf = self else { + return + } + if strongSelf.visibleTabNodes.contains(where: { $0.value === tab }) { + strongSelf.requestUpdateApparentHeight(transition) + } + }, + selectLanguage: self.selectLanguage + ) + self.addSubnode(tabNode) + self.visibleTabNodes[index] = tabNode + tabTransition = .immediate + } + + let tabLayout = tabNode.update(presentationData: presentationData, constrainedSize: CGSize(width: constrainedSize.width, height: constrainedSize.height - topContentHeight), bottomInset: bottomInset, transition: tabTransition) + tabLayouts[index] = tabLayout + let currentFractionalTabIndex = CGFloat(self.currentTabIndex) + let xOffset: CGFloat = (CGFloat(index) - currentFractionalTabIndex) * constrainedSize.width + let tabFrame = CGRect(origin: CGPoint(x: xOffset, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: tabLayout.height)) + tabTransition.updateFrame(node: tabNode, frame: tabFrame) + if let initialReferenceFrame = initialReferenceFrame { + transition.animatePositionAdditive(node: tabNode, offset: CGPoint(x: initialReferenceFrame.minX - tabFrame.minX, y: 0.0)) + } + } + + var contentSize = CGSize(width: constrainedSize.width, height: topContentHeight) + var apparentHeight = topContentHeight + + if let tabLayout = tabLayouts[self.currentTabIndex] { + contentSize.height += tabLayout.height + apparentHeight += tabLayout.apparentHeight + } + + return (contentSize, apparentHeight) + } + } + + let context: AccountContext + let languages: [(String, String)] + let back: (() -> Void)? + let selectLanguage: (String) -> Void + + public init( + context: AccountContext, + languages: [(String, String)], + back: (() -> Void)?, + selectLanguage: @escaping (String) -> Void + ) { + self.context = context + self.languages = languages + self.back = back + self.selectLanguage = selectLanguage + } + + func node( + requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void, + requestUpdateApparentHeight: @escaping (ContainedViewLayoutTransition) -> Void + ) -> ContextControllerItemsNode { + return ItemsNode( + context: self.context, + languages: self.languages, + requestUpdate: requestUpdate, + requestUpdateApparentHeight: requestUpdateApparentHeight, + back: self.back, + selectLanguage: self.selectLanguage + ) + } +} + +private func parseCocoonMenuTextEntities(_ input: String, emojiFileId: Int64) -> (String, [MessageTextEntity]) { + var output = "" + + var entities: [MessageTextEntity] = [] + + var i = input.startIndex + var outputCount = 0 + + func utf16Len(_ s: String) -> Int { + s.utf16.count + } + + func peek(_ offset: Int) -> Character? { + var idx = i + for _ in 0.. start { + entities.append(MessageTextEntity(range: start.. start { + entities.append(MessageTextEntity(range: start.. Void + public let toggle: () -> Void + public let changeLanguage: (String) -> Void + public let addDoNotTranslateLanguage: (String) -> Void + public let controller: () -> ViewController? + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + info: Info, + close: @escaping () -> Void, + toggle: @escaping () -> Void, + changeLanguage: @escaping (String) -> Void, + addDoNotTranslateLanguage: @escaping (String) -> Void, + controller: @escaping () -> ViewController? + ) { + self.context = context + self.theme = theme + self.strings = strings + self.info = info + self.close = close + self.toggle = toggle + self.changeLanguage = changeLanguage + self.addDoNotTranslateLanguage = addDoNotTranslateLanguage + self.controller = controller + } + + public static func ==(lhs: TranslateHeaderPanelComponent, rhs: TranslateHeaderPanelComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.info != rhs.info { + return false + } + return true + } + + public final class View: UIView { + private var panel: ChatTranslationPanelNode? + + private var component: TranslateHeaderPanelComponent? + private weak var state: EmptyComponentState? + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func update(component: TranslateHeaderPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let panel: ChatTranslationPanelNode + if let current = self.panel { + panel = current + } else { + panel = ChatTranslationPanelNode( + context: component.context, + close: component.close, + toggle: component.toggle, + changeLanguage: component.changeLanguage, + addDoNotTranslateLanguage: component.addDoNotTranslateLanguage, + controller: component.controller + ) + self.panel = panel + self.addSubview(panel.view) + } + + let size = CGSize(width: availableSize.width, height: 40.0) + let panelFrame = CGRect(origin: CGPoint(), size: size) + transition.setFrame(view: panel.view, frame: panelFrame) + let _ = panel.updateLayout( + width: panelFrame.width, + info: component.info, + theme: component.theme, + strings: component.strings, + transition: transition.containedViewLayoutTransition + ) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputClearIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputClearIcon.imageset/Contents.json index d4b20938..ea33d1b7 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputClearIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputClearIcon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "backspace_24.svg", + "filename" : "backspace_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputClearIcon.imageset/backspace_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputClearIcon.imageset/backspace_30.pdf new file mode 100644 index 00000000..387f2711 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputClearIcon.imageset/backspace_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/Contents.json index a4c1cddb..55a8ba0a 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "keyboard_24.svg", + "filename" : "keyboard_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/keyboard_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/keyboard_30.pdf new file mode 100644 index 00000000..729a5745 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/keyboard_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSettingsIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSettingsIcon.imageset/Contents.json index 92f837ea..f0b8b677 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSettingsIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSettingsIcon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "keyboard_2444.svg", + "filename" : "settings_30 (4).pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSettingsIcon.imageset/settings_30 (4).pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSettingsIcon.imageset/settings_30 (4).pdf new file mode 100644 index 00000000..38a6fcde Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSettingsIcon.imageset/settings_30 (4).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/Contents.json index 5a3751c0..94aac7db 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Group 1.svg", + "filename" : "trending_44.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/trending_44.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/trending_44.pdf new file mode 100644 index 00000000..0ce40466 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/trending_44.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/CollapseIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/CollapseIcon.imageset/Contents.json new file mode 100644 index 00000000..d6b25de3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/CollapseIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "summary.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/CollapseIcon.imageset/summary.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/CollapseIcon.imageset/summary.pdf new file mode 100644 index 00000000..f50bced8 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/CollapseIcon.imageset/summary.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpandIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpandIcon.imageset/Contents.json new file mode 100644 index 00000000..13001193 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpandIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "original.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpandIcon.imageset/original.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpandIcon.imageset/original.pdf new file mode 100644 index 00000000..9f7203cc Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpandIcon.imageset/original.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/Contents.json index bfad7e17..eac666dc 100644 --- a/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_left.pdf", + "filename" : "browser_back_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/browser_back_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/browser_back_30.pdf new file mode 100644 index 00000000..16eb92b0 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/browser_back_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Contents.json index 09b651c2..6193eeb8 100644 --- a/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Bookmark.pdf", + "filename" : "browser_bookmark_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/browser_bookmark_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/browser_bookmark_30.pdf new file mode 100644 index 00000000..3640d0ba Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/browser_bookmark_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Contents.json index c45b00a1..2a075937 100644 --- a/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Browser.pdf", + "filename" : "browser_safari_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/browser_safari_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/browser_safari_30.pdf new file mode 100644 index 00000000..6ed6502e Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/browser_safari_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/Contents.json index 12dcf45c..b79b78d9 100644 --- a/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_right.pdf", + "filename" : "browser_forward_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/browser_forward_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/browser_forward_30.pdf new file mode 100644 index 00000000..162c2090 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/browser_forward_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/Contents.json index c60877c4..223317f3 100644 --- a/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "docviewer_24.pdf", + "filename" : "browser_doc_30.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/browser_doc_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/browser_doc_30.pdf new file mode 100644 index 00000000..d0d4f471 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/browser_doc_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/Contents.json new file mode 100644 index 00000000..17c8b580 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "browser_search_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/browser_search_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/browser_search_30.pdf new file mode 100644 index 00000000..4a5636aa Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Search.imageset/browser_search_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/Contents.json new file mode 100644 index 00000000..e9078769 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "browser_share_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/browser_share_30.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/browser_share_30.pdf new file mode 100644 index 00000000..23c7c6d4 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Instant View/Share.imageset/browser_share_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Cocoon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Cocoon.imageset/Contents.json new file mode 100644 index 00000000..f3819ec9 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Cocoon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "cocoon.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Cocoon.imageset/cocoon.png b/submodules/TelegramUI/Images.xcassets/Premium/Cocoon.imageset/cocoon.png new file mode 100644 index 00000000..0d5c7455 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Cocoon.imageset/cocoon.png differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wearable.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wearable.imageset/Contents.json new file mode 100644 index 00000000..f235c7ea --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wearable.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "wear_30 (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wearable.imageset/wear_30 (2).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wearable.imageset/wear_30 (2).pdf new file mode 100644 index 00000000..a6b1772c Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wearable.imageset/wear_30 (2).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Dice.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Dice.imageset/Contents.json new file mode 100644 index 00000000..d45ae279 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Dice.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "dice.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Dice.imageset/dice.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Dice.imageset/dice.pdf new file mode 100644 index 00000000..43f73ca9 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Dice.imageset/dice.pdf differ diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index eab0c417..99b50385 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -1,5 +1,5 @@ import UIKit -import SwiftSignalKit +@preconcurrency import SwiftSignalKit import Display import TelegramCore import UserNotifications @@ -41,7 +41,8 @@ import MediaEditor import TelegramUIDeclareEncodables import ContextMenuScreen import MetalEngine -import RecaptchaEnterprise +import RecaptchaEnterpriseSDK +import NavigationBarImpl #if canImport(AppCenter) import AppCenter @@ -330,6 +331,10 @@ private func extractAccountManagerState(records: AccountRecordsView Void)? var declineImpl: (() -> Void)? - let controller = TermsOfServiceController(presentationData: presentationData, text: termsOfServiceUpdate.text, entities: termsOfServiceUpdate.entities, ageConfirmation: termsOfServiceUpdate.ageConfirmation, signingUp: false, accept: { proccedBot in + let controller = TermsOfServiceController(context: strongSelf.context, presentationData: presentationData, text: termsOfServiceUpdate.text, entities: termsOfServiceUpdate.entities, ageConfirmation: termsOfServiceUpdate.ageConfirmation, signingUp: false, accept: { proccedBot in acceptImpl?(proccedBot) }, decline: { declineImpl?() diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index b90870ac..abb4967a 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -124,6 +124,9 @@ import AdsInfoScreen import PostSuggestionsSettingsScreen import ChatSendStarsScreen import ChatSendAsContextMenu +import GlobalControlPanelsContext +import ComponentFlow +import ComponentDisplayAdapters extension ChatControllerImpl { func reloadChatLocation(chatLocation: ChatLocation, chatLocationContextHolder: Atomic, historyNode: ChatHistoryListNodeImpl, apply: @escaping ((ContainedViewLayoutTransition?) -> Void) -> Void) { @@ -228,17 +231,6 @@ extension ChatControllerImpl { } self.navigationBar?.userInfo = contentData.state.navigationUserInfo - if self.chatTitleView?.titleContent != contentData.state.chatTitleContent { - var animateTitleContents = false - if !synchronous, case let .messageOptions(_, _, info) = self.subject, case .reply = info { - animateTitleContents = true - } - if animateTitleContents && self.chatTitleView?.titleContent != nil { - self.chatTitleView?.animateLayoutTransition() - } - self.chatTitleView?.titleContent = contentData.state.chatTitleContent - } - if let infoAvatar = contentData.state.infoAvatar { switch infoAvatar { case let .peer(peer, imageOverride, contextActionIsEnabled, accessibilityLabel): @@ -381,6 +373,13 @@ extension ChatControllerImpl { if previousState.pinnedMessage != contentData.state.pinnedMessage { animated = true } + if previousState.translationState?.isEnabled != contentData.state.translationState?.isEnabled { + animated = true + } + if previousState.chatTitleContent != contentData.state.chatTitleContent { + animated = true + } + var transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate if let forceAnimationTransition { transition = forceAnimationTransition @@ -389,6 +388,22 @@ extension ChatControllerImpl { transition = .immediate } + if let chatTitleContent = contentData.state.chatTitleContent { + var titleTransition = ComponentTransition(transition) + if case .messageOptions = self.subject { + titleTransition = titleTransition.withAnimation(.none) + } + self.chatTitleView?.update( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + dateTimeFormat: self.presentationData.dateTimeFormat, + nameDisplayOrder: self.presentationData.nameDisplayOrder, + content: chatTitleContent, + transition: titleTransition + ) + } + self.updateChatPresentationInterfaceState(transition: transition, interactive: false, { presentationInterfaceState in var presentationInterfaceState = presentationInterfaceState presentationInterfaceState = presentationInterfaceState.updatedPeer({ _ in @@ -677,7 +692,7 @@ extension ChatControllerImpl { self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, statusBar: self.statusBar, backgroundNode: self.chatBackgroundNode, controller: self) - if let currentItem = self.tempVoicePlaylistCurrentItem { + if let currentItem = self.globalControlPanelsContext?.tempVoicePlaylistCurrentItem { self.chatDisplayNode.historyNode.voicePlaylistItemChanged(nil, currentItem) } @@ -988,7 +1003,15 @@ extension ChatControllerImpl { } if let errorText = errorText { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + let alertController = textAlertController( + context: strongSelf.context, + title: nil, + text: errorText, + actions: [ + TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) + ] + ) + strongSelf.present(alertController, in: .window(.root)) return } } @@ -1092,10 +1115,15 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() case let .businessLinkSetup(link): if messages.count > 1 { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.BusinessLink_AlertTextLimitText, actions: [ - TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) - ]), in: .window(.root)) - + let alertController = textAlertController( + context: strongSelf.context, + title: nil, + text: strongSelf.presentationData.strings.BusinessLink_AlertTextLimitText, + actions: [ + TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) + ] + ) + strongSelf.present(alertController, in: .window(.root)) return } @@ -1871,8 +1899,9 @@ extension ChatControllerImpl { titleString = self.presentationData.strings.Chat_DeletePaidMessageTon_Title textString = self.presentationData.strings.Chat_DeletePaidMessageTon_Text } - self.present(standardTextAlertController( - theme: AlertControllerTheme(presentationData: self.presentationData), + + let alertController = textAlertController( + context: self.context, title: titleString, text: textString, actions: [ @@ -1884,9 +1913,9 @@ extension ChatControllerImpl { }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}) ], - actionLayout: .vertical, - parseMarkdown: true - ), in: .window(.root)) + actionLayout: .vertical + ) + self.present(alertController, in: .window(.root)) } if let contextController { contextController.dismiss(completion: commit) @@ -3638,7 +3667,7 @@ extension ChatControllerImpl { } } - let controller = chatTextLinkEditController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: strongSelf.updatedPresentationData, account: strongSelf.context.account, text: text?.string ?? "", link: link, allowEmpty: true, apply: { [weak self] link in + let controller = chatTextLinkEditController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, text: text?.string ?? "", link: link, apply: { [weak self] link in if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange { if let link { if !link.isEmpty { @@ -4590,6 +4619,44 @@ extension ChatControllerImpl { }) } + var mediaPlayback = false + var liveLocationMode: GlobalControlPanelsContext.LiveLocationMode? + if case .standard = self.mode { + //mediaAccessoryPanelVisibility = .specific(size: .compact) + mediaPlayback = true + liveLocationMode = self.chatLocation.peerId.flatMap(GlobalControlPanelsContext.LiveLocationMode.peer) + } + + var groupCallPanelSource: EnginePeer.Id? + switch self.chatLocation { + case let .peer(peerId): + switch self.subject { + case .message, .none: + groupCallPanelSource = peerId + default: + break + } + case .replyThread, .customChatContents: + break + } + + let globalControlPanelsContext = GlobalControlPanelsContext( + context: self.context, + mediaPlayback: mediaPlayback, + liveLocationMode: liveLocationMode, + groupCalls: groupCallPanelSource, + chatListNotices: false + ) + self.globalControlPanelsContext = globalControlPanelsContext + self.globalControlPanelsContextStateDisposable = (globalControlPanelsContext.state + |> deliverOnMainQueue).startStrict(next: { [weak self] state in + guard let self else { + return + } + self.globalControlPanelsContextState = state + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + }) + self.displayNodeDidLoad() } @@ -4745,7 +4812,18 @@ extension ChatControllerImpl { return true } }) - strongSelf.chatTitleView?.inputActivities = (peerId, displayActivities) + strongSelf.chatTitleView?.updateActivities( + activities: ChatTitleComponent.Activities( + peerId: peerId, + items: displayActivities.map { item -> ChatTitleComponent.Activities.Item in + return ChatTitleComponent.Activities.Item( + peer: EnginePeer(item.0), + activity: item.1 + ) + } + ), + transition: .spring(duration: 0.4) + ) strongSelf.peerInputActivitiesPromise.set(.single(activities)) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift index e7be8359..807b2f12 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift @@ -361,49 +361,54 @@ extension ChatControllerImpl { strongSelf.recorderDataDisposable.set(nil) } else { let randomId = Int64.random(in: Int64.min ... Int64.max) - - let resource = LocalFileMediaResource(fileId: randomId) - strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData) - + let originalData = data.compressedData + let duration = data.duration let waveformBuffer: Data? = data.waveform - let correlationId = Int64.random(in: 0 ..< Int64.max) - var usedCorrelationId = false + // GHOSTGRAM: Check if Voice Morpher needs processing + let needsProcessing = VoiceMorpherManager.shared.isEnabled && VoiceMorpherManager.shared.selectedPreset != .disabled - var shouldAnimateMessageTransition = strongSelf.chatDisplayNode.shouldAnimateMessageTransition - if strongSelf.chatLocation.threadId == nil, let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = strongSelf.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { - shouldAnimateMessageTransition = false - } - - if shouldAnimateMessageTransition, let textInputPanelNode = strongSelf.chatDisplayNode.textInputPanelNode, let micButton = textInputPanelNode.micButton { - usedCorrelationId = true - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNodeImpl.Source.AudioMicInput(micButton: micButton)), initiated: { - guard let strongSelf = self else { - return + if needsProcessing { + // Show processing indicator + let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil)) + statusController.statusBar.statusBarStyle = strongSelf.presentationData.theme.rootController.statusBarStyle.style + strongSelf.present(statusController, in: .window(.root)) + + // Process async + VoiceMorpherEngine.shared.processOggData(originalData) { [weak self, weak statusController] result in + DispatchQueue.main.async { + statusController?.dismiss() + + guard let strongSelf = self else { return } + + let processedData: Data + switch result { + case .success(let data): + processedData = data + case .failure: + processedData = originalData // fallback to original on error + } + + strongSelf.finishSendingVoiceMessage( + randomId: randomId, + processedData: processedData, + duration: duration, + waveformBuffer: waveformBuffer, + viewOnce: viewOnce + ) } - strongSelf.audioRecorder.set(.single(nil)) - }) - } else { - strongSelf.audioRecorder.set(.single(nil)) - } - - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } - }) } - }, usedCorrelationId ? correlationId : nil) - - var attributes: [MessageAttribute] = [] - if viewOnce { - attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) + } else { + // No processing needed, send directly + strongSelf.finishSendingVoiceMessage( + randomId: randomId, + processedData: originalData, + duration: duration, + waveformBuffer: waveformBuffer, + viewOnce: viewOnce + ) } - strongSelf.sendMessages([.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])]) - strongSelf.recorderFeedback?.tap() strongSelf.recorderFeedback = nil strongSelf.recorderDataDisposable.set(nil) @@ -774,4 +779,50 @@ extension ChatControllerImpl { self.videoRecorderValue?.sendVideoRecording(silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect) } } + + // MARK: - GHOSTGRAM: Voice Morpher Helper + + private func finishSendingVoiceMessage( + randomId: Int64, + processedData: Data, + duration: Double, + waveformBuffer: Data?, + viewOnce: Bool + ) { + let resource = LocalFileMediaResource(fileId: randomId) + self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: processedData) + + let correlationId = Int64.random(in: 0 ..< Int64.max) + var usedCorrelationId = false + + var shouldAnimateMessageTransition = self.chatDisplayNode.shouldAnimateMessageTransition + if self.chatLocation.threadId == nil, let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + shouldAnimateMessageTransition = false + } + + if shouldAnimateMessageTransition, let textInputPanelNode = self.chatDisplayNode.textInputPanelNode, let micButton = textInputPanelNode.micButton { + usedCorrelationId = true + self.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNodeImpl.Source.AudioMicInput(micButton: micButton)), initiated: { [weak self] in + self?.audioRecorder.set(.single(nil)) + }) + } else { + self.audioRecorder.set(.single(nil)) + } + + self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } + }) + } + }, usedCorrelationId ? correlationId : nil) + + var attributes: [MessageAttribute] = [] + if viewOnce { + attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) + } + + self.sendMessages([.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(processedData.count), attributes: [.Audio(isVoice: true, duration: Int(duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: self.chatLocation.threadId, replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])]) + } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift index c6306556..7ea7d6cb 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift @@ -280,13 +280,19 @@ extension ChatControllerImpl { return } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { - }), - TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: { - beginClear(.scheduledMessages) - }) - ], parseMarkdown: true), in: .window(.root)) + let alertController = textAlertController( + context: strongSelf.context, + title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, + text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, + actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { + }), + TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: { + beginClear(.scheduledMessages) + }) + ] + ) + strongSelf.present(alertController, in: .window(.root)) })) } else { if let _ = canClearForMyself ?? canClearForEveryone { @@ -310,13 +316,19 @@ extension ChatControllerImpl { return } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: confirmationText, actions: [ - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { - }), - TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationAction, action: { - beginClear(.forEveryone) - }) - ], parseMarkdown: true), in: .window(.root)) + let alertController = textAlertController( + context: strongSelf.context, + title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, + text: confirmationText, + actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { + }), + TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationAction, action: { + beginClear(.forEveryone) + }) + ] + ) + strongSelf.present(alertController, in: .window(.root)) })) } if let canClearForMyself = canClearForMyself { @@ -330,13 +342,19 @@ extension ChatControllerImpl { items.append(ActionSheetButtonItem(title: text, color: .destructive, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() if mainPeer.id == context.account.peerId, let strongSelf = self { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { - }), - TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: { - beginClear(.forLocalPeer) - }) - ], parseMarkdown: true), in: .window(.root)) + let alertController = textAlertController( + context: strongSelf.context, + title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, + text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, + actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { + }), + TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: { + beginClear(.forLocalPeer) + }) + ] + ) + strongSelf.present(alertController, in: .window(.root)) } else { beginClear(.forLocalPeer) } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index ff318176..d4e1caea 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -17,6 +17,7 @@ import PremiumUI import TooltipUI import TopMessageReactions import TelegramNotices +import PresentationDataUtils extension ChatControllerImpl { func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, anyRecognizer: UIGestureRecognizer?, location: CGPoint?) -> Void { @@ -433,9 +434,15 @@ extension ChatControllerImpl { if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed { if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [ - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) - ]), in: .window(.root)) + let alertController = textAlertController( + context: strongSelf.context, + title: nil, + text: strongSelf.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, + actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) + ] + ) + strongSelf.present(alertController, in: .window(.root)) } return } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift index 481e550d..ca5ac6c4 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift @@ -16,7 +16,12 @@ extension ChatControllerImpl { break } } - let controller = factCheckAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, value: currentText, entities: currentEntities, apply: { [weak self] text, entities in + let controller = factCheckAlertController( + context: self.context, + updatedPresentationData: self.updatedPresentationData, + value: currentText, + entities: currentEntities, + apply: { [weak self] text, entities in guard let self else { return } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift index a97982eb..1c93cb42 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift @@ -357,7 +357,9 @@ extension ChatControllerImpl { }))) } - items.append(.separator) + if !items.isEmpty { + items.append(.separator) + } items.append(.action(ContextMenuActionItem(text: strings.Conversation_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor) }, action: { [weak self] action in diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index dafe1781..ea9c5938 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -14,6 +14,7 @@ import PresentationDataUtils import TelegramCallsUI import AttachmentUI import WebUI +import LegacyChatHeaderPanelComponent func updateChatPresentationInterfaceStateImpl( selfController: ChatControllerImpl, diff --git a/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift index 4fe8585e..fd0e74da 100644 --- a/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift @@ -14,6 +14,7 @@ import TelegramCore import SwiftSignalKit import UndoUI import ShareController +import LegacyChatHeaderPanelComponent private final class ChatBusinessLinkTitlePanelComponent: Component { let context: AccountContext diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f1c29167..6ba6d498 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -144,6 +144,9 @@ import FaceScanScreen import ChatThemeScreen import ChatTextInputPanelNode import ChatInputAccessoryPanel +import GlobalControlPanelsContext +import ChatSearchNavigationContentNode +import ChatAgeRestrictionAlertController public final class ChatControllerOverlayPresentationData { public let expandData: (ASDisplayNode?, () -> Void) @@ -293,7 +296,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let chatThemePromise = Promise() let chatWallpaperPromise = Promise() - var chatTitleView: ChatTitleView? + var chatTitleView: ChatNavigationBarTitleView? var leftNavigationButton: ChatNavigationButton? var rightNavigationButton: ChatNavigationButton? var secondaryRightNavigationButton: ChatNavigationButton? @@ -524,9 +527,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let joinChannelDisposable = MetaDisposable() var shouldDisplayDownButton = false - - var hasEmbeddedTitleContent = false - var isEmbeddedTitleContentHidden = false var chatLocationContextHolder: Atomic @@ -626,6 +626,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var lastPostedScheduledMessagesToastTimestamp: Double = 0.0 var postedScheduledMessagesEventsDisposable: Disposable? + var globalControlPanelsContext: GlobalControlPanelsContext? + var globalControlPanelsContextState: GlobalControlPanelsContext.State? + var globalControlPanelsContextStateDisposable: Disposable? + public init( context: AccountContext, chatLocation: ChatLocation, @@ -675,26 +679,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: useSharedAnimationPhase) self.wallpaperReady.set(self.chatBackgroundNode.isReady) - var locationBroadcastPanelSource: LocationBroadcastPanelSource - var groupCallPanelSource: GroupCallPanelSource - - switch chatLocation { - case let .peer(peerId): - locationBroadcastPanelSource = .peer(peerId) - switch subject { - case .message, .none: - groupCallPanelSource = .peer(peerId) - default: - groupCallPanelSource = .none - } - case .replyThread: - locationBroadcastPanelSource = .none - groupCallPanelSource = .none - case .customChatContents: - locationBroadcastPanelSource = .none - groupCallPanelSource = .none - } - var presentationData = context.sharedContext.currentPresentationData.with { $0 } if let forcedTheme = self.forcedTheme { presentationData = presentationData.withUpdated(theme: forcedTheme) @@ -707,7 +691,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.stickerSettings = ChatInterfaceStickerSettings() - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, greetingData: context.prefetchManager?.preloadedGreetingSticker, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, greetingData: context.prefetchManager?.preloadedGreetingSticker, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) if case let .customChatContents(customChatContents) = subject { switch customChatContents.kind { @@ -726,25 +710,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.presentationInterfaceStatePromise = ValuePromise(self.presentationInterfaceState) - var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none - if case .standard = mode { - mediaAccessoryPanelVisibility = .specific(size: .compact) - } else { - locationBroadcastPanelSource = .none - groupCallPanelSource = .none - } let navigationBarPresentationData: NavigationBarPresentationData? switch mode { case .inline, .standard(.embedded): navigationBarPresentationData = nil default: - navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false) + navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: false, hideBadge: false, style: .glass) } - self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.rootController.navigationBar.buttonColor) + self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.chat.inputPanel.panelControlColor) self.moreBarButton.isUserInteractionEnabled = true - super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource, groupCallPanelSource: groupCallPanelSource) + super.init(context: context, navigationBarPresentationData: navigationBarPresentationData) + + self._hasGlassStyle = true self.automaticallyControlPresentationContextLayout = false self.blocksBackgroundWhenInOverlay = true @@ -754,6 +733,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.ready.set(.never()) + self.chatBackgroundNode.isDarkUpdated = { [weak self] in + guard let self else { + return + } + self.updateStatusBarPresentation(animated: false) + } + self.scrollToTop = { [weak self] in guard let strongSelf = self, strongSelf.isNodeLoaded else { return @@ -800,12 +786,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G textString = strongSelf.presentationData.strings.QuickReply_ChatRemoveAwayMessage_Text } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: titleString, text: textString, actions: [ - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), - TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.QuickReply_ChatRemoveGeneric_DeleteAction, action: { [weak strongSelf] in - strongSelf?.dismiss() - }) - ]), in: .window(.root)) + let alertController = textAlertController( + context: strongSelf.context, + title: titleString, + text: textString, + actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.QuickReply_ChatRemoveGeneric_DeleteAction, action: { [weak strongSelf] in + strongSelf?.dismiss() + }) + ] + ) + strongSelf.present(alertController, in: .window(.root)) return false } @@ -817,12 +809,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let message = inputText.string if message != link.message || entities != link.entities { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Business_Links_AlertUnsavedText, actions: [ - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), - TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Business_Links_AlertUnsavedAction, action: { [weak strongSelf] in - strongSelf?.dismiss() - }) - ]), in: .window(.root)) + let alertController = textAlertController( + context: strongSelf.context, + title: nil, + text: strongSelf.presentationData.strings.Business_Links_AlertUnsavedText, + actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Business_Links_AlertUnsavedAction, action: { [weak strongSelf] in + strongSelf?.dismiss() + }) + ] + ) + strongSelf.present(alertController, in: .window(.root)) return false } @@ -1833,9 +1831,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed { if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [ - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) - ]), in: .window(.root)) + let alertController = textAlertController( + context: strongSelf.context, + title: nil, + text: strongSelf.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, + actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) + ] + ) + strongSelf.present(alertController, in: .window(.root)) } return } @@ -2448,6 +2452,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let controller = self.context.sharedContext.makeGiftOfferScreen( context: self.context, + updatedPresentationData: self.updatedPresentationData, gift: gift, peer: peer, amount: amount, @@ -2477,7 +2482,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if message.effectivelyIncoming(strongSelf.context.account.peerId) { switch buttonType { case 0: - let promptController = promptController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: strongSelf.updatedPresentationData, text: strongSelf.presentationData.strings.Chat_PostSuggestion_Reject_Title, titleFont: .bold, value: "", placeholder: strongSelf.presentationData.strings.Chat_PostSuggestion_Reject_Placeholder, characterLimit: 4096, apply: { value in + let promptController = promptController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, text: strongSelf.presentationData.strings.Chat_PostSuggestion_Reject_Title, titleFont: .bold, value: "", placeholder: strongSelf.presentationData.strings.Chat_PostSuggestion_Reject_Placeholder, characterLimit: 4096, apply: { value in guard let self else { return } @@ -2501,13 +2506,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let configuration = StarsSubscriptionConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) funds = (amount, amount.currency == .stars ? Int(configuration.channelMessageSuggestionStarsCommissionPermille) : Int(configuration.channelMessageSuggestionTonCommissionPermille)) } - - #if DEBUG - if "".isEmpty { - funds = nil - } - #endif - + var isAdmin = false if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = strongSelf.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { isAdmin = true @@ -4323,7 +4322,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var message: Message? if let historyMessage = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { message = historyMessage - } else if let panelMessage = self.chatDisplayNode.adPanelNode?.message, panelMessage.id == messageId { + } else if let panelMessage = self.chatDisplayNode.adPanelMessage, panelMessage.id == messageId { message = panelMessage } @@ -4585,7 +4584,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G adOpaqueId = adAttribute.opaqueId } } - if adOpaqueId == nil, let panelMessage = self.chatDisplayNode.adPanelNode?.message, let adAttribute = panelMessage.adAttribute { + if adOpaqueId == nil, let panelMessage = self.chatDisplayNode.adPanelMessage, let adAttribute = panelMessage.adAttribute { adOpaqueId = adAttribute.opaqueId } let _ = self.context.engine.accountData.updateAdMessagesEnabled(enabled: false).start() @@ -5177,14 +5176,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return true } - self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer) - - if case .messageOptions = self.subject { - self.chatTitleView?.disableAnimations = true - } + self.chatTitleView = ChatNavigationBarTitleView(frame: CGRect()) self.navigationItem.titleView = self.chatTitleView - self.chatTitleView?.longPressed = { [weak self] in + self.chatTitleView?.longTapAction = { [weak self] in if let strongSelf = self, let peerView = strongSelf.contentData?.state.peerView, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible { if case .standard(.previewing) = strongSelf.mode { } else { @@ -5570,7 +5565,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside) self.navigationItem.titleView = self.chatTitleView - self.chatTitleView?.pressed = { [weak self] in + self.chatTitleView?.tapAction = { [weak self] in self?.navigationButtonAction(.openChatInfo(expandAvatar: false, section: nil)) } @@ -6136,7 +6131,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.networkStateDisposable = (context.account.networkState |> deliverOnMainQueue).startStrict(next: { [weak self] state in if let strongSelf = self, case .standard(.default) = strongSelf.presentationInterfaceState.mode { - strongSelf.chatTitleView?.networkState = state + strongSelf.chatTitleView?.updateNetworkState(networkState: state, transition: .spring(duration: 0.4)) } }) @@ -6241,6 +6236,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.newTopicEventsDisposable?.dispose() self.updateMessageTodoDisposables?.dispose() self.preloadNextChatPeerIdDisposable.dispose() + self.globalControlPanelsContextStateDisposable?.dispose() } public func updatePresentationMode(_ mode: ChatControllerPresentationMode) { @@ -6324,7 +6320,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .embedded: self.statusBar.statusBarStyle = .Ignore default: - self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + if let isDark = self.chatDisplayNode.backgroundNode.isDark { + if isDark { + self.statusBar.statusBarStyle = .White + } else { + self.statusBar.statusBarStyle = .Black + } + } else { + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + } self.deferScreenEdgeGestures = [] } case .overlay: @@ -6360,18 +6364,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let presentationTheme: PresentationTheme if let forcedNavigationBarTheme = self.forcedNavigationBarTheme { presentationTheme = forcedNavigationBarTheme - navigationBarTheme = NavigationBarTheme(rootControllerTheme: forcedNavigationBarTheme, hideBackground: false, hideBadge: true) - } else if self.hasEmbeddedTitleContent { - presentationTheme = self.presentationData.theme - navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: true) + navigationBarTheme = NavigationBarTheme(rootControllerTheme: forcedNavigationBarTheme, hideBackground: false, hideBadge: true, edgeEffectColor: .clear, style: .glass) } else { presentationTheme = self.presentationData.theme - navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false) + navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: false, hideBadge: false, edgeEffectColor: .clear, style: .glass) } - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)), transition: .immediate) - self.chatTitleView?.updateThemeAndStrings(theme: presentationTheme, strings: self.presentationData.strings, hasEmbeddedTitleContent: self.hasEmbeddedTitleContent) + self.moreBarButton.updateColor(color: presentationTheme.chat.inputPanel.panelControlColor) } enum PinnedReferenceMessage { @@ -6790,9 +6791,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - if !chatNavigationStack.isEmpty { - self.chatDisplayNode.navigationBar?.backButtonNode.isGestureEnabled = true - self.chatDisplayNode.navigationBar?.backButtonNode.activated = { [weak self] gesture, _ in + if !chatNavigationStack.isEmpty, let backButtonNode = self.chatDisplayNode.navigationBar?.backButtonNode as? ContextControllerSourceNode { + backButtonNode.isGestureEnabled = true + backButtonNode.activated = { [weak self] gesture, _ in guard let strongSelf = self, let backButtonNode = strongSelf.chatDisplayNode.navigationBar?.backButtonNode, let navigationController = strongSelf.effectiveNavigationController else { gesture.cancel() return @@ -6849,7 +6850,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case .standard(.default) = self.presentationInterfaceState.mode, self.raiseToListen == nil { self.raiseToListen = RaiseToListenManager(shouldActivate: { [weak self] in - if let strongSelf = self, strongSelf.isNodeLoaded && strongSelf.canReadHistoryValue, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil, strongSelf.playlistStateAndType == nil { + if let strongSelf = self, strongSelf.isNodeLoaded && strongSelf.canReadHistoryValue, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil, strongSelf.globalControlPanelsContext?.playlistStateAndType == nil { if !strongSelf.context.sharedContext.currentMediaInputSettings.with({ $0.enableRaiseToSpeak }) { return false } @@ -6890,7 +6891,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.deactivateRaiseGesture() }) self.raiseToListen?.enabled = self.canReadHistoryValue - self.tempVoicePlaylistEnded = { [weak self] in + self.globalControlPanelsContext?.setTempVoicePlaylistEnded({ [weak self] in guard let strongSelf = self else { return } @@ -6907,14 +6908,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.returnInputViewFocus = false strongSelf.chatDisplayNode.ensureInputViewFocused() } - } - self.tempVoicePlaylistItemChanged = { [weak self] previousItem, currentItem in + }) + self.globalControlPanelsContext?.setTempVoicePlaylistItemChanged({ [weak self] previousItem, currentItem in guard let strongSelf = self else { return } strongSelf.chatDisplayNode.historyNode.voicePlaylistItemChanged(previousItem, currentItem) - } + }) } if let arguments = self.presentationArguments as? ChatControllerOverlayPresentationData { @@ -7189,24 +7190,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G controller.navigationPresentation = .modal controller.setState(.custom(icon: .animation("BroadcastGroup"), title: presentationData.strings.BroadcastGroups_IntroTitle, subtitle: nil, text: presentationData.strings.BroadcastGroups_IntroText, buttonTitle: presentationData.strings.BroadcastGroups_Convert, secondaryButtonTitle: presentationData.strings.BroadcastGroups_Cancel, footerText: nil), animated: false) controller.proceed = { [weak controller] result in - let attributedTitle = NSAttributedString(string: presentationData.strings.BroadcastGroups_ConfirmationAlert_Title, font: Font.semibold(presentationData.listsFontSize.baseDisplaySize), textColor: presentationData.theme.actionSheet.primaryTextColor, paragraphAlignment: .center) - let body = MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: presentationData.theme.actionSheet.primaryTextColor) - let bold = MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: presentationData.theme.actionSheet.primaryTextColor) - let attributedText = parseMarkdownIntoAttributedString(presentationData.strings.BroadcastGroups_ConfirmationAlert_Text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - let alertController = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - let _ = context.engine.notices.dismissPeerSpecificServerProvidedSuggestion(peerId: peerId, suggestion: .convertToGigagroup).startStandalone() - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.BroadcastGroups_ConfirmationAlert_Convert, action: { [weak controller] in - controller?.dismiss() - - let _ = context.engine.notices.dismissPeerSpecificServerProvidedSuggestion(peerId: peerId, suggestion: .convertToGigagroup).startStandalone() - - let _ = (convertGroupToGigagroup(account: context.account, peerId: peerId) - |> deliverOnMainQueue).startStandalone(completed: { - let participantsLimit = context.currentLimitsConfiguration.with { $0 }.maxSupergroupMemberCount - strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .gigagroupConversion(text: presentationData.strings.BroadcastGroups_Success(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.decimalSeparator)).string), elevatedLayout: false, action: { _ in return false }), in: .current) - }) - })]) + let alertController = textAlertController( + context: context, + title: presentationData.strings.BroadcastGroups_ConfirmationAlert_Title, + text: presentationData.strings.BroadcastGroups_ConfirmationAlert_Text, + actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + let _ = context.engine.notices.dismissPeerSpecificServerProvidedSuggestion(peerId: peerId, suggestion: .convertToGigagroup).startStandalone() + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.BroadcastGroups_ConfirmationAlert_Convert, action: { [weak controller] in + controller?.dismiss() + + let _ = context.engine.notices.dismissPeerSpecificServerProvidedSuggestion(peerId: peerId, suggestion: .convertToGigagroup).startStandalone() + + let _ = (convertGroupToGigagroup(account: context.account, peerId: peerId) + |> deliverOnMainQueue).startStandalone(completed: { + let participantsLimit = context.currentLimitsConfiguration.with { $0 }.maxSupergroupMemberCount + strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .gigagroupConversion(text: presentationData.strings.BroadcastGroups_Success(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.decimalSeparator)).string), elevatedLayout: false, action: { _ in return false }), in: .current) + }) + }) + ] + ) controller?.present(alertController, in: .window(.root)) } strongSelf.push(controller) @@ -7235,7 +7239,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.storedAnimateFromSnapshotState = nil if let titleViewSnapshotState = snapshotState.titleViewSnapshotState { - self.chatTitleView?.animateFromSnapshot(titleViewSnapshotState) + self.chatTitleView?.animateFromSnapshot(titleViewSnapshotState, direction: .up) } if let avatarSnapshotState = snapshotState.avatarSnapshotState { (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.animateFromSnapshot(avatarSnapshotState) @@ -7299,6 +7303,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G override public func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + if #available(iOS 18.0, *) { + } else { + //TODO:release + } UIView.performWithoutAnimation { self.view.endEditing(true) } @@ -7434,7 +7442,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G super.containerLayoutUpdated(layout, transition: transition) self.validLayout = layout - self.chatTitleView?.layout = layout switch self.presentationInterfaceState.mode { case .standard, .inline: @@ -8081,64 +8088,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.presentEmojiList(references: [stickerPackReference], previewIconFile: previewIconFile) } } - - func displayDiceTooltip(dice: TelegramMediaDice) { - guard let _ = dice.value else { - return - } - self.window?.forEachController({ controller in - if let controller = controller as? UndoOverlayController { - controller.dismissWithCommitAction() - } - }) - self.forEachController({ controller in - if let controller = controller as? UndoOverlayController { - controller.dismissWithCommitAction() - } - return true - }) - let value: String? - let emoji = dice.emoji.strippedEmoji - switch emoji { - case "๐ŸŽฒ": - value = self.presentationData.strings.Conversation_Dice_u1F3B2 - case "๐ŸŽฏ": - value = self.presentationData.strings.Conversation_Dice_u1F3AF - case "๐Ÿ€": - value = self.presentationData.strings.Conversation_Dice_u1F3C0 - case "โšฝ": - value = self.presentationData.strings.Conversation_Dice_u26BD - case "๐ŸŽฐ": - value = self.presentationData.strings.Conversation_Dice_u1F3B0 - case "๐ŸŽณ": - value = self.presentationData.strings.Conversation_Dice_u1F3B3 - default: - let emojiHex = emoji.unicodeScalars.map({ String(format:"%02x", $0.value) }).joined().uppercased() - let key = "Conversation.Dice.u\(emojiHex)" - if let string = self.presentationData.strings.primaryComponent.dict[key] { - value = string - } else if let string = self.presentationData.strings.secondaryComponent?.dict[key] { - value = string - } else { - value = nil - } - } - if let value = value { - self.present(UndoOverlayController(presentationData: self.presentationData, content: .dice(dice: dice, context: self.context, text: value, action: canSendMessagesToChat(self.presentationInterfaceState) ? self.presentationData.strings.Conversation_SendDice : nil), elevatedLayout: false, action: { [weak self] action in - if let self, canSendMessagesToChat(self.presentationInterfaceState), action == .undo { - self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in - guard let self else { - return - } - self.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: dice.emoji)), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])], postpone: postpone) - }) - } - return false - }), in: .current) - } - } - func transformEnqueueMessages(_ messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil, repeatPeriod: Int32? = nil, postpone: Bool = false) -> [EnqueueMessage] { var defaultThreadId: Int64? var defaultReplyMessageSubject: EngineMessageReplySubject? @@ -8470,9 +8420,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case let .customChatContents(customChatContents) = strongSelf.presentationInterfaceState.subject, let messageLimit = customChatContents.messageLimit { if let originalHistoryView = strongSelf.chatDisplayNode.historyNode.originalHistoryView, originalHistoryView.entries.count + mappedMessages.count > messageLimit { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_QuickReplyMediaMessageLimitReachedText(Int32(messageLimit)), actions: [ - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) - ]), in: .window(.root)) + let alertController = textAlertController( + context: strongSelf.context, + title: nil, + text: strongSelf.presentationData.strings.Chat_QuickReplyMediaMessageLimitReachedText(Int32(messageLimit)), + actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) + ] + ) + strongSelf.present(alertController, in: .window(.root)) return } } diff --git a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift index bbd1aa11..a8834e58 100644 --- a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift +++ b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift @@ -404,8 +404,8 @@ extension ChatControllerImpl { titleString = self.presentationData.strings.Chat_DeletePaidMessageTon_Title textString = self.presentationData.strings.Chat_DeletePaidMessageTon_Text } - self.present(standardTextAlertController( - theme: AlertControllerTheme(presentationData: self.presentationData), + self.present(textAlertController( + context: self.context, title: titleString, text: textString, actions: [ @@ -429,6 +429,7 @@ extension ChatControllerImpl { return } + if messageIds.count == 1, let message = messages.values.compactMap({ $0 }).first, let repeatAttribute = message.attributes.first(where: { $0 is ScheduledRepeatAttribute }) as? ScheduledRepeatAttribute { let commit = { [weak self] in guard let self else { @@ -449,8 +450,8 @@ extension ChatControllerImpl { deleteOneAction = self.presentationData.strings.ScheduledMessages_DeleteRepeatingActionSingle deleteAllAction = self.presentationData.strings.ScheduledMessages_DeleteRepeatingActionMultiple } - self.present(standardTextAlertController( - theme: AlertControllerTheme(presentationData: self.presentationData), + self.present(textAlertController( + context: self.context, title: title, text: text, actions: [ diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift index 31d439a2..3caadb68 100644 --- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -519,32 +519,44 @@ extension ChatControllerImpl { if case .reply = info { let titleContent: ChatTitleContent if case let .reply(hasQuote) = messageOptionsTitleInfo, hasQuote { - titleContent = .custom(strings.Chat_TitleQuoteSelection, subtitleText, false) + titleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Chat_TitleQuoteSelection))], subtitle: subtitleText, isEnabled: false) } else { - titleContent = .custom(strings.Chat_TitleReply, subtitleText, false) + titleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Chat_TitleReply))], subtitle: subtitleText, isEnabled: false) } strongSelf.state.chatTitleContent = titleContent } else if case .link = info { - strongSelf.state.chatTitleContent = .custom(strings.Chat_TitleLinkOptions, subtitleText, false) + strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Chat_TitleLinkOptions))], subtitle: subtitleText, isEnabled: false) } else if displayedCount == 1 { - strongSelf.state.chatTitleContent = .custom(strings.Conversation_ForwardOptions_ForwardTitleSingle, subtitleText, false) + strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Conversation_ForwardOptions_ForwardTitleSingle))], subtitle: subtitleText, isEnabled: false) } else { - strongSelf.state.chatTitleContent = .custom(strings.Conversation_ForwardOptions_ForwardTitle(Int32(displayedCount ?? 1)), subtitleText, false) + strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Conversation_ForwardOptions_ForwardTitle(Int32(displayedCount ?? 1))))], subtitle: subtitleText, isEnabled: false) } } else if let selectionState = configuration.selectionState { if selectionState.selectedIds.count > 0 { - strongSelf.state.chatTitleContent = .custom(strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count)), nil, false) + let rawText = strings.Conversation_SelectedMessagesFormat(Int32(selectionState.selectedIds.count)) + var items: [ChatTitleContent.TitleTextItem] = [] + if let range = rawText.range(of: "{}") { + if range.lowerBound != rawText.startIndex { + items.append(ChatTitleContent.TitleTextItem(id: AnyHashable("selection_0"), content: .text(String(rawText[rawText.startIndex ..< range.lowerBound])))) + } + items.append(ChatTitleContent.TitleTextItem(id: AnyHashable("selection_1"), content: .number(selectionState.selectedIds.count, minDigits: 1))) + if range.upperBound != rawText.endIndex { + items.append(ChatTitleContent.TitleTextItem(id: AnyHashable("selection_2"), content: .text(String(rawText[range.upperBound ..< rawText.endIndex])))) + } + } + + strongSelf.state.chatTitleContent = .custom(title: items, subtitle: nil, isEnabled: false) } else { if let reportReason = configuration.reportReason { - strongSelf.state.chatTitleContent = .custom(reportReason.title, strings.Conversation_SelectMessages, false) + strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(1), content: .text(reportReason.title))], subtitle: strings.Conversation_SelectMessages, isEnabled: false) } else { - strongSelf.state.chatTitleContent = .custom(strings.Conversation_SelectMessages, nil, false) + strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(2), content: .text(strings.Conversation_SelectMessages))], subtitle: nil, isEnabled: false) } } } else if let peer = peerViewMainPeer(peerView) { if case .pinnedMessages = configuration.subject { - strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) + strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1))))], subtitle: nil, isEnabled: false) } else if let channel = peer as? TelegramChannel, channel.isMonoForum { if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] { strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData( @@ -557,7 +569,7 @@ extension ChatControllerImpl { cachedData: nil ), customTitle: nil, customSubtitle: strings.Chat_Monoforum_Subtitle, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true) } else { - strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, true) + strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(channel.debugDisplayTitle))], subtitle: nil, isEnabled: true) } } else { strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) @@ -1549,7 +1561,7 @@ extension ChatControllerImpl { } strongSelf.state.infoAvatar = .emojiStatus(content: avatarContent, contextActionIsEnabled: infoContextActionIsEnabled) } else if chatLocation.threadId == EngineMessage.newTopicThreadId { - strongSelf.state.chatTitleContent = .custom(strongSelf.presentationData.strings.Chat_MessageHeaderBotNewThread, nil, false) + strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strongSelf.presentationData.strings.Chat_MessageHeaderBotNewThread))], subtitle: nil, isEnabled: false) strongSelf.state.infoAvatar = nil } else { strongSelf.state.chatTitleContent = .replyThread(type: replyThreadType, count: count) @@ -1742,11 +1754,11 @@ extension ChatControllerImpl { case let .quickReplyMessageInput(shortcut, shortcutType): switch shortcutType { case .generic: - self.state.chatTitleContent = .custom("\(shortcut)", nil, false) + self.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text("\(shortcut)"))], subtitle: nil, isEnabled: false) case .greeting: - self.state.chatTitleContent = .custom(strings.QuickReply_TitleGreetingMessage, nil, false) + self.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.QuickReply_TitleGreetingMessage))], subtitle: nil, isEnabled: false) case .away: - self.state.chatTitleContent = .custom(strings.QuickReply_TitleAwayMessage, nil, false) + self.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(strings.QuickReply_TitleAwayMessage))], subtitle: nil, isEnabled: false) } case let .businessLinkSetup(link): let linkUrl: String @@ -1756,10 +1768,10 @@ extension ChatControllerImpl { linkUrl = link.url } - self.state.chatTitleContent = .custom(link.title ?? strings.Business_Links_EditLinkTitle, linkUrl, false) + self.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(link.title ?? strings.Business_Links_EditLinkTitle))], subtitle: linkUrl, isEnabled: false) } } else { - self.state.chatTitleContent = .custom(" ", nil, false) + self.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(" "))], subtitle: nil, isEnabled: false) } self.peerDisposable = (peerView diff --git a/submodules/TelegramUI/Sources/ChatControllerDisplayDiceTooltip.swift b/submodules/TelegramUI/Sources/ChatControllerDisplayDiceTooltip.swift new file mode 100644 index 00000000..6b59404e --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatControllerDisplayDiceTooltip.swift @@ -0,0 +1,124 @@ +import Foundation +import AccountContext +import Postbox +import TelegramCore +import SwiftSignalKit +import Display +import TelegramPresentationData +import PresentationDataUtils +import UndoUI +import EmojiGameStakeScreen +import ChatPresentationInterfaceState +import TelegramStringFormatting + +extension ChatControllerImpl { + func presentEmojiGameStake() { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.EmojiGame()) + |> deliverOnMainQueue).start(next: { [weak self] gameInfo in + guard let self, case let .available(info) = gameInfo else { + return + } + let controller = EmojiGameStakeScreen( + context: self.context, + gameInfo: info, + completion: { [weak self] stake in + guard let self else { + return + } + self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in + guard let self else { + return + } + self.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: "๐ŸŽฒ", tonAmount: stake.value > 0 ? stake.value : nil)), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])], postpone: postpone) + }) + } + ) + self.push(controller) + }) + } + + func displayDiceTooltip(dice: TelegramMediaDice) { + guard let _ = dice.value else { + return + } + self.window?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitAction() + } + }) + self.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitAction() + } + return true + }) + + let emoji = dice.emoji.strippedEmoji + + + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.EmojiGame()) + |> deliverOnMainQueue).start(next: { [weak self] gameInfo in + guard let self else { + return + } + + let canSendMessages = canSendMessagesToChat(self.presentationInterfaceState) + let value: String? + var changeAction: String? + var tonAmount: Int64? + if canSendMessages, emoji == "๐ŸŽฒ", case let .available(info) = gameInfo { + let currentStake = info.previousStake + value = "\(self.presentationData.strings.Conversation_Dice_Stake) $ \(formatTonAmountText(currentStake, dateTimeFormat: self.presentationData.dateTimeFormat))" + changeAction = self.presentationData.strings.Conversation_Dice_Change + tonAmount = info.previousStake + } else { + switch emoji { + case "๐ŸŽฒ": + value = self.presentationData.strings.Conversation_Dice_u1F3B2 + case "๐ŸŽฏ": + value = self.presentationData.strings.Conversation_Dice_u1F3AF + case "๐Ÿ€": + value = self.presentationData.strings.Conversation_Dice_u1F3C0 + case "โšฝ": + value = self.presentationData.strings.Conversation_Dice_u26BD + case "๐ŸŽฐ": + value = self.presentationData.strings.Conversation_Dice_u1F3B0 + case "๐ŸŽณ": + value = self.presentationData.strings.Conversation_Dice_u1F3B3 + default: + let emojiHex = emoji.unicodeScalars.map({ String(format:"%02x", $0.value) }).joined().uppercased() + let key = "Conversation.Dice.u\(emojiHex)" + if let string = self.presentationData.strings.primaryComponent.dict[key] { + value = string + } else if let string = self.presentationData.strings.secondaryComponent?.dict[key] { + value = string + } else { + value = nil + } + } + } + if let value = value { + self.present(UndoOverlayController(presentationData: self.presentationData, content: .dice(dice: dice, context: self.context, text: value, action: canSendMessages ? self.presentationData.strings.Conversation_SendDice : nil, changeAction: changeAction), elevatedLayout: false, action: { [weak self] action in + if let self, canSendMessagesToChat(self.presentationInterfaceState) { + switch action { + case .undo: + self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in + guard let self else { + return + } + self.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: dice.emoji, tonAmount: tonAmount)), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])], postpone: postpone) + }) + case .info: + if let _ = changeAction { + self.presentEmojiGameStake() + } + default: + break + } + } + return false + }), in: .current) + } + }) + } +} diff --git a/submodules/TelegramUI/Sources/ChatControllerEditChat.swift b/submodules/TelegramUI/Sources/ChatControllerEditChat.swift index 50f1593c..acec0b49 100644 --- a/submodules/TelegramUI/Sources/ChatControllerEditChat.swift +++ b/submodules/TelegramUI/Sources/ChatControllerEditChat.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import PresentationDataUtils import QuickReplyNameAlertController import BusinessLinkNameAlertController +import ChatTitleView extension ChatControllerImpl { func editChat() { @@ -48,7 +49,16 @@ extension ChatControllerImpl { contentNode.setErrorText(errorText: self.presentationData.strings.QuickReply_ShortcutExistsInlineError) } } else { - self.chatTitleView?.titleContent = .custom("\(value)", nil, false) + self.chatTitleView?.update( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + dateTimeFormat: self.presentationData.dateTimeFormat, + nameDisplayOrder: self.presentationData.nameDisplayOrder, + content: .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text("\(value)"))], subtitle: nil, isEnabled: false), + transition: .immediate + ) + alertController?.view.endEditing(true) alertController?.dismissAnimated() @@ -66,22 +76,19 @@ extension ChatControllerImpl { var completion: ((String?) -> Void)? let alertController = businessLinkNameAlertController( context: self.context, - text: self.presentationData.strings.Business_Links_LinkNameTitle, - subtext: self.presentationData.strings.Business_Links_LinkNameText, value: currentValue, - characterLimit: 32, apply: { value in completion?(value) } ) completion = { [weak self, weak alertController] value in guard let self else { - alertController?.dismissAnimated() + alertController?.dismiss(completion: nil) return } if let value { if value == currentValue { - alertController?.dismissAnimated() + alertController?.dismiss(completion: nil) return } @@ -93,13 +100,21 @@ extension ChatControllerImpl { } else { linkUrl = link.url } - self.chatTitleView?.titleContent = .custom(value.isEmpty ? self.presentationData.strings.Business_Links_EditLinkTitle : value, linkUrl, false) + self.chatTitleView?.update( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + dateTimeFormat: self.presentationData.dateTimeFormat, + nameDisplayOrder: self.presentationData.nameDisplayOrder, + content: .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(value.isEmpty ? self.presentationData.strings.Business_Links_EditLinkTitle : value))], subtitle: linkUrl, isEnabled: false), + transition: .immediate + ) if case let .customChatContents(customChatContents) = self.subject { customChatContents.businessLinkUpdate(message: link.message, entities: link.entities, title: value.isEmpty ? nil : value) } alertController?.view.endEditing(true) - alertController?.dismissAnimated() + alertController?.dismiss(completion: nil) } } self.present(alertController, in: .window(.root)) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index e7efdff9..a1e76201 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -51,6 +51,16 @@ import ChatThemeScreen import ChatTextInputPanelNode import ChatInputAccessoryPanel import ChatMessageTextBubbleContentNode +import HeaderPanelContainerComponent +import MediaPlaybackHeaderPanelComponent +import LiveLocationHeaderPanelComponent +import TranslateHeaderPanelComponent +import AdPanelHeaderPanelComponent +import MessageFeeHeaderPanelComponent +import LegacyChatHeaderPanelComponent +import ChatSearchNavigationContentNode +import GroupCallHeaderPanelComponent +import PresentationDataUtils final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -119,7 +129,9 @@ class HistoryNodeContainer: ASDisplayNode { var isSecret: Bool { didSet { if self.isSecret != oldValue { - setLayerDisableScreenshots(self.layer, self.isSecret) + // MISC: Bypass screenshot protection if enabled + let shouldDisable = self.isSecret && !MiscSettingsManager.shared.shouldBypassScreenshotProtection + setLayerDisableScreenshots(self.layer, shouldDisable) } } } @@ -134,7 +146,9 @@ class HistoryNodeContainer: ASDisplayNode { super.init() if self.isSecret { - setLayerDisableScreenshots(self.layer, self.isSecret) + // MISC: Bypass screenshot protection if enabled + let shouldDisable = self.isSecret && !MiscSettingsManager.shared.shouldBypassScreenshotProtection + setLayerDisableScreenshots(self.layer, shouldDisable) } } } @@ -218,26 +232,29 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { private let inputPanelClippingNode: SparseNode let inputPanelBackgroundNode: NavigationBackgroundNode - private var navigationBarBackgroundContent: WallpaperBubbleBackgroundNode? - private var intrinsicInputPanelBackgroundNodeSize: CGSize? private var inputPanelBottomBackgroundSeparatorBaseOffset: CGFloat = 0.0 private var plainInputSeparatorAlpha: CGFloat? private var usePlainInputSeparator: Bool - private var chatImportStatusPanel: ChatImportStatusPanel? - - private(set) var adPanelNode: ChatAdPanelNode? - private(set) var feePanelNode: ChatFeePanelNode? + var adPanelMessage: Message? { + guard let headerPanelsComponentView = self.headerPanelsView?.view as? HeaderPanelContainerComponent.View else { + return nil + } + guard let adPanelView = headerPanelsComponentView.panel(forKey: AnyHashable("ad")) as? AdPanelHeaderPanelComponent.View else { + return nil + } + return adPanelView.message?._asMessage() + } private let titleAccessoryPanelContainer: ChatControllerTitlePanelNodeContainer - private var titleAccessoryPanelNode: ChatTitleAccessoryPanelNode? - - private var chatTranslationPanel: ChatTranslationPanelNode? + private var currentTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode? private var floatingTopicsPanelContainer: ChatControllerTitlePanelNodeContainer private var floatingTopicsPanel: (view: ComponentView, component: ChatFloatingTopicsPanel)? + private var headerPanelsView: ComponentView? + private var topBackgroundEdgeEffectNode: WallpaperEdgeEffectNode? private var bottomBackgroundEdgeEffectNode: WallpaperEdgeEffectNode? private(set) var inputPanelNode: ChatInputPanelNode? @@ -1091,7 +1108,18 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if self.inputPanelContainerNode.expansionFraction > 0.3 { statusBar.updateStatusBarStyle(.White, animated: true) } else { - statusBar.updateStatusBarStyle(self.chatPresentationInterfaceState.theme.rootController.statusBarStyle.style, animated: true) + let statusBarStyle: StatusBarStyle + if let isDark = self.backgroundNode.isDark { + if isDark { + statusBarStyle = .White + } else { + statusBarStyle = .Black + } + } else { + statusBarStyle = self.chatPresentationInterfaceState.theme.rootController.statusBarStyle.style + } + + statusBar.updateStatusBarStyle(statusBarStyle, animated: true) } self.controller?.deferScreenEdgeGestures = [] case .overlay: @@ -1318,7 +1346,275 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } self.containerLayoutAndNavigationBarHeight = (layout, navigationBarHeight) + var headerPanels: [HeaderPanelContainerComponent.Panel] = [] + if let mediaPlayback = self.controller?.globalControlPanelsContextState?.mediaPlayback { + headerPanels.append(HeaderPanelContainerComponent.Panel( + key: "media", + orderIndex: 0, + component: AnyComponent(MediaPlaybackHeaderPanelComponent( + context: self.context, + theme: self.chatPresentationInterfaceState.theme, + strings: self.chatPresentationInterfaceState.strings, + data: mediaPlayback, + controller: { [weak self] in + return self?.controller + } + ))) + ) + } + if let liveLocation = self.controller?.globalControlPanelsContextState?.liveLocation { + headerPanels.append(HeaderPanelContainerComponent.Panel( + key: "liveLocation", + orderIndex: 1, + component: AnyComponent(LiveLocationHeaderPanelComponent( + context: self.context, + theme: self.chatPresentationInterfaceState.theme, + strings: self.chatPresentationInterfaceState.strings, + data: liveLocation, + controller: { [weak self] in + return self?.controller + } + ))) + ) + } + if let groupCall = self.controller?.globalControlPanelsContextState?.groupCall { + headerPanels.append(HeaderPanelContainerComponent.Panel( + key: "groupCall", + orderIndex: 2, + component: AnyComponent(GroupCallHeaderPanelComponent( + context: self.context, + theme: self.chatPresentationInterfaceState.theme, + strings: self.chatPresentationInterfaceState.strings, + data: groupCall, + onTapAction: { [weak self] in + guard let self, let groupCall = self.controller?.globalControlPanelsContextState?.groupCall else { + return + } + self.controller?.joinGroupCall( + peerId: groupCall.peerId, + invite: nil, + activeCall: EngineGroupCallDescription( + id: groupCall.info.id, + accessHash: groupCall.info.accessHash, + title: groupCall.info.title, + scheduleTimestamp: groupCall.info.scheduleTimestamp, + subscribedToScheduled: groupCall.info.subscribedToScheduled, + isStream: groupCall.info.isStream + ) + ) + }, + onNotifyScheduledTapAction: { [weak self] in + guard let self, let controller = self.controller, let groupCall = self.controller?.globalControlPanelsContextState?.groupCall else { + return + } + if groupCall.info.scheduleTimestamp != nil && !groupCall.info.subscribedToScheduled { + let _ = self.context.engine.calls.toggleScheduledGroupCallSubscription(peerId: groupCall.peerId, reference: .id(id: groupCall.info.id, accessHash: groupCall.info.accessHash), subscribe: true).startStandalone() + + controller.controllerInteraction?.displayUndo( + .universal( + animation: "anim_set_notification", + scale: 0.06, + colors: [ + "Middle.Group 1.Fill 1": UIColor.white, + "Top.Group 1.Fill 1": UIColor.white, + "Bottom.Group 1.Fill 1": UIColor.white, + "EXAMPLE.Group 1.Fill 1": UIColor.white, + "Line.Group 1.Stroke 1": UIColor.white + ], + title: nil, + text: controller.presentationData.strings.Chat_ToastSubscribedToScheduledLiveStream_Text, + customUndoText: nil, + timeout: nil + ) + ) + } + } + ))) + ) + } + + var hasTranslationPanel = false + if let _ = self.chatPresentationInterfaceState.translationState, self.emptyType == nil { + if case .overlay = self.chatPresentationInterfaceState.mode { + } else if self.chatPresentationInterfaceState.renderedPeer?.peer?.restrictionText(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }) != nil { + } else if self.chatPresentationInterfaceState.search != nil { + } else { + hasTranslationPanel = true + } + } + + var displayAdPanel = false + if let _ = self.chatPresentationInterfaceState.adMessage { + if let chatHistoryState = self.chatPresentationInterfaceState.chatHistoryState, case .loaded(false, _) = chatHistoryState { + if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil && !self.chatPresentationInterfaceState.peerIsBlocked && self.chatPresentationInterfaceState.hasAtLeast3Messages { + displayAdPanel = true + } + } + } + if displayAdPanel, let adMessage = self.chatPresentationInterfaceState.adMessage { + headerPanels.append(HeaderPanelContainerComponent.Panel( + key: "ad", + orderIndex: 2, + component: AnyComponent(AdPanelHeaderPanelComponent( + context: self.context, + theme: self.chatPresentationInterfaceState.theme, + strings: self.chatPresentationInterfaceState.strings, + info: AdPanelHeaderPanelComponent.Info( + message: EngineMessage(adMessage) + ), + action: { [weak self] message in + guard let self else { + return + } + self.controllerInteraction.activateAdAction(message.id, nil, false, false) + }, + contextAction: { [weak self] message, sourceNode, gesture in + guard let self else { + return + } + self.controllerInteraction.adContextAction(message._asMessage(), sourceNode, gesture) + }, + close: { [weak self] in + guard let self, let adMessage = self.chatPresentationInterfaceState.adMessage else { + return + } + if self.context.isPremium, let adAttribute = adMessage.adAttribute { + self.controllerInteraction.removeAd(adAttribute.opaqueId) + } else { + self.controllerInteraction.openNoAdsDemo() + } + } + ))) + ) + } + + if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.currentTitleAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) { + self.currentTitleAccessoryPanelNode = titleAccessoryPanelNode + let panelKey = "\(type(of: titleAccessoryPanelNode))" + headerPanels.append(HeaderPanelContainerComponent.Panel( + key: panelKey, + orderIndex: 3, + component: AnyComponent(LegacyChatHeaderPanelComponent( + panelNode: titleAccessoryPanelNode, + interfaceState: self.chatPresentationInterfaceState + ))) + ) + } else { + self.currentTitleAccessoryPanelNode = nil + } + + var displayFeePanel: (value: Int64, peer: EnginePeer)? + if let chatHistoryState = self.chatPresentationInterfaceState.chatHistoryState, case .loaded(false, _) = chatHistoryState { + if let user = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo == nil { + if !self.chatPresentationInterfaceState.peerIsBlocked, let paidMessageStars = self.chatPresentationInterfaceState.contactStatus?.peerStatusSettings?.paidMessageStars, paidMessageStars.value > 0 { + displayFeePanel = (paidMessageStars.value, .user(user)) + } + } else if let removePaidMessageFeeData = self.chatPresentationInterfaceState.removePaidMessageFeeData { + displayFeePanel = (removePaidMessageFeeData.amount.value, removePaidMessageFeeData.peer) + } + } + if let displayFeePanel { + headerPanels.append(HeaderPanelContainerComponent.Panel( + key: "fee", + orderIndex: 4, + component: AnyComponent(MessageFeeHeaderPanelComponent( + context: self.context, + theme: self.chatPresentationInterfaceState.theme, + strings: self.chatPresentationInterfaceState.strings, + info: MessageFeeHeaderPanelComponent.Info( + value: displayFeePanel.value, + peer: displayFeePanel.peer + ), + removeFee: { [weak self] in + guard let self else { + return + } + self.controllerInteraction.openMessageFeeException() + }, + ))) + ) + } + + if hasTranslationPanel, let translationState = self.chatPresentationInterfaceState.translationState { + headerPanels.append(HeaderPanelContainerComponent.Panel( + key: "translate", + orderIndex: 5, + component: AnyComponent(TranslateHeaderPanelComponent( + context: self.context, + theme: self.chatPresentationInterfaceState.theme, + strings: self.chatPresentationInterfaceState.strings, + info: TranslateHeaderPanelComponent.Info( + isPremium: self.chatPresentationInterfaceState.isPremium, + isActive: translationState.isEnabled, + fromLang: translationState.fromLang, + toLang: translationState.toLang, + peer: (self.chatPresentationInterfaceState.renderedPeer?.chatMainPeer).flatMap(EnginePeer.init) + ), + close: { [weak self] in + guard let self else { + return + } + self.interfaceInteraction?.hideTranslationPanel() + }, + toggle: { [weak self] in + guard let self, let translationState = self.chatPresentationInterfaceState.translationState else { + return + } + self.interfaceInteraction?.toggleTranslation(translationState.isEnabled ? .original : .translated) + }, + changeLanguage: { [weak self] code in + guard let self else { + return + } + self.interfaceInteraction?.changeTranslationLanguage(code) + }, + addDoNotTranslateLanguage: { [weak self] code in + guard let self else { + return + } + self.interfaceInteraction?.addDoNotTranslateLanguage(code) + }, + controller: { [weak self] in + return self?.controller + } + ))) + ) + } + var floatingTopicsPanelInsets = UIEdgeInsets() + + var headerPanelsSize: CGSize? + if !headerPanels.isEmpty { + let headerPanelsView: ComponentView + var headerPanelsTransition = ComponentTransition(transition) + if let current = self.headerPanelsView { + headerPanelsView = current + } else { + headerPanelsTransition = headerPanelsTransition.withAnimation(.none) + headerPanelsView = ComponentView() + self.headerPanelsView = headerPanelsView + } + let headerPanelsSizeValue = headerPanelsView.update( + transition: headerPanelsTransition, + component: AnyComponent(HeaderPanelContainerComponent( + theme: self.chatPresentationInterfaceState.theme, + tabs: nil, + panels: headerPanels + )), + environment: {}, + containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height) + ) + headerPanelsSize = headerPanelsSizeValue + floatingTopicsPanelInsets.top += headerPanelsSizeValue.height + } else if let headerPanelsView = self.headerPanelsView { + self.headerPanelsView = nil + if let headerPanelsComponentView = headerPanelsView.view { + transition.updateAlpha(layer: headerPanelsComponentView.layer, alpha: 0.0, completion: { [weak headerPanelsComponentView] _ in + headerPanelsComponentView?.removeFromSuperview() + }) + } + } + var dismissedFloatingTopicsPanel: (view: ComponentView, component: ChatFloatingTopicsPanel)? var immediatelyLayoutFloatingTopicsNodeAndAnimateAppearance = false var didChangeFloatingTopicsPanel = false @@ -1337,215 +1633,15 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { switch floatingTopicsPanelComponent.location { case .side: - floatingTopicsPanelInsets.left = 72.0 + 8.0 + 8.0 + floatingTopicsPanelInsets.left += 72.0 + 10.0 + 10.0 case .top: - floatingTopicsPanelInsets.top = 40.0 + 8.0 + floatingTopicsPanelInsets.top += 40.0 + 10.0 } } else if let floatingTopicsPanel = self.floatingTopicsPanel { self.floatingTopicsPanel = nil dismissedFloatingTopicsPanel = floatingTopicsPanel } - var dismissedTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode? - var immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance = false - var titleAccessoryPanelHeight: CGFloat? - var titleAccessoryPanelBackgroundHeight: CGFloat? - var titleAccessoryPanelHitTestSlop: CGFloat? - - if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.titleAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) { - if self.titleAccessoryPanelNode != titleAccessoryPanelNode { - dismissedTitleAccessoryPanelNode = self.titleAccessoryPanelNode - self.titleAccessoryPanelNode = titleAccessoryPanelNode - immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance = true - self.titleAccessoryPanelContainer.addSubnode(titleAccessoryPanelNode) - - titleAccessoryPanelNode.clipsToBounds = true - } - - let layoutResult = titleAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState) - titleAccessoryPanelHeight = layoutResult.insetHeight - titleAccessoryPanelBackgroundHeight = layoutResult.backgroundHeight - titleAccessoryPanelHitTestSlop = layoutResult.hitTestSlop - if immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance { - if transition.isAnimated { - titleAccessoryPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - titleAccessoryPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -layoutResult.backgroundHeight, 0.0) - transition.updateSublayerTransformOffset(layer: titleAccessoryPanelNode.layer, offset: CGPoint()) - } - } else if let titleAccessoryPanelNode = self.titleAccessoryPanelNode { - dismissedTitleAccessoryPanelNode = titleAccessoryPanelNode - self.titleAccessoryPanelNode = nil - } - - var dismissedTranslationPanelNode: ChatTranslationPanelNode? - var immediatelyLayoutTranslationPanelNodeAndAnimateAppearance = false - var translationPanelHeight: CGFloat? - - var hasTranslationPanel = false - if let _ = self.chatPresentationInterfaceState.translationState, self.emptyType == nil { - if case .overlay = self.chatPresentationInterfaceState.mode { - } else if self.chatPresentationInterfaceState.renderedPeer?.peer?.restrictionText(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }) != nil { - } else if self.chatPresentationInterfaceState.search != nil { - } else { - hasTranslationPanel = true - } - } - - /*#if DEBUG - if "".isEmpty { - hasTranslationPanel = true - } - #endif*/ - - if hasTranslationPanel { - let translationPanelNode: ChatTranslationPanelNode - if let current = self.chatTranslationPanel { - translationPanelNode = current - } else { - translationPanelNode = ChatTranslationPanelNode(context: self.context) - } - translationPanelNode.interfaceInteraction = self.interfaceInteraction - - if self.chatTranslationPanel != translationPanelNode { - dismissedTranslationPanelNode = self.chatTranslationPanel - self.chatTranslationPanel = translationPanelNode - immediatelyLayoutTranslationPanelNodeAndAnimateAppearance = true - self.titleAccessoryPanelContainer.addSubnode(translationPanelNode) - - translationPanelNode.clipsToBounds = true - } - - let height = translationPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, leftDisplayInset: 0.0, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState) - translationPanelHeight = height - if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance { - translationPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - translationPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -height, 0.0) - transition.updateSublayerTransformOffset(layer: translationPanelNode.layer, offset: CGPoint()) - } - } else if let chatTranslationPanel = self.chatTranslationPanel { - dismissedTranslationPanelNode = chatTranslationPanel - self.chatTranslationPanel = nil - } - - var dismissedImportStatusPanelNode: ChatImportStatusPanel? - var importStatusPanelHeight: CGFloat? - if let importState = self.chatPresentationInterfaceState.importState { - let importStatusPanelNode: ChatImportStatusPanel - if let current = self.chatImportStatusPanel { - importStatusPanelNode = current - } else { - importStatusPanelNode = ChatImportStatusPanel() - } - - if self.chatImportStatusPanel != importStatusPanelNode { - dismissedImportStatusPanelNode = self.chatImportStatusPanel - self.chatImportStatusPanel = importStatusPanelNode - self.contentContainerNode.contentNode.addSubnode(importStatusPanelNode) - } - - importStatusPanelHeight = importStatusPanelNode.update(context: self.context, progress: CGFloat(importState.progress), presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: self.chatPresentationInterfaceState.theme, wallpaper: self.chatPresentationInterfaceState.chatWallpaper), fontSize: self.chatPresentationInterfaceState.fontSize, strings: self.chatPresentationInterfaceState.strings, dateTimeFormat: self.chatPresentationInterfaceState.dateTimeFormat, nameDisplayOrder: self.chatPresentationInterfaceState.nameDisplayOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), width: layout.size.width) - } else if let importStatusPanelNode = self.chatImportStatusPanel { - dismissedImportStatusPanelNode = importStatusPanelNode - self.chatImportStatusPanel = nil - } - - var dismissedAdPanelNode: ChatAdPanelNode? - var adPanelHeight: CGFloat? - - var displayAdPanel = false - if let _ = self.chatPresentationInterfaceState.adMessage { - if let chatHistoryState = self.chatPresentationInterfaceState.chatHistoryState, case .loaded(false, _) = chatHistoryState { - if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil && !self.chatPresentationInterfaceState.peerIsBlocked && self.chatPresentationInterfaceState.hasAtLeast3Messages { - displayAdPanel = true - } - } - } - - if displayAdPanel { - var animateAppearance = false - let adPanelNode: ChatAdPanelNode - if let current = self.adPanelNode { - adPanelNode = current - } else { - adPanelNode = ChatAdPanelNode(context: self.context, animationCache: self.controllerInteraction.presentationContext.animationCache, animationRenderer: self.controllerInteraction.presentationContext.animationRenderer) - adPanelNode.controllerInteraction = self.controllerInteraction - adPanelNode.clipsToBounds = true - animateAppearance = true - } - - if self.adPanelNode != adPanelNode { - dismissedAdPanelNode = self.adPanelNode - self.adPanelNode = adPanelNode - self.titleAccessoryPanelContainer.addSubnode(adPanelNode) - } - - let height = adPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition, interfaceState: self.chatPresentationInterfaceState) - if let adMessage = self.chatPresentationInterfaceState.adMessage, let opaqueId = adMessage.adAttribute?.opaqueId { - self.historyNode.markAdAsSeen(opaqueId: opaqueId) - } - - adPanelHeight = height - if transition.isAnimated && animateAppearance { - adPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - adPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -height, 0.0) - transition.updateSublayerTransformOffset(layer: adPanelNode.layer, offset: CGPoint()) - } - } else if let adPanelNode = self.adPanelNode { - dismissedAdPanelNode = adPanelNode - self.adPanelNode = nil - } - - var dismissedFeePanelNode: ChatFeePanelNode? - var feePanelHeight: CGFloat? - - var displayFeePanel = false - if let chatHistoryState = self.chatPresentationInterfaceState.chatHistoryState, case .loaded(false, _) = chatHistoryState { - if let user = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo == nil { - if !self.chatPresentationInterfaceState.peerIsBlocked, let paidMessageStars = self.chatPresentationInterfaceState.contactStatus?.peerStatusSettings?.paidMessageStars, paidMessageStars.value > 0 { - displayFeePanel = true - } - } else if self.chatPresentationInterfaceState.removePaidMessageFeeData != nil { - displayFeePanel = true - } - } - - var immediatelyLayoutFeePanelNodeAndAnimateAppearance = false - if displayFeePanel { - var animateAppearance = false - let feePanelNode: ChatFeePanelNode - if let current = self.feePanelNode { - feePanelNode = current - } else { - feePanelNode = ChatFeePanelNode(context: self.context) - feePanelNode.controllerInteraction = self.controllerInteraction - feePanelNode.clipsToBounds = true - animateAppearance = true - } - - if self.feePanelNode != feePanelNode { - dismissedFeePanelNode = self.feePanelNode - self.feePanelNode = feePanelNode - self.titleAccessoryPanelContainer.addSubnode(feePanelNode) - } - - let height = feePanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, leftDisplayInset: 0.0, transition: animateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState) - - feePanelHeight = height - if transition.isAnimated && animateAppearance { - immediatelyLayoutFeePanelNodeAndAnimateAppearance = true - } - - if immediatelyLayoutFeePanelNodeAndAnimateAppearance { - feePanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - feePanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -height, 0.0) - transition.updateSublayerTransformOffset(layer: feePanelNode.layer, offset: CGPoint()) - } - } else if let feePanelNode = self.feePanelNode { - dismissedFeePanelNode = feePanelNode - self.feePanelNode = nil - } - var isSidebarOpen = false if let floatingTopicsPanel = self.floatingTopicsPanel { isSidebarOpen = floatingTopicsPanel.component.location == .side @@ -1830,54 +1926,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { transition.updateFrame(node: self.inputContextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) transition.updateFrame(node: self.inputContextOverTextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) - - var extraNavigationBarHeight: CGFloat = 0.0 - var extraNavigationBarHitTestSlop: CGFloat = 0.0 - var titlePanelsContentOffset: CGFloat = 0.0 - - var titleAccessoryPanelFrame: CGRect? - let titleAccessoryPanelBaseY = titlePanelsContentOffset - if let _ = self.titleAccessoryPanelNode, let panelHeight = titleAccessoryPanelHeight { - titleAccessoryPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: titlePanelsContentOffset), size: CGSize(width: layout.size.width, height: panelHeight)) - insets.top += panelHeight - extraNavigationBarHeight += titleAccessoryPanelBackgroundHeight ?? 0.0 - extraNavigationBarHitTestSlop = titleAccessoryPanelHitTestSlop ?? 0.0 - titlePanelsContentOffset += panelHeight - } + updateExtraNavigationBarBackgroundHeight(0.0, 0.0, nil, transition) - let feePanelBaseY = titlePanelsContentOffset - - var translationPanelFrame: CGRect? - if let _ = self.chatTranslationPanel, let panelHeight = translationPanelHeight { - translationPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: extraNavigationBarHeight), size: CGSize(width: layout.size.width, height: panelHeight)) - insets.top += panelHeight - extraNavigationBarHeight += panelHeight - } - - var importStatusPanelFrame: CGRect? - if let _ = self.chatImportStatusPanel, let panelHeight = importStatusPanelHeight { - importStatusPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: panelHeight)) - insets.top += panelHeight - } - - var adPanelFrame: CGRect? - if let _ = self.adPanelNode, let panelHeight = adPanelHeight { - adPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: extraNavigationBarHeight), size: CGSize(width: layout.size.width, height: panelHeight)) - insets.top += panelHeight - extraNavigationBarHeight += panelHeight - } - - var feePanelFrame: CGRect? - if let _ = self.feePanelNode, let panelHeight = feePanelHeight { - feePanelFrame = CGRect(origin: CGPoint(x: 0.0, y: extraNavigationBarHeight), size: CGSize(width: layout.size.width, height: panelHeight)) - insets.top += panelHeight - extraNavigationBarHeight += panelHeight - } - - updateExtraNavigationBarBackgroundHeight(extraNavigationBarHeight, extraNavigationBarHitTestSlop, nil, transition) - - let sidePanelTopInset: CGFloat = insets.top + var sidePanelTopInset: CGFloat = insets.top + 4.0 let contentBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height - wrappingInsets.top - wrappingInsets.bottom) @@ -2129,7 +2181,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { additionalOffset = 80.0 } if let _ = inputPanelSize { - inputPanelHideOffset += -40.0 - additionalOffset + inputPanelHideOffset += -48.0 - additionalOffset } if let accessoryPanelSize = accessoryPanelSize { inputPanelHideOffset += -accessoryPanelSize.height - additionalOffset @@ -2146,7 +2198,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } if self.secondaryInputPanelNode != nil { - secondaryInputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - secondaryInputPanelSize!.height - 8.0), size: CGSize(width: layout.size.width, height: secondaryInputPanelSize!.height)) + secondaryInputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight - 8.0 - secondaryInputPanelSize!.height - 8.0), size: CGSize(width: layout.size.width, height: secondaryInputPanelSize!.height)) if self.dismissedAsOverlay { secondaryInputPanelFrame!.origin.y = layout.size.height } @@ -2199,12 +2251,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } if let bottomBackgroundEdgeEffectNode { var blurFrame = inputBackgroundFrame - blurFrame.origin.y -= 26.0 + blurFrame.origin.y -= 18.0 blurFrame.size.height = max(100.0, layout.size.height - blurFrame.origin.y) transition.updateFrame(node: bottomBackgroundEdgeEffectNode, frame: blurFrame) bottomBackgroundEdgeEffectNode.update( rect: blurFrame, - edge: WallpaperEdgeEffectEdge(edge: .bottom, size: 80.0), + edge: WallpaperEdgeEffectEdge(edge: .bottom, size: 100.0), + blur: false, containerSize: wallpaperBounds.size, transition: transition ) @@ -2328,15 +2381,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } if displayTopDimNode { - var topInset = listInsets.bottom + UIScreenPixel - if let titleAccessoryPanelHeight = titleAccessoryPanelHeight { - if expandTopDimNode { - topInset -= titleAccessoryPanelHeight - } else { - topInset -= UIScreenPixel - } - } - let inputPanelOrigin = layout.size.height - insets.bottom - bottomOverflowOffset - inputPanelsHeight if expandTopDimNode { @@ -2399,6 +2443,29 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } }) + var topBackgroundEdgeEffectNode: WallpaperEdgeEffectNode? + if let current = self.topBackgroundEdgeEffectNode { + topBackgroundEdgeEffectNode = current + } else { + if let value = self.backgroundNode.makeEdgeEffectNode() { + topBackgroundEdgeEffectNode = value + self.topBackgroundEdgeEffectNode = value + self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view) + } + } + if let topBackgroundEdgeEffectNode { + var blurFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(100.0, listInsets.bottom + 10.0))) + blurFrame.origin.y = listInsets.bottom + 10.0 - blurFrame.height + transition.updateFrame(node: topBackgroundEdgeEffectNode, frame: blurFrame) + topBackgroundEdgeEffectNode.update( + rect: blurFrame, + edge: WallpaperEdgeEffectEdge(edge: .top, size: 100.0), + blur: false, + containerSize: wallpaperBounds.size, + transition: transition + ) + } + if self.isScrollingLockedAtTop { switch self.historyNode.visibleContentOffset() { case let .known(value) where value <= CGFloat.ulpOfOne: @@ -2454,6 +2521,18 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { transition.updateBounds(node: self.inputPanelOverlayNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: layout.size), beginWithCurrentState: true) transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame, beginWithCurrentState: true) + if let headerPanelsComponentView = self.headerPanelsView?.view, let headerPanelsSize { + let headerPanelsFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: sidePanelTopInset), size: headerPanelsSize) + var headerPanelsTransition = ComponentTransition(transition) + if headerPanelsComponentView.superview == nil { + headerPanelsTransition.animateAlpha(view: headerPanelsComponentView, from: 0.0, to: 1.0) + headerPanelsTransition = headerPanelsTransition.withAnimation(.none) + self.floatingTopicsPanelContainer.view.addSubview(headerPanelsComponentView) + } + headerPanelsTransition.setFrame(view: headerPanelsComponentView, frame: headerPanelsFrame) + sidePanelTopInset += headerPanelsSize.height + 2.0 + } + let floatingTopicsPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 0.0, height: layout.size.height)) transition.updateFrame(node: self.floatingTopicsPanelContainer, frame: floatingTopicsPanelContainerFrame) if let floatingTopicsPanel = self.floatingTopicsPanel { @@ -2502,11 +2581,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { })*/ dismissedFloatingTopicsPanelView.removeFromSuperview() } - - if let navigationBarBackgroundContent = self.navigationBarBackgroundContent { - transition.updateFrame(node: navigationBarBackgroundContent, frame: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0) + (translationPanelHeight ?? 0.0))), beginWithCurrentState: true) - navigationBarBackgroundContent.update(rect: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0) + (translationPanelHeight ?? 0.0))), within: layout.size, transition: transition) - } transition.updateFrame(node: self.contentDimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: apparentInputBackgroundFrame.origin.y))) @@ -2517,47 +2591,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame) self.navigateButtons.update(rect: apparentNavigateButtonsFrame, within: layout.size, transition: transition) - - if let titleAccessoryPanelNode = self.titleAccessoryPanelNode, let titleAccessoryPanelFrame, !titleAccessoryPanelNode.frame.equalTo(titleAccessoryPanelFrame) { - let previousFrame = titleAccessoryPanelNode.frame - titleAccessoryPanelNode.frame = titleAccessoryPanelFrame - if transition.isAnimated && previousFrame.width != titleAccessoryPanelFrame.width { - } else if immediatelyLayoutAccessoryPanelAndAnimateAppearance { - transition.animatePositionAdditive(node: titleAccessoryPanelNode, offset: CGPoint(x: 0.0, y: -titleAccessoryPanelFrame.height)) - } else if previousFrame.minY != titleAccessoryPanelFrame.minY { - transition.animatePositionAdditive(node: titleAccessoryPanelNode, offset: CGPoint(x: 0.0, y: previousFrame.minY - titleAccessoryPanelFrame.minY)) - } - } - - if let chatTranslationPanel = self.chatTranslationPanel, let translationPanelFrame, !chatTranslationPanel.frame.equalTo(translationPanelFrame) { - let previousFrame = chatTranslationPanel.frame - chatTranslationPanel.frame = translationPanelFrame - if transition.isAnimated && previousFrame.width != translationPanelFrame.width { - } else if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance { - transition.animatePositionAdditive(node: chatTranslationPanel, offset: CGPoint(x: 0.0, y: -translationPanelFrame.height)) - } else if previousFrame.minY != translationPanelFrame.minY { - transition.animatePositionAdditive(node: chatTranslationPanel, offset: CGPoint(x: 0.0, y: previousFrame.minY - translationPanelFrame.minY)) - } - } - - if let chatImportStatusPanel = self.chatImportStatusPanel, let importStatusPanelFrame, !chatImportStatusPanel.frame.equalTo(importStatusPanelFrame) { - chatImportStatusPanel.frame = importStatusPanelFrame - } - - if let adPanelNode = self.adPanelNode, let adPanelFrame, !adPanelNode.frame.equalTo(adPanelFrame) { - adPanelNode.frame = adPanelFrame - } - - if let feePanelNode = self.feePanelNode, let feePanelFrame, !feePanelNode.frame.equalTo(feePanelFrame) { - let previousFrame = feePanelNode.frame - feePanelNode.frame = feePanelFrame - if transition.isAnimated && previousFrame.width != feePanelFrame.width { - } else if immediatelyLayoutFeePanelNodeAndAnimateAppearance { - transition.animatePositionAdditive(node: feePanelNode, offset: CGPoint(x: 0.0, y: -feePanelFrame.height)) - } else if previousFrame.minY != feePanelFrame.minY { - transition.animatePositionAdditive(node: feePanelNode, offset: CGPoint(x: 0.0, y: previousFrame.minY - feePanelFrame.minY)) - } - } if let secondaryInputPanelNode = self.secondaryInputPanelNode, let apparentSecondaryInputPanelFrame = apparentSecondaryInputPanelFrame, !secondaryInputPanelNode.frame.equalTo(apparentSecondaryInputPanelFrame) { if immediatelyLayoutSecondaryInputPanelAndAnimateAppearance { @@ -2599,10 +2632,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: insets.bottom + inputPanelsHeight + 8.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState) } - if !inputContextPanelNode.frame.equalTo(panelFrame) || inputContextPanelNode.theme !== self.chatPresentationInterfaceState.theme { - transition.updateFrame(node: inputContextPanelNode, frame: panelFrame) - inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: 0.0, transition: transition, interfaceState: self.chatPresentationInterfaceState) - } + transition.updateFrame(node: inputContextPanelNode, frame: panelFrame) + inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: insets.bottom + inputPanelsHeight + 8.0, transition: transition, interfaceState: self.chatPresentationInterfaceState) } if let overlayContextPanelNode = self.overlayContextPanelNode { @@ -2640,60 +2671,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - if let dismissedTitleAccessoryPanelNode { - var dismissedPanelFrame = dismissedTitleAccessoryPanelNode.frame - transition.updateSublayerTransformOffset(layer: dismissedTitleAccessoryPanelNode.layer, offset: CGPoint(x: 0.0, y: -dismissedPanelFrame.height)) - dismissedPanelFrame.origin.y = titleAccessoryPanelBaseY - dismissedTitleAccessoryPanelNode.clipsToBounds = true - dismissedPanelFrame.size.height = 0.0 - if transition.isAnimated { - dismissedTitleAccessoryPanelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - } - transition.updateFrame(node: dismissedTitleAccessoryPanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedTitleAccessoryPanelNode] _ in - dismissedTitleAccessoryPanelNode?.removeFromSupernode() - }) - } - - if let dismissedTranslationPanelNode { - var dismissedPanelFrame = dismissedTranslationPanelNode.frame - dismissedPanelFrame.origin.y = -dismissedPanelFrame.size.height - transition.updateAlpha(node: dismissedTranslationPanelNode, alpha: 0.0, completion: { [weak dismissedTranslationPanelNode] _ in - dismissedTranslationPanelNode?.removeFromSupernode() - }) - dismissedTranslationPanelNode.animateOut() - } - - if let dismissedImportStatusPanelNode { - var dismissedPanelFrame = dismissedImportStatusPanelNode.frame - dismissedPanelFrame.origin.y = -dismissedPanelFrame.size.height - transition.updateFrame(node: dismissedImportStatusPanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedImportStatusPanelNode] _ in - dismissedImportStatusPanelNode?.removeFromSupernode() - }) - } - - if let dismissedAdPanelNode { - var dismissedPanelFrame = dismissedAdPanelNode.frame - dismissedPanelFrame.origin.y = -dismissedPanelFrame.size.height - transition.updateAlpha(node: dismissedAdPanelNode, alpha: 0.0) - transition.updateFrame(node: dismissedAdPanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedAdPanelNode] _ in - dismissedAdPanelNode?.removeFromSupernode() - }) - } - - if let dismissedFeePanelNode { - var dismissedPanelFrame = dismissedFeePanelNode.frame - transition.updateSublayerTransformOffset(layer: dismissedFeePanelNode.layer, offset: CGPoint(x: 0.0, y: -dismissedPanelFrame.height)) - dismissedPanelFrame.origin.y = feePanelBaseY - dismissedFeePanelNode.clipsToBounds = true - dismissedPanelFrame.size.height = 0.0 - if transition.isAnimated { - dismissedFeePanelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - } - transition.updateFrame(node: dismissedFeePanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedFeePanelNode] _ in - dismissedFeePanelNode?.removeFromSupernode() - }) - } - if let inputPanelNode = self.inputPanelNode, let apparentInputPanelFrame = apparentInputPanelFrame, !inputPanelNode.frame.equalTo(apparentInputPanelFrame) { if immediatelyLayoutInputPanelAndAnimateAppearance { inputPanelNode.frame = apparentInputPanelFrame.offsetBy(dx: 0.0, dy: apparentInputPanelFrame.height + previousInputPanelBackgroundFrame.maxY - apparentInputBackgroundFrame.maxY) @@ -3485,22 +3462,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.updatePlainInputSeparator(transition: .immediate) self.backgroundNode.updateBubbleTheme(bubbleTheme: chatPresentationInterfaceState.theme, bubbleCorners: chatPresentationInterfaceState.bubbleCorners) - - if self.backgroundNode.hasExtraBubbleBackground() { - if self.navigationBarBackgroundContent == nil { - if let navigationBarBackgroundContent = self.backgroundNode.makeBubbleBackground(for: .free) { - self.navigationBarBackgroundContent = navigationBarBackgroundContent - - navigationBarBackgroundContent.allowsGroupOpacity = true - navigationBarBackgroundContent.implicitContentUpdate = false - navigationBarBackgroundContent.alpha = 0.3 - self.navigationBar?.insertSubnode(navigationBarBackgroundContent, at: 1) - } - } - } else { - self.navigationBarBackgroundContent?.removeFromSupernode() - self.navigationBarBackgroundContent = nil - } } let keepSendButtonEnabled = chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil || chatPresentationInterfaceState.interfaceState.editMessage != nil @@ -4445,11 +4406,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - if !found { + if !found, let controller = self.controller { let authorName: String = (replyMessage.author.flatMap(EnginePeer.init))?.compactDisplayTitle ?? "" let errorTextData = self.chatPresentationInterfaceState.strings.Chat_ErrorQuoteOutdatedText(authorName) let errorText = errorTextData.string - self.controller?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.context.sharedContext.currentPresentationData.with({ $0 })), title: self.chatPresentationInterfaceState.strings.Chat_ErrorQuoteOutdatedTitle, text: errorText, actions: [ + controller.present(textAlertController(context: self.context, title: self.chatPresentationInterfaceState.strings.Chat_ErrorQuoteOutdatedTitle, text: errorText, actions: [ TextAlertAction(type: .genericAction, title: self.chatPresentationInterfaceState.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.chatPresentationInterfaceState.strings.Chat_ErrorQuoteOutdatedActionEdit, action: { [weak self] in guard let self, let controller = self.controller else { @@ -4909,11 +4870,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { titleViewSnapshotState: ChatTitleView.SnapshotState?, avatarSnapshotState: ChatAvatarNavigationNode.SnapshotState? ) -> SnapshotState { - var titleAccessoryPanelSnapshot: UIView? - if let titleAccessoryPanelNode = self.titleAccessoryPanelNode, let snapshot = titleAccessoryPanelNode.view.snapshotView(afterScreenUpdates: false) { - snapshot.frame = titleAccessoryPanelNode.frame - titleAccessoryPanelSnapshot = snapshot - } + let titleAccessoryPanelSnapshot: UIView? = nil var inputPanelNodeSnapshot: UIView? if let inputPanelNode = self.inputPanelNode, let snapshot = inputPanelNode.view.snapshotView(afterScreenUpdates: false) { snapshot.frame = inputPanelNode.frame @@ -4944,25 +4901,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let titleAccessoryPanelSnapshot = snapshotState.titleAccessoryPanelSnapshot { self.titleAccessoryPanelContainer.view.addSubview(titleAccessoryPanelSnapshot) - if let _ = self.titleAccessoryPanelNode { - titleAccessoryPanelSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak titleAccessoryPanelSnapshot] _ in - titleAccessoryPanelSnapshot?.removeFromSuperview() - }) - titleAccessoryPanelSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -10.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) - } else { - titleAccessoryPanelSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -titleAccessoryPanelSnapshot.bounds.height), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak titleAccessoryPanelSnapshot] _ in - titleAccessoryPanelSnapshot?.removeFromSuperview() - }) - } - } - - if let titleAccessoryPanelNode = self.titleAccessoryPanelNode { - if let _ = snapshotState.titleAccessoryPanelSnapshot { - titleAccessoryPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 10.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) - titleAccessoryPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: true) - } else { - titleAccessoryPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -titleAccessoryPanelNode.bounds.height), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) - } + titleAccessoryPanelSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -titleAccessoryPanelSnapshot.bounds.height), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak titleAccessoryPanelSnapshot] _ in + titleAccessoryPanelSnapshot?.removeFromSuperview() + }) } if let navigationBar = self.navigationBar { diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 551db3f3..5ea627c2 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -1013,14 +1013,14 @@ extension ChatControllerImpl { } else { text = strongSelf.presentationData.strings.Chat_AttachmentLimitReached } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, presentCantSendMultipleFiles: { guard let strongSelf = self else { return } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_AttachmentMultipleFilesDisabled, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Chat_AttachmentMultipleFilesDisabled, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, presentJpegConversionAlert: { completion in - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.MediaPicker_JpegConversionText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.MediaPicker_KeepHeic, action: { + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.MediaPicker_JpegConversionText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.MediaPicker_KeepHeic, action: { completion(false) }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.MediaPicker_ConvertToJpeg, action: { completion(true) @@ -1463,7 +1463,7 @@ extension ChatControllerImpl { text = strongSelf.presentationData.strings.Chat_AttachmentLimitReached } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, presentSchedulePicker: { [weak self] media, done in if let strongSelf = self { strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time, repeatPeriod in @@ -1588,19 +1588,19 @@ extension ChatControllerImpl { switch itemType { case .image: if bannedSendPhotos != nil { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } case .video: if bannedSendVideos != nil { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } case .gif: if bannedSendGifs != nil { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return false } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index 9640bc72..bd1b5f7e 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -20,6 +20,7 @@ import ChatMessageItemCommon import ChatMessageItemView import ReactionSelectionNode import AnimatedTextComponent +import PresentationDataUtils extension ChatControllerImpl { func presentTagPremiumPaywall() { @@ -392,7 +393,7 @@ extension ChatControllerImpl { if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed { if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer { - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [ + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [ TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_OK, action: {}) ]), in: .window(.root)) } diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index b02cb643..44097d73 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -37,11 +37,11 @@ func chatHistoryEntriesForView( currentState: ChatHistoryEntriesForViewState, context: AccountContext, location: ChatLocation, + subject: ChatControllerSubject?, view: MessageHistoryView, includeUnreadEntry: Bool, includeEmptyEntry: Bool, includeChatInfoEntry: Bool, - includeSearchEntry: Bool, includeEmbeddedSavedChatInfo: Bool, reverse: Bool, groupMessages: Bool, @@ -332,7 +332,10 @@ func chatHistoryEntriesForView( } } var attributes = attributes - attributes.displayContinueThreadFooter = true + if let subject, case .pinnedMessages = subject { + } else { + attributes.displayContinueThreadFooter = true + } entries[i] = .MessageEntry(message, presentationData, isRead, location, selection, attributes) break outer default: @@ -508,7 +511,12 @@ func chatHistoryEntriesForView( entries.insert(.ChatInfoEntry(.botInfo(title: "", text: presentationData.strings.VerificationCodes_DescriptionText, photo: nil, video: nil), presentationData), at: 0) } else if let cachedPeerData = cachedPeerData as? CachedUserData { if let botInfo = cachedPeerData.botInfo, !botInfo.description.isEmpty { - entries.insert(.ChatInfoEntry(.botInfo(title: presentationData.strings.Bot_DescriptionTitle, text: botInfo.description, photo: botInfo.photo, video: botInfo.video), presentationData), at: 0) + if location.threadId == nil { + if let subject, case .pinnedMessages = subject { + } else { + entries.insert(.ChatInfoEntry(.botInfo(title: presentationData.strings.Bot_DescriptionTitle, text: botInfo.description, photo: botInfo.photo, video: botInfo.video), presentationData), at: 0) + } + } } else if let peerStatusSettings = cachedPeerData.peerStatusSettings, peerStatusSettings.registrationDate != nil || peerStatusSettings.phoneCountry != nil { if peerStatusSettings.flags.contains(.canAddContact) || peerStatusSettings.flags.contains(.canReport) || peerStatusSettings.flags.contains(.canBlock) { @@ -680,15 +688,12 @@ func chatHistoryEntriesForView( entries.append(.MessageEntry(updatedMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false))) } } - } else if includeSearchEntry { - if view.laterId == nil { - if !view.entries.isEmpty { - entries.append(.SearchEntry(presentationData.theme.theme, presentationData.strings)) - } - } } if addBotForumHeader { - entries.append(.ChatInfoEntry(.newThreadInfo, presentationData)) + if let subject, case .pinnedMessages = subject { + } else { + entries.append(.ChatInfoEntry(.newThreadInfo, presentationData)) + } } if includeEmbeddedSavedChatInfo, let peerId = location.peerId { if !view.isLoading && view.laterId == nil { diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 83bfb769..eec79b5e 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -11,9 +11,9 @@ import TelegramUIPreferences import MediaResources import AccountContext import TemporaryCachedPeerDataManager -import ChatListSearchItemNode import Emoji import AppBundle +import ItemListUI import ListMessageItem import AccountContext import ChatInterfaceState @@ -216,7 +216,7 @@ extension ListMessageItemInteraction { } } -private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, entries: [ChatHistoryViewTransitionInsertEntry]) -> [ListViewInsertItem] { +private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, entries: [ChatHistoryViewTransitionInsertEntry], systemStyle: ItemListSystemStyle) -> [ListViewInsertItem] { var disableFloatingDateHeaders = false if case .customChatContents = chatLocation { disableFloatingDateHeaders = true @@ -229,7 +229,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders || message.timestamp < 10) - case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): + case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { case .none: @@ -239,7 +239,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId } - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch, isSavedMusic: isSavedMusic, canReorder: canReorder) + item = ListMessageItem(presentationData: presentationData, systemStyle: systemStyle, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch, isSavedMusic: isSavedMusic, canReorder: canReorder) } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .MessageGroupEntry(_, messages, presentationData): @@ -249,7 +249,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages), disableDate: disableFloatingDateHeaders) case .list: assertionFailure() - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) + item = ListMessageItem(presentationData: presentationData, systemStyle: systemStyle, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .UnreadEntry(_, presentationData): @@ -267,15 +267,11 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca item = ChatNewThreadInfoItem(controllerInteraction: controllerInteraction, presentationData: presentationData, context: context) } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) - case let .SearchEntry(theme, strings): - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: { - controllerInteraction.openSearch() - }), directionHint: entry.directionHint) } } } -private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, entries: [ChatHistoryViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { +private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, entries: [ChatHistoryViewTransitionUpdateEntry], systemStyle: ItemListSystemStyle) -> [ListViewUpdateItem] { var disableFloatingDateHeaders = false if case .customChatContents = chatLocation { disableFloatingDateHeaders = true @@ -288,7 +284,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders || message.timestamp < 10) - case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): + case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { case .none: @@ -298,7 +294,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId } - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch, isSavedMusic: isSavedMusic, canReorder: canReorder) + item = ListMessageItem(presentationData: presentationData, systemStyle: systemStyle, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch, isSavedMusic: isSavedMusic, canReorder: canReorder) } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .MessageGroupEntry(_, messages, presentationData): @@ -308,7 +304,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages), disableDate: disableFloatingDateHeaders) case .list: assertionFailure() - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) + item = ListMessageItem(presentationData: presentationData, systemStyle: systemStyle, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .UnreadEntry(_, presentationData): @@ -326,16 +322,12 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca item = ChatNewThreadInfoItem(controllerInteraction: controllerInteraction, presentationData: presentationData, context: context) } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) - case let .SearchEntry(theme, strings): - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: { - controllerInteraction.openSearch() - }), directionHint: entry.directionHint) } } } -private func mappedChatHistoryViewListTransition(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, animateFromPreviousFilter: Bool, transition: ChatHistoryViewTransition) -> ChatHistoryListViewTransition { - return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: canReorder, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: canReorder, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, animateIn: transition.animateIn, reason: transition.reason, flashIndicators: transition.flashIndicators, animateFromPreviousFilter: animateFromPreviousFilter) +private func mappedChatHistoryViewListTransition(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, isSavedMusic: Bool, canReorder: Bool, animateFromPreviousFilter: Bool, transition: ChatHistoryViewTransition, systemStyle: ItemListSystemStyle) -> ChatHistoryListViewTransition { + return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: canReorder, entries: transition.insertEntries, systemStyle: systemStyle), updateItems: mappedUpdateEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: canReorder, entries: transition.updateEntries, systemStyle: systemStyle), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, animateIn: transition.animateIn, reason: transition.reason, flashIndicators: transition.flashIndicators, animateFromPreviousFilter: animateFromPreviousFilter) } final class ChatHistoryTransactionOpaqueState { @@ -475,6 +467,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto static let fixedAdMessageStableId: UInt32 = UInt32.max - 5000 public let context: AccountContext + private let systemStyle: ItemListSystemStyle private(set) var chatLocation: ChatLocation private let chatLocationContextHolder: Atomic private let source: ChatHistoryListSource @@ -762,7 +755,23 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto private let initTimestamp: Double - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), chatLocation: ChatLocation, chatLocationContextHolder: Atomic, adMessagesContext: AdMessagesHistoryContext?, tag: HistoryViewInputTag?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles, rotated: Bool = false, isChatPreview: Bool, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) { + public init( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal), + systemStyle: ItemListSystemStyle = .legacy, + chatLocation: ChatLocation, + chatLocationContextHolder: Atomic, + adMessagesContext: AdMessagesHistoryContext?, + tag: HistoryViewInputTag?, + source: ChatHistoryListSource, + subject: ChatControllerSubject?, + controllerInteraction: ChatControllerInteraction, + selectedMessages: Signal?, NoError>, + mode: ChatHistoryListMode = .bubbles, + rotated: Bool = false, + isChatPreview: Bool, + messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl? + ) { self.initTimestamp = CFAbsoluteTimeGetCurrent() var tag = tag @@ -771,6 +780,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } self.context = context + self.systemStyle = systemStyle self.chatLocation = chatLocation self.chatLocationContextHolder = chatLocationContextHolder self.source = source @@ -915,7 +925,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } } - self.dynamicBounceEnabled = !self.currentPresentationData.disableAnimations self.experimentalSnapScrollToItem = false //self.debugInfo = true @@ -1321,6 +1330,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto let messageTransitionNode = self.messageTransitionNode let mode = self.mode let rotated = self.rotated + let systemStyle = self.systemStyle var resetScrollingMessageId: (index: MessageIndex, offset: CGFloat)? @@ -1801,7 +1811,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto return historyViewUpdateValue } } - + let startTime = CFAbsoluteTimeGetCurrent() var measure_isFirstTime = true let messageViewQueue = Queue.mainQueue() @@ -1931,7 +1941,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto let forceSynchronous = true let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: false, chatLocation: chatLocation, source: source, controllerInteraction: controllerInteraction, scrollPosition: nil, scrollAnimationCurve: nil, initialData: initialData?.initialData, keyboardButtonsMessage: nil, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: false, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: false) - var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: previousViewValue.associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: 0, isSavedMusic: isSavedMusic, canReorder: canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition) + var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: previousViewValue.associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: 0, isSavedMusic: isSavedMusic, canReorder: canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition, systemStyle: systemStyle) if disableAnimations { mappedTransition.options.remove(.AnimateInsertion) @@ -2022,14 +2032,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto var reverse = false var reverseGroups = false - var includeSearchEntry = false - if case let .list(search, reverseValue, reverseGroupsValue, _, _, _) = mode { - includeSearchEntry = search + if case let .list(reverseValue, reverseGroupsValue, _, _, _) = mode { reverse = reverseValue reverseGroups = reverseGroupsValue } - var isPremium = false if case let .user(user) = accountPeer, user.isPremium { isPremium = true @@ -2083,11 +2090,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto currentState: previousChatHistoryEntriesForViewState, context: context, location: chatLocation, + subject: subject, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tag == nil, includeChatInfoEntry: mode == .bubbles, - includeSearchEntry: includeSearchEntry && tag != nil, includeEmbeddedSavedChatInfo: includeEmbeddedSavedChatInfo, reverse: reverse, groupMessages: mode == .bubbles, @@ -2317,12 +2324,15 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } var keyboardButtonsMessage = view.topTaggedMessages.first + if keyboardButtonsMessage != nil && keyboardButtonsMessage?.threadId != chatLocation.threadId { + keyboardButtonsMessage = nil + } if let keyboardButtonsMessageValue = keyboardButtonsMessage, keyboardButtonsMessageValue.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with({ $0 })) { keyboardButtonsMessage = nil } let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, source: source, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, scrollAnimationCurve: scrollAnimationCurve, initialData: initialData?.initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: !isSavedMusic || forceUpdateAll) - var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: processedView.filteredEntries.count > 1 && canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition) + var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: processedView.filteredEntries.count > 1 && canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition, systemStyle: systemStyle) if disableAnimations { mappedTransition.options.remove(.AnimateInsertion) @@ -2447,7 +2457,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto let chatPresentationData = ChatPresentationData(theme: themeData, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners, animatedEmojiScale: animatedEmojiConfig.scale) strongSelf.currentPresentationData = chatPresentationData - strongSelf.dynamicBounceEnabled = false strongSelf.forEachItemHeaderNode { itemHeaderNode in if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNodeImpl { @@ -4551,7 +4560,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto switch self.mode { case .bubbles: item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders) - case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): + case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { case .none: @@ -4561,7 +4570,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId } - item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) + item = ListMessageItem(presentationData: presentationData, systemStyle: self.systemStyle, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) @@ -4582,7 +4591,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .group(messages: messages), disableDate: disableFloatingDateHeaders) case .list: assertionFailure() - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) + item = ListMessageItem(presentationData: presentationData, systemStyle: self.systemStyle, context: self.context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) } let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) @@ -4629,7 +4638,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto switch self.mode { case .bubbles: item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders) - case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch): + case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { case .none: @@ -4639,7 +4648,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId } - item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) + item = ListMessageItem(presentationData: presentationData, systemStyle: self.systemStyle, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift index 57811058..616a8f94 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift @@ -20,21 +20,21 @@ enum ChatHistoryNavigationButtonType { class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { let containerNode: ContextExtractedContentContainingNode - let buttonNode: HighlightTrackingButtonNode private let backgroundView: GlassBackgroundView let imageView: GlassBackgroundView.ContentImageView private let badgeBackgroundView: GlassBackgroundView private let badgeTextNode: ImmediateAnimatedCountLabelNode + private var tapRecognizer: UITapGestureRecognizer? var tapped: (() -> Void)? { didSet { - if (oldValue != nil) != (self.tapped != nil) { - if self.tapped != nil { - self.buttonNode.addTarget(self, action: #selector(self.onTap), forControlEvents: .touchUpInside) - } else { - self.buttonNode.removeTarget(self, action: #selector(self.onTap), forControlEvents: .touchUpInside) - } - } + self.tapRecognizer?.isEnabled = self.tapped != nil && self.isEnabled + } + } + + var isEnabled: Bool = true { + didSet { + self.tapRecognizer?.isEnabled = self.tapped != nil && self.isEnabled } } @@ -54,7 +54,6 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { self.type = type self.containerNode = ContextExtractedContentContainingNode() - self.buttonNode = HighlightTrackingButtonNode() self.backgroundView = GlassBackgroundView() @@ -80,7 +79,10 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { super.init() - self.targetNodeForActivationProgress = self.buttonNode + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))) + self.tapRecognizer = tapRecognizer + self.backgroundView.contentView.addGestureRecognizer(tapRecognizer) + tapRecognizer.isEnabled = false self.addSubnode(self.containerNode) @@ -89,18 +91,16 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { self.containerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size) self.containerNode.contentRect = CGRect(origin: CGPoint(), size: size) - self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) - self.containerNode.contentNode.addSubnode(self.buttonNode) + self.containerNode.contentNode.view.addSubview(self.backgroundView) - self.buttonNode.view.addSubview(self.backgroundView) self.backgroundView.frame = CGRect(origin: CGPoint(), size: size) - self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: .immediate) + self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: .immediate) self.imageView.tintColor = theme.chat.inputPanel.panelControlColor self.backgroundView.contentView.addSubview(self.imageView) self.imageView.frame = CGRect(origin: CGPoint(), size: size) - self.buttonNode.view.addSubview(self.badgeBackgroundView) + self.containerNode.contentNode.view.addSubview(self.badgeBackgroundView) self.badgeBackgroundView.contentView.addSubview(self.badgeTextNode.view) self.frame = CGRect(origin: CGPoint(), size: size) @@ -143,9 +143,11 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { self.absoluteRect = (rect, containerSize) } - @objc func onTap() { - if let tapped = self.tapped { - tapped() + @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + if let tapped = self.tapped { + tapped() + } } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift index 190d7b52..c860391d 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift @@ -183,7 +183,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { if let down = self.directionButtonState.down { self.downButton.imageView.alpha = down.isEnabled ? 1.0 : 0.5 - self.downButton.buttonNode.isEnabled = down.isEnabled + self.downButton.isEnabled = down.isEnabled mentionsOffset += buttonSize.height + 12.0 upOffset += buttonSize.height + 12.0 @@ -203,7 +203,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { if let up = self.directionButtonState.up { self.upButton.imageView.alpha = up.isEnabled ? 1.0 : 0.5 - self.upButton.buttonNode.isEnabled = up.isEnabled + self.upButton.isEnabled = up.isEnabled mentionsOffset += buttonSize.height + 12.0 diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift index e0a074b4..0944f199 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift @@ -153,35 +153,8 @@ func textInputContextPanel(context: AccountContext, chatPresentationInterfaceSta } else { return nil } - case let .contextRequestResult(_, results): - let _ = results + case .contextRequestResult: return nil - /*if let results = results, (!results.results.isEmpty || results.switchPeer != nil || results.webView != nil) { - switch results.presentation { - case .list: - if let currentPanel = currentPanel as? VerticalListContextResultsChatInputContextPanelNode { - currentPanel.updateResults(results) - return currentPanel - } else { - let panel = VerticalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext) - panel.interfaceInteraction = interfaceInteraction - panel.updateResults(results) - return panel - } - case .media: - if let currentPanel = currentPanel as? HorizontalListContextResultsChatInputContextPanelNode { - currentPanel.updateResults(results) - return currentPanel - } else { - let panel = HorizontalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext) - panel.interfaceInteraction = interfaceInteraction - panel.updateResults(results) - return panel - } - } - } else { - return nil - }*/ } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift index 7c213a88..9358698e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift @@ -18,6 +18,7 @@ import Display import Markdown import TextFormat import TelegramPresentationData +import AlertComponent func textInputAccessoryPanel( context: AccountContext, @@ -112,7 +113,6 @@ func textInputAccessoryPanel( let theme = chatPresentationInterfaceState.theme let strings = chatPresentationInterfaceState.strings let nameDisplayOrder = chatPresentationInterfaceState.nameDisplayOrder - let fontSize = chatPresentationInterfaceState.fontSize return AnyComponentWithIdentity(id: "forward", component: AnyComponent(ChatInputMessageAccessoryPanel( context: context, @@ -158,23 +158,45 @@ func textInputAccessoryPanel( string = strings.Conversation_ForwardOptions_Text(messages, peerDisplayTitle) } - let font = Font.regular(floor(fontSize.baseDisplaySize * 15.0 / 17.0)) - let boldFont = Font.semibold(floor(fontSize.baseDisplaySize * 15.0 / 17.0)) - let body = MarkdownAttributeSet(font: font, textColor: theme.actionSheet.secondaryTextColor) - let bold = MarkdownAttributeSet(font: boldFont, textColor: theme.actionSheet.secondaryTextColor) + let font = Font.regular(15.0) + let boldFont = Font.semibold(15.0) + let body = MarkdownAttributeSet(font: font, textColor: theme.actionSheet.primaryTextColor) + let bold = MarkdownAttributeSet(font: boldFont, textColor: theme.actionSheet.primaryTextColor) + let text = addAttributesToStringWithRanges(string._tuple, body: body, argumentAttributes: [0: bold, 1: bold], textAlignment: .natural) - let title = NSAttributedString(string: strings.Conversation_ForwardOptions_Title(messageCount), font: Font.semibold(floor(fontSize.baseDisplaySize)), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center) - let text = addAttributesToStringWithRanges(string._tuple, body: body, argumentAttributes: [0: bold, 1: bold], textAlignment: .center) + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent( + title: strings.Conversation_ForwardOptions_Title(messageCount) + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .attributed(text)) + ) + )) - let alertController = richTextAlertController(context: context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: strings.Conversation_ForwardOptions_ShowOptions, action: { - guard let sourceView else { - return - } - interfaceInteraction?.presentForwardOptions(sourceView) - let _ = ApplicationSpecificNotice.incrementChatForwardOptionsTip(accountManager: context.sharedContext.accountManager, count: 3).start() - }), TextAlertAction(type: .destructiveAction, title: strings.Conversation_ForwardOptions_CancelForwarding, action: { - interfaceInteraction?.dismissForwardMessages() - })], actionLayout: .vertical) + let alertController = AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + content: content, + actions: [ + .init(title: strings.Conversation_ForwardOptions_ShowOptions, action: { + guard let sourceView else { + return + } + interfaceInteraction?.presentForwardOptions(sourceView) + let _ = ApplicationSpecificNotice.incrementChatForwardOptionsTip(accountManager: context.sharedContext.accountManager, count: 3).start() + }), + .init(title: strings.Conversation_ForwardOptions_CancelForwarding, type: .destructive, action: { + interfaceInteraction?.dismissForwardMessages() + }) + ] + ) interfaceInteraction?.presentController(alertController, nil) } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 81195abb..0d05ccc3 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -37,6 +37,10 @@ import ChatMessageItemView import ChatMessageBubbleItemNode import AdsInfoScreen import AdsReportScreen +import LegacyComponents +import LocalMediaResources +import MediaResources +import AVFoundation private struct MessageContextMenuData { let starStatus: Bool? @@ -647,8 +651,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState if let restrictedText = restrictedText { storeMessageTextInPasteboard(restrictedText, entities: nil) } else { - if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled, - let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { + var translateToLang: String? + if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled { + translateToLang = translationState.toLang + } + if controllerInteraction.summarizedMessageIds.contains(message.id), let attribute = message.attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute, let summary = attribute.summaryForLang(translateToLang) { + storeMessageTextInPasteboard(summary.text, entities: summary.entities) + } else if let translateToLang, let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translateToLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { storeMessageTextInPasteboard(translation.text, entities: translation.entities) } else { storeMessageTextInPasteboard(message.text, entities: messageEntities) @@ -1178,8 +1187,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState c?.dismiss(completion: { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - controllerInteraction.presentController(standardTextAlertController( - theme: AlertControllerTheme(presentationData: presentationData), + controllerInteraction.presentController(textAlertController( + context: context, title: presentationData.strings.Chat_ScheduledForceSendProcessingVideo_Title, text: presentationData.strings.Chat_ScheduledForceSendProcessingVideo_Text, actions: [ @@ -1282,11 +1291,16 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState if let restrictedText = restrictedText { storeMessageTextInPasteboard(restrictedText, entities: nil) } else { - if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled, - let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { + var translateToLang: String? + if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled { + translateToLang = translationState.toLang + } + if controllerInteraction.summarizedMessageIds.contains(message.id), let attribute = message.attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute, let summary = attribute.summaryForLang(translateToLang) { + storeMessageTextInPasteboard(summary.text, entities: summary.entities) + } else if let translateToLang, let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translateToLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { storeMessageTextInPasteboard(translation.text, entities: translation.entities) } else { - storeMessageTextInPasteboard(messageText, entities: messageEntities) + storeMessageTextInPasteboard(message.text, entities: messageEntities) } } @@ -1355,10 +1369,17 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in var text = messageText - if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled, - let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { + + var translateToLang: String? + if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled { + translateToLang = translationState.toLang + } + if controllerInteraction.summarizedMessageIds.contains(message.id), let attribute = message.attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute, let summary = attribute.summaryForLang(translateToLang) { + text = summary.text + } else if let translateToLang, let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translateToLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { text = translation.text } + controllerInteraction.performTextSelectionAction(message, !isCopyProtected, NSAttributedString(string: text), .speak) f(.default) }))) @@ -1392,6 +1413,16 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState }) f(.default) }))) + + // MARK: - Ghostgram: Send as Circle (Video Message) + if isVideo, let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile { + actions.append(.action(ContextMenuActionItem(text: "ะžั‚ะฟั€ะฐะฒะธั‚ัŒ ะบะฐะบ ะบั€ัƒะถะพะบ", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoMessage"), color: theme.actionSheet.primaryTextColor) + }, action: { (controller: ContextControllerProtocol?, dismiss: @escaping (ContextMenuActionResult) -> Void) in + dismiss(.default) + convertVideoToCircleAndSend(context: context, message: message, file: file, controllerInteraction: controllerInteraction) + }))) + } } } @@ -1426,16 +1457,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } - if (loggingSettings.logToFile || loggingSettings.logToConsole) && !downloadableMediaResourceInfos.isEmpty { - actions.append(.action(ContextMenuActionItem(text: "Send Logs", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - triggerDebugSendLogsUI(context: context, additionalInfo: "User has requested download logs for \(downloadableMediaResourceInfos)", pushController: { c in - controllerInteraction.navigationController()?.pushViewController(c) - }) - f(.default) - }))) - } + // REMOVED: Send Logs button + var threadId: Int64? var threadMessageCount: Int = 0 @@ -1511,6 +1534,56 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState }))) } + // MARK: - Ghostgram: Local Edit (Client-Side Only) + // Allows editing any message text locally without sending to server + if !message.text.isEmpty { + actions.append(.action(ContextMenuActionItem(text: "ะ˜ะทะผะตะฝะธั‚ัŒ ะปะพะบะฐะปัŒะฝะพ", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor) + }, action: { [weak controllerInteraction] _, f in + f(.dismissWithoutContent) + + guard let controllerInteraction = controllerInteraction else { return } + + let peerId = message.id.peerId.toInt64() + let messageId = message.id.id + let currentText = LocalEditManager.shared.getLocalEdit(peerId: peerId, messageId: messageId) ?? message.text + + let alertController = UIAlertController( + title: "ะ˜ะทะผะตะฝะธั‚ัŒ ะปะพะบะฐะปัŒะฝะพ", + message: "ะญั‚ะพ ะธะทะผะตะฝะตะฝะธะต ะฒะธะดะฝะพ ั‚ะพะปัŒะบะพ ะฒะฐะผ ะธ ัะฑั€ะพัะธั‚ัั ะฟะพัะปะต ะฟะตั€ะตะทะฐะฟัƒัะบะฐ ะฟั€ะธะปะพะถะตะฝะธั", + preferredStyle: .alert + ) + + alertController.addTextField { textField in + textField.text = currentText + textField.placeholder = "ะ’ะฒะตะดะธั‚ะต ะฝะพะฒั‹ะน ั‚ะตะบัั‚" + } + + alertController.addAction(UIAlertAction(title: "ะžั‚ะผะตะฝะฐ", style: .cancel, handler: nil)) + alertController.addAction(UIAlertAction(title: "ะกะพั…ั€ะฐะฝะธั‚ัŒ", style: .default) { [weak alertController] _ in + guard let textField = alertController?.textFields?.first, + let newText = textField.text, !newText.isEmpty else { + return + } + + LocalEditManager.shared.setLocalEdit(peerId: peerId, messageId: messageId, newText: newText) + + // Request UI update + controllerInteraction.requestMessageUpdate(message.id, true) + }) + + if let chatControllerNode = controllerInteraction.chatControllerNode() { + if let viewController = chatControllerNode.view.window?.rootViewController { + var topController = viewController + while let presented = topController.presentedViewController { + topController = presented + } + topController.present(alertController, animated: true, completion: nil) + } + } + }))) + } + if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, channel.isMonoForum { var canSuggestPost = true for media in message.media { @@ -2012,6 +2085,33 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } actions.insert(.custom(ChatReadReportContextItem(context: context, message: message, hasReadReports: false, isEdit: true, stats: MessageReadStats(reactionCount: 0, peers: [], readTimestamps: [:]), action: nil), false), at: 0) } + + // GHOSTGRAM: Show edit history button if message was edited and has saved history + if isEdited && EditHistoryManager.shared.hasEditHistory(peerId: message.id.peerId.toInt64(), messageId: message.id.id) { + actions.append(.action(ContextMenuActionItem(text: "ะ˜ัั‚ะพั€ะธั", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor) + }, action: { c, f in + let editHistory = EditHistoryManager.shared.getEditHistory(peerId: message.id.peerId.toInt64(), messageId: message.id.id) + var historyText = "ะžั€ะธะณะธะฝะฐะปัŒะฝั‹ะต ะฒะตั€ัะธะธ:\n\n" + for (index, record) in editHistory.enumerated() { + let date = Date(timeIntervalSince1970: TimeInterval(record.editDate)) + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .short + historyText += "\(index + 1). [\(formatter.string(from: date))]\n\(record.text)\n\n" + } + + c?.dismiss(completion: { + let alertController = textAlertController( + context: context, + title: "ะ˜ัั‚ะพั€ะธั ะฟั€ะฐะฒะพะบ", + text: historyText, + actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})] + ) + controllerInteraction.presentController(alertController, nil) + }) + }))) + } if canViewAuthor { actions.insert(.custom(ChatMessageAuthorContextItem(context: context, message: message, action: { c, f, peer in @@ -2162,8 +2262,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState if let restrictedText = restrictedText { storeMessageTextInPasteboard(restrictedText, entities: nil) } else { - if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled, - let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { + var translateToLang: String? + if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled { + translateToLang = translationState.toLang + } + if controllerInteraction.summarizedMessageIds.contains(message.id), let attribute = message.attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute, let summary = attribute.summaryForLang(translateToLang) { + storeMessageTextInPasteboard(summary.text, entities: summary.entities) + } else if let translateToLang, let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translateToLang }) as? TranslationMessageAttribute, !translation.text.isEmpty { storeMessageTextInPasteboard(translation.text, entities: translation.entities) } else { storeMessageTextInPasteboard(message.text, entities: messageEntities) @@ -2331,7 +2436,7 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer } } - if message.isCopyProtected() || message.containsSecretMedia { + if (message.isCopyProtected() || message.containsSecretMedia) && !MiscSettingsManager.shared.shouldBypassCopyProtection { isCopyProtected = true } for media in message.media { @@ -2437,7 +2542,8 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer } } if !message.containsSecretMedia && !isAction && !isShareProtected { - if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.isCopyProtected() { + let copyProtectedCheck = message.isCopyProtected() && !MiscSettingsManager.shared.shouldBypassCopyProtection + if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !copyProtectedCheck { if !(message.flags.isSending || message.flags.contains(.Failed)) { optionsMap[id]!.insert(.forward) } @@ -2453,7 +2559,8 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer } } else if let group = peer as? TelegramGroup { if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia { - if !isAction && !message.isCopyProtected() && !isShareProtected { + let copyProtectedCheck = message.isCopyProtected() && !MiscSettingsManager.shared.shouldBypassCopyProtection + if !isAction && !copyProtectedCheck && !isShareProtected { if !(message.flags.isSending || message.flags.contains(.Failed)) { optionsMap[id]!.insert(.forward) } @@ -2472,7 +2579,8 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer optionsMap[id]!.insert(.report) } } else if let user = peer as? TelegramUser { - if !isScheduled && message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction && !message.id.peerId.isReplies && !message.isCopyProtected() && !isShareProtected { + let copyProtectedCheck = message.isCopyProtected() && !MiscSettingsManager.shared.shouldBypassCopyProtection + if !isScheduled && message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction && !message.id.peerId.isReplies && !copyProtectedCheck && !isShareProtected { if !(message.flags.isSending || message.flags.contains(.Failed)) { optionsMap[id]!.insert(.forward) } @@ -3860,3 +3968,222 @@ private final class ChatRateTranscriptionContextItemNode: ASDisplayNode, Context return self } } + +// MARK: - Ghostgram: Video to Circle Conversion Helper +private func convertVideoToCircleAndSend( + context: AccountContext, + message: Message, + file: TelegramMediaFile, + controllerInteraction: ChatControllerInteraction +) { + // Show progress + controllerInteraction.displayUndo(.info(title: nil, text: "ะšะพะฝะฒะตั€ั‚ะฐั†ะธั ะฒ ะบั€ัƒะถะพะบ...", timeout: 5.0, customUndoText: nil)) + + // Get video file path + let resourcePath = context.account.postbox.mediaBox.completedResourcePath(file.resource) + + guard let videoPath = resourcePath, FileManager.default.fileExists(atPath: videoPath) else { + controllerInteraction.displayUndo(.info(title: "ะžัˆะธะฑะบะฐ", text: "ะ’ะธะดะตะพ ะตั‰ั‘ ะฝะต ะทะฐะณั€ัƒะถะตะฝะพ. ะžั‚ะบั€ะพะนั‚ะต ะตะณะพ ะดะปั ะทะฐะณั€ัƒะทะบะธ.", timeout: nil, customUndoText: nil)) + return + } + + // Get video dimensions + var videoDimensions = CGSize(width: 240, height: 240) + for attr in file.attributes { + switch attr { + case let .Video(_, size, _, _, _, _): + videoDimensions = size.cgSize + default: + break + } + } + + // Create square crop (center crop to 1:1 aspect ratio) + let minDim = min(videoDimensions.width, videoDimensions.height) + let cropX = (videoDimensions.width - minDim) / 2 + let cropY = (videoDimensions.height - minDim) / 2 + let cropRect = CGRect(x: cropX, y: cropY, width: minDim, height: minDim) + + // Create adjustments for video message format + let adjustments = TGVideoEditAdjustments( + originalSize: videoDimensions, + cropRect: cropRect, + cropOrientation: .up, + cropRotation: 0.0, + cropLockedAspectRatio: 1.0, + cropMirrored: false, + trimStartValue: 0.0, + trimEndValue: 0.0, + toolValues: nil, + paintingData: nil, + sendAsGif: false, + preset: TGMediaVideoConversionPresetVideoMessage + ) + + // Generate output path + let outputPath = NSTemporaryDirectory() + "circle_\(Int64.random(in: Int64.min...Int64.max)).mp4" + + print("[Ghostgram Circle] Starting conversion from: \(videoPath)") + print("[Ghostgram Circle] Output path: \(outputPath)") + print("[Ghostgram Circle] Video dimensions: \(videoDimensions)") + + // Start conversion in background + DispatchQueue.global(qos: .userInitiated).async { + // AVFoundation needs file extension to detect format + // Postbox stores files without extension, so copy to temp with .mp4 + let tempSourcePath = NSTemporaryDirectory() + "source_\(Int64.random(in: Int64.min...Int64.max)).mp4" + + do { + try FileManager.default.copyItem(atPath: videoPath, toPath: tempSourcePath) + print("[Ghostgram Circle] Copied to temp with extension: \(tempSourcePath)") + } catch { + print("[Ghostgram Circle] ERROR: Failed to copy file: \(error)") + DispatchQueue.main.async { + controllerInteraction.displayUndo(.info(title: "ะžัˆะธะฑะบะฐ", text: "ะะต ัƒะดะฐะปะพััŒ ะฟะพะดะณะพั‚ะพะฒะธั‚ัŒ ะฒะธะดะตะพ", timeout: nil, customUndoText: nil)) + } + return + } + + let videoUrl = URL(fileURLWithPath: tempSourcePath) + let avAsset = AVURLAsset(url: videoUrl) + + print("[Ghostgram Circle] Created AVAsset, starting conversion...") + + // Use TGMediaVideoConverter with correct signature + guard let signal = TGMediaVideoConverter.convert( + avAsset, + adjustments: adjustments, + path: outputPath, + watcher: nil, + entityRenderer: nil + ) else { + print("[Ghostgram Circle] ERROR: Failed to create conversion signal!") + DispatchQueue.main.async { + controllerInteraction.displayUndo(.info(title: "ะžัˆะธะฑะบะฐ", text: "ะะต ัƒะดะฐะปะพััŒ ะทะฐะฟัƒัั‚ะธั‚ัŒ ะบะพะฝะฒะตั€ั‚ะฐั†ะธัŽ", timeout: nil, customUndoText: nil)) + } + return + } + + print("[Ghostgram Circle] Conversion signal created, starting...") + + // Use SBlockDisposable instead to keep signal alive + let disposable = signal.start(next: { result in + print("[Ghostgram Circle] Got result: \(String(describing: result))") + + guard let result = result as? TGMediaVideoConversionResult, + let resultUrl = result.fileURL else { + print("[Ghostgram Circle] ERROR: Result is not TGMediaVideoConversionResult or no fileURL") + DispatchQueue.main.async { + controllerInteraction.displayUndo(.info(title: "ะžัˆะธะฑะบะฐ", text: "ะะต ัƒะดะฐะปะพััŒ ะบะพะฝะฒะตั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฒะธะดะตะพ", timeout: nil, customUndoText: nil)) + } + return + } + + print("[Ghostgram Circle] Conversion SUCCESS! File: \(resultUrl.path)") + + let finalDuration = result.duration + let finalDimensions = result.dimensions + + DispatchQueue.main.async { + // Create preview image + var previewRepresentations: [TelegramMediaImageRepresentation] = [] + if let previewImage = result.coverImage { + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min...Int64.max)) + let thumbnailSize = CGSize(width: 240, height: 240) + if let thumbnailImage = TGScaleImageToPixelSize(previewImage, thumbnailSize), + let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) { + context.account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnailData) + previewRepresentations.append(TelegramMediaImageRepresentation( + dimensions: PixelDimensions(thumbnailSize), + resource: resource, + progressiveSizes: [], + immediateThumbnailData: nil, + hasVideo: false, + isPersonal: false + )) + } + } + + // Create local resource for converted video + let videoResource = LocalFileVideoMediaResource( + randomId: Int64.random(in: Int64.min...Int64.max), + path: resultUrl.path, + adjustments: nil + ) + + // Create TelegramMediaFile with instantRoundVideo flag + let media = TelegramMediaFile( + fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min...Int64.max)), + partialReference: nil, + resource: videoResource, + previewRepresentations: previewRepresentations, + videoThumbnails: [], + immediateThumbnailData: nil, + mimeType: "video/mp4", + size: nil, + attributes: [ + .FileName(fileName: "video.mp4"), + .Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.instantRoundVideo], preloadSize: nil, coverTime: nil, videoCodec: nil) + ], + alternativeRepresentations: [] + ) + + // Create message + let outMessage: EnqueueMessage = .message( + text: "", + attributes: [], + inlineStickers: [:], + mediaReference: .standalone(media: media), + threadId: nil, + replyToMessageId: nil, + replyToStoryId: nil, + localGroupingKey: nil, + correlationId: nil, + bubbleUpEmojiOrStickersets: [] + ) + + print("[Ghostgram Circle] Sending message...") + + // Send message + let _ = enqueueMessages(account: context.account, peerId: message.id.peerId, messages: [outMessage]).start() + + // Show success + controllerInteraction.displayUndo(.succeed(text: "ะšั€ัƒะถะพะบ ะพั‚ะฟั€ะฐะฒะปะตะฝ!", timeout: nil, customUndoText: nil)) + } + }, error: { error in + print("[Ghostgram Circle] ERROR in conversion: \(String(describing: error))") + DispatchQueue.main.async { + controllerInteraction.displayUndo(.info(title: "ะžัˆะธะฑะบะฐ", text: "ะะต ัƒะดะฐะปะพััŒ ะบะพะฝะฒะตั€ั‚ะธั€ะพะฒะฐั‚ัŒ ะฒะธะดะตะพ", timeout: nil, customUndoText: nil)) + } + }, completed: { + print("[Ghostgram Circle] Conversion completed callback!") + }) + + // Keep disposable alive - hold reference until task completes + // This is a workaround for ObjC signal disposal + DispatchQueue.main.async { + CircleConversionHolder.shared.add(disposable) + } + } +} + +// Helper class to hold conversion disposables +private final class CircleConversionHolder { + static let shared = CircleConversionHolder() + private var disposables: [AnyObject] = [] + private let lock = NSLock() + + func add(_ disposable: AnyObject?) { + guard let disposable = disposable else { return } + lock.lock() + disposables.append(disposable) + lock.unlock() + + // Auto-cleanup after 2 minutes + DispatchQueue.main.asyncAfter(deadline: .now() + 120) { [weak self] in + self?.lock.lock() + self?.disposables.removeAll { $0 === disposable } + self?.lock.unlock() + } + } +} diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 2c827245..ac69da09 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -6,6 +6,7 @@ import ChatPresentationInterfaceState import ChatControllerInteraction import ComponentFlow import ChatSideTopicsPanel +import LegacyChatHeaderPanelComponent func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTitleAccessoryPanelNode? { if !force, case .standard(.embedded) = chatPresentationInterfaceState.mode { diff --git a/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift index 1fb6330e..b80d3cdb 100644 --- a/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift @@ -11,6 +11,7 @@ import TelegramNotices import AnimatedAvatarSetNode import AccountContext import ChatPresentationInterfaceState +import LegacyChatHeaderPanelComponent private final class ChatInfoTitlePanelPeerNearbyInfoNode: ASDisplayNode { private var theme: PresentationTheme? diff --git a/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift index cfcdc984..79e53b0a 100644 --- a/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift @@ -14,6 +14,7 @@ import TelegramCore import BundleIconComponent import ContextUI import SwiftSignalKit +import LegacyChatHeaderPanelComponent private final class ChatManagingBotTitlePanelComponent: Component { let context: AccountContext diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index b3700c97..6f493bf6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -933,7 +933,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } if itemNode == nil || itemNode === currentItemNode { if let contextController = self.contextController { - contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: offset), transition: transition) + contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) } if let standaloneReactionAnimation = self.standaloneReactionAnimation { standaloneReactionAnimation.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index bc7e3b87..5f5f9f17 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -23,6 +23,7 @@ import AnimationCache import MultiAnimationRenderer import TranslateUI import ChatControllerInteraction +import LegacyChatHeaderPanelComponent private enum PinnedMessageAnimation { case slideToTop @@ -67,8 +68,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { private let imageNode: TransformImageNode private let imageNodeContainer: ASDisplayNode - private let separatorNode: ASDisplayNode - private var currentLayout: (CGFloat, CGFloat, CGFloat)? private var currentMessage: ChatPinnedMessage? private var previousMediaReference: AnyMediaReference? @@ -132,9 +131,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.activityIndicator.alpha = 0.0 ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.activityIndicatorContainer, scale: 0.1) - self.separatorNode = ASDisplayNode() - self.separatorNode.isLayerBacked = true - self.contextContainer = ContextControllerSourceNode() self.clippingContainer = ASDisplayNode() @@ -219,8 +215,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside]) self.contextContainer.addSubnode(self.tapButton) - self.addSubnode(self.separatorNode) - self.contextContainer.activated = { [weak self] gesture, _ in guard let strongSelf = self else { return @@ -249,9 +243,19 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { if self.theme !== interfaceState.theme { themeUpdated = true self.theme = interfaceState.theme - self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(interfaceState.theme), for: []) - self.listButton.setImage(PresentationResourcesChat.chatInputPanelPinnedListIconImage(interfaceState.theme), for: []) - self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor + self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(interfaceState.theme.chat.inputPanel.panelControlColor.cgColor) + context.setLineWidth(1.33) + context.setLineCap(.round) + context.move(to: CGPoint(x: 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0)) + context.strokePath() + }), for: []) + self.listButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/PinnedList"), color: interfaceState.theme.chat.inputPanel.panelControlColor), for: []) self.actionButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 14.0 * 2.0, color: interfaceState.theme.list.itemCheckColors.fillColor, strokeColor: nil, strokeWidth: nil, backgroundColor: nil) } @@ -473,7 +477,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { transition.updateFrame(node: self.activityIndicatorContainer, frame: CGRect(origin: CGPoint(x: width - rightInset - indicatorSize.width + 5.0, y: 15.0), size: indicatorSize)) transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(), size: indicatorSize)) - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: width - tapButtonRightInset, height: panelHeight)) self.clippingContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) @@ -534,7 +537,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { transition.updateSublayerTransformScale(node: self.buttonsContainer, scale: 0.1) if let theme = self.theme { - self.activityIndicator.transitionToState(.progress(color: theme.chat.inputPanel.panelControlAccentColor, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true), animated: false, completion: { + self.activityIndicator.transitionToState(.progress(color: theme.chat.inputPanel.panelControlColor, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true), animated: false, completion: { }) } } @@ -609,20 +612,20 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { var titleStrings: [AnimatedCountLabelNode.Segment] = [] if let _ = giveaway { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedGiveaway) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedGiveaway) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))) } else { if pinnedMessage.totalCount == 2 { if pinnedMessage.index == 0 { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))) } else { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))) } } else if pinnedMessage.totalCount > 1 && pinnedMessage.index != pinnedMessage.totalCount - 1 { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) - titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) - titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))) + titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))) + titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))) } else { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))) } } @@ -675,14 +678,14 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { } else { titleString = "" } - titleStrings = [.text(0, NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))] + titleStrings = [.text(0, NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))] } else { for media in message.media { if let media = media as? TelegramMediaInvoice { - titleStrings = [.text(0, NSAttributedString(string: media.title, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))] + titleStrings = [.text(0, NSAttributedString(string: media.title, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))] break } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.type == "telegram_call" { - titleStrings = [.text(0, NSAttributedString(string: strings.Chat_PinnedGroupCallTitle, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))] + titleStrings = [.text(0, NSAttributedString(string: strings.Chat_PinnedGroupCallTitle, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlColor))] break } } @@ -810,7 +813,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { animationTransition.updateFrameAdditive(node: strongSelf.contentTextContainer, frame: CGRect(origin: CGPoint(x: contentLeftInset + textLineInset, y: 0.0), size: CGSize(width: width, height: panelHeight))) - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size) + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 6.0), size: titleLayout.size) let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size) strongSelf.textNode.textNode.frame = textFrame @@ -857,8 +860,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { animationTransition.updateFrame(node: strongSelf.lineNode, frame: lineFrame) strongSelf.lineNode.update( colors: AnimatedNavigationStripeNode.Colors( - foreground: theme.chat.inputPanel.panelControlAccentColor, - background: theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.5), + foreground: theme.chat.inputPanel.panelControlColor, + background: theme.chat.inputPanel.panelControlColor.withAlphaComponent(0.5), clearBackground: theme.chat.inputPanel.panelBackgroundColor ), configuration: AnimatedNavigationStripeNode.Configuration( @@ -869,7 +872,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { transition: animationTransition ) - strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 7.0), size: CGSize(width: 35.0, height: 35.0)) + strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 8.0), size: CGSize(width: 35.0, height: 35.0)) strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 35.0, height: 35.0)) if let applyImage = applyImage { diff --git a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift index f08548e6..0697955c 100644 --- a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift @@ -16,6 +16,7 @@ import AnimationCache import MultiAnimationRenderer import AccountContext import PremiumUI +import LegacyChatHeaderPanelComponent private enum ChatReportPeerTitleButton: Equatable { case block diff --git a/submodules/TelegramUI/Sources/ChatRequestInProgressTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatRequestInProgressTitlePanelNode.swift index 9148b240..7b7e4981 100644 --- a/submodules/TelegramUI/Sources/ChatRequestInProgressTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRequestInProgressTitlePanelNode.swift @@ -4,6 +4,7 @@ import Display import AsyncDisplayKit import TelegramPresentationData import ChatPresentationInterfaceState +import LegacyChatHeaderPanelComponent final class ChatRequestInProgressTitlePanelNode: ChatTitleAccessoryPanelNode { private let separatorNode: ASDisplayNode diff --git a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift index d889eeaa..a13a8cb7 100644 --- a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift @@ -157,7 +157,7 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { self.tintSubtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: .black) let panelHeight = defaultHeight(metrics: metrics) - let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight)) + let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 16.0 * 2.0, height: panelHeight)) let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight)) var originX: CGFloat = leftInset + floor((width - leftInset - rightInset - textSize.width) / 2.0) @@ -196,7 +196,8 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { combinedFrame = combinedFrame.union(iconView.frame) } combinedFrame = combinedFrame.insetBy(dx: -12.0, dy: -6.0) - combinedFrame.origin.y += 1.0 + combinedFrame.size.height = 40.0 + combinedFrame.origin.y = floorToScreenPixels((panelHeight - 40.0) * 0.5) self.textNode.frame = textFrame.offsetBy(dx: -combinedFrame.minX, dy: -combinedFrame.minY) self.tintTextNode.frame = self.textNode.frame diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 26b8afe9..b0148af5 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -291,7 +291,6 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe }, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in - }, dismissNotice: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsController.swift b/submodules/TelegramUI/Sources/ChatSearchResultsController.swift index 4e35b65c..c686bf1d 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsController.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsController.swift @@ -43,7 +43,7 @@ final class ChatSearchResultsController: ViewController { |> deliverOnMainQueue).startStrict(next: { [weak self] presentationData in if let strongSelf = self { strongSelf.presentationData = presentationData - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationData.theme, presentationStrings: presentationData.strings)) + strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationData.theme, presentationStrings: presentationData.strings), transition: .immediate) strongSelf.controllerNode.updatePresentationData(presentationData) } }) diff --git a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift index f4507ed7..5653241c 100644 --- a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift @@ -17,6 +17,7 @@ import ContextUI import PromptUI import BundleIconComponent import SavedTagNameAlertController +import LegacyChatHeaderPanelComponent private let backgroundTagImage: UIImage? = { if let image = UIImage(bundleImageName: "Chat/Title Panels/SearchTagTab") { @@ -557,7 +558,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat self.update(params: params, transition: transition) } - let panelHeight: CGFloat = 39.0 + let panelHeight: CGFloat = 40.0 return LayoutResult(backgroundHeight: panelHeight, insetHeight: panelHeight, hitTestSlop: 0.0) } @@ -567,7 +568,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat } private func update(params: Params, transition: ContainedViewLayoutTransition) { - let panelHeight: CGFloat = 39.0 + let panelHeight: CGFloat = 40.0 let containerInsets = UIEdgeInsets(top: 0.0, left: params.leftInset + 16.0, bottom: 0.0, right: params.rightInset + 16.0) let itemSpacing: CGFloat = 24.0 @@ -598,7 +599,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat } let itemSize = promoView.update(theme: params.interfaceState.theme, strings: params.interfaceState.strings, height: panelHeight, isUnlock: !self.items.isEmpty, transition: .immediate) - let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize) + let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: 0.0), size: itemSize) itemTransition.updatePosition(layer: promoView.layer, position: itemFrame.center) promoView.bounds = CGRect(origin: CGPoint(), size: itemFrame.size) @@ -704,7 +705,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat } } let itemSize = itemView.update(item: item, isSelected: isSelected, isLocked: !params.interfaceState.isPremium, theme: params.interfaceState.theme, height: panelHeight, transition: .immediate) - let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize) + let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: 0.0), size: itemSize) itemTransition.updatePosition(layer: itemView.layer, position: itemFrame.center) itemTransition.updateBounds(layer: itemView.layer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) diff --git a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift index 952ecd5d..e240d853 100644 --- a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift @@ -59,6 +59,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { } } + private let backgroundContainerView: GlassBackgroundContainerView private let leftControlsBackgroundView: GlassBackgroundView private let rightControlsBackgroundView: GlassBackgroundView private let calendarButton = ComponentView() @@ -93,13 +94,15 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { init(theme: PresentationTheme, alwaysShowTotalMessagesCount: Bool) { self.alwaysShowTotalMessagesCount = alwaysShowTotalMessagesCount + self.backgroundContainerView = GlassBackgroundContainerView() self.leftControlsBackgroundView = GlassBackgroundView() self.rightControlsBackgroundView = GlassBackgroundView() super.init() - self.view.addSubview(self.leftControlsBackgroundView) - self.view.addSubview(self.rightControlsBackgroundView) + self.view.addSubview(self.backgroundContainerView) + self.backgroundContainerView.contentView.addSubview(self.leftControlsBackgroundView) + self.backgroundContainerView.contentView.addSubview(self.rightControlsBackgroundView) } deinit { @@ -108,6 +111,14 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { } override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + var leftInset = leftInset + 8.0 + var rightInset = rightInset + 8.0 + + if bottomInset <= 32.0 { + leftInset += 18.0 + rightInset += 18.0 + } + let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) if let currentLayout = self.currentLayout, currentLayout.params == params { return currentLayout.height @@ -332,7 +343,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { buttonView.alpha = 0.0 self.view.addSubview(buttonView) } - let listModeFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 20.0 - 8.0 - buttonSize.width, y: floor((size.height - buttonSize.height) * 0.5)), size: buttonSize) + let listModeFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 8.0 - buttonSize.width, y: floor((size.height - buttonSize.height) * 0.5)), size: buttonSize) listModeButtonFrameValue = listModeFrame listModeButtonTransition.setPosition(view: buttonView, position: CGPoint(x: listModeFrame.minX + listModeFrame.width * buttonView.layer.anchorPoint.x, y: listModeFrame.minY + listModeFrame.height * buttonView.layer.anchorPoint.y)) listModeButtonTransition.setBounds(view: buttonView, bounds: CGRect(origin: CGPoint(), size: listModeFrame.size)) @@ -349,7 +360,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { } } - var nextLeftX: CGFloat = 16.0 + 8.0 + var nextLeftX: CGFloat = params.leftInset + 4.0 var calendarButtonFrameValue: CGRect? var membersButtonFrameValue: CGRect? @@ -547,7 +558,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { } } - var leftControlsBackgroundFrame = CGRect(origin: CGPoint(x: 20.0, y: floor((height - 40.0) * 0.5)), size: CGSize(width: 0.0, height: 40.0)) + var leftControlsBackgroundFrame = CGRect(origin: CGPoint(x: params.leftInset, y: floor((height - 40.0) * 0.5)), size: CGSize(width: 0.0, height: 40.0)) leftControlsBackgroundFrame.size.width = max(40.0, leftControlsRect.maxX - leftControlsBackgroundFrame.minX) transition.setFrame(view: self.leftControlsBackgroundView, frame: leftControlsBackgroundFrame) self.leftControlsBackgroundView.update(size: leftControlsBackgroundFrame.size, cornerRadius: leftControlsBackgroundFrame.height * 0.5, isDark: params.interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: params.interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition) @@ -568,12 +579,15 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { } } - var rightControlsBackgroundFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 20.0, y: floor((height - 40.0) * 0.5)), size: CGSize(width: 0.0, height: 40.0)) + var rightControlsBackgroundFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset, y: floor((height - 40.0) * 0.5)), size: CGSize(width: 0.0, height: 40.0)) rightControlsBackgroundFrame.size.width = max(40.0, rightControlsRect.maxX - rightControlsRect.minX + 8.0 * 2.0) rightControlsBackgroundFrame.origin.x -= rightControlsBackgroundFrame.width transition.setFrame(view: self.rightControlsBackgroundView, frame: rightControlsBackgroundFrame) self.rightControlsBackgroundView.update(size: rightControlsBackgroundFrame.size, cornerRadius: rightControlsBackgroundFrame.height * 0.5, isDark: params.interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: params.interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: transition) - transition.setAlpha(view: self.rightControlsBackgroundView, alpha: rightControlsRect.isEmpty ? 0.0 : 1.0) + self.rightControlsBackgroundView.isHidden = rightControlsRect.isEmpty + + transition.setFrame(view: self.backgroundContainerView, frame: CGRect(origin: CGPoint(), size: CGSize(width: params.width, height: height))) + self.backgroundContainerView.update(size: CGSize(width: params.width, height: height), isDark: params.interfaceState.theme.overallDarkAppearance, transition: transition) return height } diff --git a/submodules/TelegramUI/Sources/ChatToastAlertPanelNode.swift b/submodules/TelegramUI/Sources/ChatToastAlertPanelNode.swift index 0ab0c145..3beeadf2 100644 --- a/submodules/TelegramUI/Sources/ChatToastAlertPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatToastAlertPanelNode.swift @@ -3,6 +3,7 @@ import UIKit import Display import AsyncDisplayKit import ChatPresentationInterfaceState +import LegacyChatHeaderPanelComponent final class ChatToastAlertPanelNode: ChatTitleAccessoryPanelNode { private let separatorNode: ASDisplayNode diff --git a/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift index 80ec6665..5cd2fb70 100644 --- a/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift @@ -15,6 +15,7 @@ import AnimationCache import MultiAnimationRenderer import AccountContext import TelegramNotices +import LegacyChatHeaderPanelComponent final class ChatVerifiedPeerTitlePanelNode: ChatTitleAccessoryPanelNode { private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index 457fd8a8..144f6008 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -175,8 +175,6 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { }, openStarsTopup: { _ in }, - dismissNotice: { _ in - }, editPeer: { _ in }, openWebApp: { _ in diff --git a/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift b/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift index 3b7fd427..8d27ec66 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift @@ -107,7 +107,7 @@ final class CommandChatInputPanelItemNode: ListViewItemNode { self.activateAreaNode = AccessibilityAreaNode() self.activateAreaNode.accessibilityTraits = [.button] - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.separatorNode) diff --git a/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift b/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift index 48db2d54..e27f05e5 100644 --- a/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift @@ -144,7 +144,7 @@ final class CommandMenuChatInputPanelItemNode: ListViewItemNode { self.activateAreaNode = AccessibilityAreaNode() self.activateAreaNode.accessibilityTraits = [.button] - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.clippingNode) self.clippingNode.addSubnode(self.shadowNode) diff --git a/submodules/TelegramUI/Sources/ComposeController.swift b/submodules/TelegramUI/Sources/ComposeController.swift index f4881a50..d4825816 100644 --- a/submodules/TelegramUI/Sources/ComposeController.swift +++ b/submodules/TelegramUI/Sources/ComposeController.swift @@ -41,8 +41,9 @@ public class ComposeControllerImpl: ViewController, ComposeController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + self._hasGlassStyle = true self.navigationPresentation = .modal self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -51,8 +52,6 @@ public class ComposeControllerImpl: ViewController, ComposeController { self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed)) - self.scrollToTop = { [weak self] in if let strongSelf = self { if let searchContentNode = strongSelf.searchContentNode { @@ -93,7 +92,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) self.title = self.presentationData.strings.Compose_NewMessage self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) diff --git a/submodules/TelegramUI/Sources/ComposeControllerNode.swift b/submodules/TelegramUI/Sources/ComposeControllerNode.swift index 20ec24de..574b4975 100644 --- a/submodules/TelegramUI/Sources/ComposeControllerNode.swift +++ b/submodules/TelegramUI/Sources/ComposeControllerNode.swift @@ -131,7 +131,7 @@ final class ComposeControllerNode: ASDisplayNode { self.requestOpenDisabledPeerFromSearch?(peer, reason) }, contextAction: nil), cancel: { [weak self] in self?.requestDeactivateSearch?() - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index 043b0977..7dbf6599 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -109,7 +109,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection self.titleView = CounterControllerTitleView(theme: self.presentationData.theme) - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + self._hasGlassStyle = true self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -226,7 +227,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.updateTitle() self.contactsNode.updatePresentationData(self.presentationData) diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index 3a2d3bc9..08c8d27e 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -277,7 +277,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { } } - self.tokenListNode = EditableTokenListNode(context: self.context, presentationTheme: self.presentationData.theme, theme: EditableTokenListNodeTheme(backgroundColor: .clear, separatorColor: self.presentationData.theme.rootController.navigationBar.separatorColor, placeholderTextColor: self.presentationData.theme.list.itemPlaceholderTextColor, primaryTextColor: self.presentationData.theme.list.itemPrimaryTextColor, tokenBackgroundColor: self.presentationData.theme.list.itemCheckColors.strokeColor.withAlphaComponent(0.25), selectedTextColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, selectedBackgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, accentColor: self.presentationData.theme.list.itemAccentColor, keyboardColor: self.presentationData.theme.rootController.keyboardColor), placeholder: placeholder, shortPlaceholder: shortPlaceholder) + self.tokenListNode = EditableTokenListNode(context: self.context, theme: self.presentationData.theme, placeholder: placeholder, shortPlaceholder: shortPlaceholder) super.init() @@ -413,6 +413,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { var insets = layout.insets(options: [.input]) insets.top += navigationBarHeight insets.top += strongSelf.tokenListNode.bounds.size.height + insets.top += 10.0 + 10.0 var headerInsets = layout.insets(options: [.input]) headerInsets.top += actualNavigationBarHeight @@ -465,16 +466,17 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { var insets = layout.insets(options: [.input]) insets.top += navigationBarHeight + insets.top += 10.0 - let tokenListHeight = self.tokenListNode.updateLayout(tokens: self.editableTokens, width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) + let tokenListHeight = self.tokenListNode.updateLayout(tokens: self.editableTokens, width: layout.size.width - (16.0 + layout.safeInsets.left) * 2.0, leftInset: 0.0, rightInset: 0.0, transition: transition) - transition.updateFrame(node: self.tokenListNode, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: tokenListHeight))) + transition.updateFrame(node: self.tokenListNode, frame: CGRect(origin: CGPoint(x: 16.0 + layout.safeInsets.left, y: insets.top), size: CGSize(width: layout.size.width - (16.0 + layout.safeInsets.left) * 2.0, height: tokenListHeight))) var headerInsets = layout.insets(options: [.input]) headerInsets.top += actualNavigationBarHeight - insets.top += tokenListHeight - headerInsets.top += tokenListHeight + headerInsets.top += 10.0 + tokenListHeight + 8.0 + insets = headerInsets if let footerPanelNode = self.footerPanelNode { var count = 0 diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 5af60510..22a2c985 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -133,7 +133,9 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.presentationData = self.presentationData.withUpdated(theme: self.presentationData.theme.withModalBlocksBackground()) } - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: glass, hideSeparator: glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: glass, hideSeparator: glass, style: .glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) + + self._hasGlassStyle = true self.blocksBackgroundWhenInOverlay = true self.acceptsFocusWhenInOverlay = true @@ -236,18 +238,19 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController guard case .glass = self.style else { return } - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let closeComponent: AnyComponentWithIdentity = AnyComponentWithIdentity( id: "close", component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, state: .generic, + animateScale: false, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -262,13 +265,14 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController id: "search", component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, state: .generic, + animateScale: false, component: AnyComponentWithIdentity(id: "search", component: AnyComponent( BundleIconComponent( name: "Navigation/Search", - tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -289,15 +293,19 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.closeButtonNode = closeButtonNode self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: closeButtonNode) } - - let searchButtonNode: BarComponentHostNode - if let current = self.searchButtonNode { - searchButtonNode = current - searchButtonNode.component = searchComponent + + if searchComponent != nil { + let searchButtonNode: BarComponentHostNode + if let current = self.searchButtonNode { + searchButtonNode = current + searchButtonNode.component = searchComponent + } else { + searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize) + self.searchButtonNode = searchButtonNode + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode) + } } else { - searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize) - self.searchButtonNode = searchButtonNode - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode) + self.navigationItem.rightBarButtonItem = nil } } @@ -307,7 +315,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController if case .glass = self.style { glass = true } - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: glass, hideSeparator: glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: glass, hideSeparator: glass, style: .glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)), transition: .immediate) (self.searchContentNode as? NavigationBarSearchContentNode)?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) self.title = self.titleProducer(self.presentationData.strings) self.tabBarItem.title = self.presentationData.strings.Contacts_Title @@ -571,7 +579,7 @@ final class ContactsSearchNavigationContentNode: NavigationBarContentNode { init(presentationData: PresentationData, dismissSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void) { self.presentationData = presentationData - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings, fieldStyle: .modern) + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), presentationTheme: presentationData.theme, strings: presentationData.strings, fieldStyle: .modern) self.searchBar.placeholderString = NSAttributedString(string: presentationData.strings.Common_Search, font: searchBarFont, textColor: presentationData.theme.rootController.navigationSearchBar.inputPlaceholderTextColor) super.init() @@ -591,10 +599,12 @@ final class ContactsSearchNavigationContentNode: NavigationBarContentNode { return 56.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate() { @@ -615,7 +625,7 @@ final class ContactsSearchNavigationContentNode: NavigationBarContentNode { func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData - self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings) + self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), presentationTheme: presentationData.theme, strings: presentationData.strings) } } diff --git a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift index e15dafce..0e32c4d1 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift @@ -477,7 +477,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } - }) + }, fieldStyle: placeholderNode.fieldStyle) self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in diff --git a/submodules/TelegramUI/Sources/CreateChannelController.swift b/submodules/TelegramUI/Sources/CreateChannelController.swift index c537e8b8..b8550916 100644 --- a/submodules/TelegramUI/Sources/CreateChannelController.swift +++ b/submodules/TelegramUI/Sources/CreateChannelController.swift @@ -194,7 +194,7 @@ private enum CreateChannelEntry: ItemListNodeEntry { let arguments = arguments as! CreateChannelArguments switch self { case let .channelInfo(_, _, dateTimeFormat, peer, state, avatar): - return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in + return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in arguments.updateEditingName(editingName) }, editingNameCompleted: { arguments.focusOnDescription() @@ -202,11 +202,11 @@ private enum CreateChannelEntry: ItemListNodeEntry { arguments.changeProfilePhoto() }, updatingImage: avatar, tag: CreateChannelEntryTag.info) case let .setProfilePhoto(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: { arguments.changeProfilePhoto() }) case let .descriptionSetup(_, text, value): - return ItemListMultilineInputItem(presentationData: presentationData, text: value, placeholder: text, maxLength: ItemListMultilineInputItemTextLimit(value: 255, display: true), sectionId: self.section, style: .blocks, textUpdated: { updatedText in + return ItemListMultilineInputItem(presentationData: presentationData, systemStyle: .glass, text: value, placeholder: text, maxLength: ItemListMultilineInputItemTextLimit(value: 255, display: true), sectionId: self.section, style: .blocks, textUpdated: { updatedText in arguments.updateEditingDescriptionText(updatedText) }, tag: CreateChannelEntryTag.description) case let .descriptionInfo(_, text): @@ -214,7 +214,7 @@ private enum CreateChannelEntry: ItemListNodeEntry { case let .usernameHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .username(theme, placeholder, text): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in arguments.updatePublicLinkText(updatedText) }, action: { }) diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index d68f995c..05d3e8a2 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -321,7 +321,7 @@ private enum CreateGroupEntry: ItemListNodeEntry { let arguments = arguments as! CreateGroupArguments switch self { case let .groupInfo(_, _, dateTimeFormat, peer, state, avatar): - return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in + return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in arguments.updateEditingName(editingName) }, editingNameCompleted: { arguments.done() @@ -329,13 +329,13 @@ private enum CreateGroupEntry: ItemListNodeEntry { arguments.changeProfilePhoto() }, updatingImage: avatar, tag: CreateGroupEntryTag.info) case let .setProfilePhoto(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: { arguments.changeProfilePhoto() }) case let .usernameHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .username(theme, placeholder, text): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: placeholder, type: .username, clearType: .always, tag: nil, sectionId: self.section, textUpdated: { updatedText in arguments.updatePublicLinkText(updatedText) }, action: { }) @@ -364,24 +364,24 @@ private enum CreateGroupEntry: ItemListNodeEntry { case let .usernameInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .topics(_, text): - return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Topics")?.precomposed(), title: text, value: true, enabled: false, sectionId: self.section, style: .blocks, updated: { _ in }) + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Topics")?.precomposed(), title: text, value: true, enabled: false, sectionId: self.section, style: .blocks, updated: { _ in }) case let .topicsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .autoDelete(text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: { arguments.updateAutoDelete() }, tag: CreateGroupEntryTag.autoDelete) case let .autoDeleteInfo(text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .member(_, _, _, dateTimeFormat, nameDisplayOrder, peer, presence): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: presence.flatMap(EnginePeer.Presence.init), text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: presence.flatMap(EnginePeer.Presence.init), text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) case let .locationHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .location(theme, location): let imageSignal = chatMapSnapshotImage(engine: arguments.context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) - return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil) + return ItemListAddressItem(theme: theme, systemStyle: .glass, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil) case let .changeLocation(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: { arguments.changeLocation() }) case let .locationInfo(_, text): @@ -389,7 +389,7 @@ private enum CreateGroupEntry: ItemListNodeEntry { case let .venueHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .venue(_, _, venue): - return ItemListVenueItem(presentationData: presentationData, engine: arguments.context.engine, venue: venue, sectionId: self.section, style: .blocks, action: { + return ItemListVenueItem(presentationData: presentationData, systemStyle: .glass, engine: arguments.context.engine, venue: venue, sectionId: self.section, style: .blocks, action: { arguments.updateWithVenue(venue) }) } diff --git a/submodules/TelegramUI/Sources/EmojisChatInputPanelItem.swift b/submodules/TelegramUI/Sources/EmojisChatInputPanelItem.swift index d193f2c4..99908fef 100644 --- a/submodules/TelegramUI/Sources/EmojisChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/EmojisChatInputPanelItem.swift @@ -100,7 +100,7 @@ final class EmojisChatInputPanelItemNode: ListViewItemNode { self.symbolNode = TextNode() self.symbolNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.symbolNode) } diff --git a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift index 216bf427..977345b9 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift @@ -176,7 +176,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { peer: peer, title: self.strings.Chat_HashtagSuggestion_UseLocal_Title("#\(query)@\(addressName)").string, text: isGroup ? self.strings.Chat_HashtagSuggestion_UseLocal_Group_Text : self.strings.Chat_HashtagSuggestion_UseLocal_Channel_Text, - badge: self.strings.ChatList_ContextMenuBadgeNew, + badge: nil, hashtag: "\(query)@\(addressName)", revealed: false, isAdditionalRecent: false diff --git a/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift index 0ffafe06..9dfef707 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift @@ -149,7 +149,7 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { self.activateAreaNode = AccessibilityAreaNode() self.activateAreaNode.accessibilityTraits = [.button] - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.separatorNode) self.addSubnode(self.titleNode) diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift index 4209c7fd..ed79bfdb 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift @@ -133,7 +133,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode CMTimebaseSetRate(timebase!, rate: 0.0) self.timebase = timebase! - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.imageNodeBackground) diff --git a/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift b/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift index e59b7c15..928dba8a 100644 --- a/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift @@ -126,7 +126,7 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { self.activateAreaNode = AccessibilityAreaNode() self.activateAreaNode.accessibilityTraits = [.button] - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.separatorNode) diff --git a/submodules/TelegramUI/Sources/NotificationContainerControllerNode.swift b/submodules/TelegramUI/Sources/NotificationContainerControllerNode.swift index 4d055b9b..7221f5db 100644 --- a/submodules/TelegramUI/Sources/NotificationContainerControllerNode.swift +++ b/submodules/TelegramUI/Sources/NotificationContainerControllerNode.swift @@ -95,10 +95,7 @@ final class NotificationContainerControllerNode: ASDisplayNode { }) } - var useCompactLayout = false - if let validLayout = self.validLayout { - useCompactLayout = min(validLayout.size.width, validLayout.size.height) < 375.0 - } + let useCompactLayout = "".isEmpty let itemNode = item.node(compact: useCompactLayout) let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme, contentNode: itemNode) @@ -165,10 +162,7 @@ final class NotificationContainerControllerNode: ASDisplayNode { } self.topItemAndNode = nil - var useCompactLayout = false - if let validLayout = self.validLayout { - useCompactLayout = min(validLayout.size.width, validLayout.size.height) < 375.0 - } + let useCompactLayout = "".isEmpty let itemNode = item.node(compact: useCompactLayout) let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme, contentNode: itemNode) diff --git a/submodules/TelegramUI/Sources/NotificationItemContainerNode.swift b/submodules/TelegramUI/Sources/NotificationItemContainerNode.swift index aff92e02..f413f325 100644 --- a/submodules/TelegramUI/Sources/NotificationItemContainerNode.swift +++ b/submodules/TelegramUI/Sources/NotificationItemContainerNode.swift @@ -2,14 +2,16 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import ComponentFlow import TelegramPresentationData import ChatMessageNotificationItem +import GlassBackgroundComponent final class NotificationItemContainerNode: ASDisplayNode { - private let backgroundNode: ASImageNode - - private var validLayout: ContainerViewLayout? + private let theme: PresentationTheme + private let backgroundView = GlassBackgroundView() + var item: NotificationItem? private var hapticFeedback: HapticFeedback? @@ -40,6 +42,8 @@ final class NotificationItemContainerNode: ASDisplayNode { } } + private var validLayout: ContainerViewLayout? + var dismissed: ((NotificationItem) -> Void)? var cancelTimeout: ((NotificationItem) -> Void)? var resumeTimeout: ((NotificationItem) -> Void)? @@ -48,15 +52,10 @@ final class NotificationItemContainerNode: ASDisplayNode { init(theme: PresentationTheme, contentNode: NotificationItemNode?) { self.contentNode = contentNode - - self.backgroundNode = ASImageNode() - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.image = PresentationResourcesRootController.inAppNotificationBackground(theme) - + self.theme = theme + super.init() - self.addSubnode(self.backgroundNode) if let contentNode { self.addSubnode(contentNode) } @@ -65,6 +64,8 @@ final class NotificationItemContainerNode: ASDisplayNode { override func didLoad() { super.didLoad() + self.view.insertSubview(self.backgroundView, at: 0) + if let contentNode = self.contentNode, !contentNode.acceptsTouches { self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) @@ -76,13 +77,13 @@ final class NotificationItemContainerNode: ASDisplayNode { func animateIn() { if let _ = self.validLayout { - self.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.backgroundNode.bounds.size.height), to: CGPoint(), duration: 0.4, additive: true) + self.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.backgroundView.frame.maxY), to: CGPoint(), duration: 0.4, additive: true) } } func animateOut(completion: @escaping () -> Void) { if let _ = self.validLayout { - self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -self.backgroundNode.bounds.size.height), duration: 0.4, removeOnCompletion: false, additive: true, completion: { _ in + self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -self.backgroundView.frame.maxY), duration: 0.4, removeOnCompletion: false, additive: true, completion: { _ in completion() }) } else { @@ -100,7 +101,7 @@ final class NotificationItemContainerNode: ASDisplayNode { if let statusBarHeight = layout.statusBarHeight, statusBarHeight >= 39.0 { if layout.deviceMetrics.hasDynamicIsland { - contentInsets.top = statusBarHeight + contentInsets.top = statusBarHeight + 6.0 } else if statusBarHeight >= 44.0 { contentInsets.top += 34.0 } else { @@ -113,7 +114,10 @@ final class NotificationItemContainerNode: ASDisplayNode { let contentWidth = containerWidth - contentInsets.left - contentInsets.right let contentHeight = contentNode.updateLayout(width: contentWidth, transition: transition) - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - containerWidth - 8.0 * 2.0) / 2.0), y: contentInsets.top - 16.0), size: CGSize(width: containerWidth + 8.0 * 2.0, height: 8.0 + contentHeight + 20.0 + 8.0))) + let backgroundInset: CGFloat = 8.0 + let backgroundSize = CGSize(width: containerWidth - backgroundInset * 2.0, height: contentHeight) + self.backgroundView.update(size: backgroundSize, cornerRadius: 24.0, isDark: self.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), transition: ComponentTransition(transition)) + transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - backgroundSize.width) / 2.0), y: contentInsets.top), size: backgroundSize)) transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - contentWidth) / 2.0), y: contentInsets.top), size: CGSize(width: contentWidth, height: contentHeight))) } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 355eacea..0a99f2c9 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -38,6 +38,7 @@ import TextFormat import BrowserUI import MediaEditorScreen import GiftSetupScreen +import AlertComponent private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer { if case .default = navigation { @@ -95,7 +96,7 @@ func openResolvedUrlImpl( present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) } case .inaccessiblePeer: - present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) case let .botStart(peer, payload): openPeer(EnginePeer(peer), .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive))) case let .groupBotStart(botPeerId, payload, adminRights, peerType): @@ -132,9 +133,8 @@ func openResolvedUrlImpl( let addMemberImpl = { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let theme = AlertControllerTheme(presentationData: presentationData) - let attributedTitle = NSAttributedString(string: presentationData.strings.Bot_AddToChat_Add_MemberAlertTitle, font: Font.semibold(presentationData.listsFontSize.baseDisplaySize), textColor: theme.primaryColor, paragraphAlignment: .center) - + let strings = presentationData.strings + var isGroup: Bool = false var peerTitle: String = "" if case let .legacyGroup(peer) = peer { @@ -147,51 +147,54 @@ func openResolvedUrlImpl( peerTitle = peer.title } - let text = isGroup ? presentationData.strings.Bot_AddToChat_Add_MemberAlertTextGroup(peerTitle).string : presentationData.strings.Bot_AddToChat_Add_MemberAlertTextChannel(peerTitle).string + let text = isGroup ? strings.Bot_AddToChat_Add_MemberAlertTextGroup(peerTitle).string : strings.Bot_AddToChat_Add_MemberAlertTextChannel(peerTitle).string - let body = MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor) - let bold = MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: theme.primaryColor) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center) - - let controller = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Bot_AddToChat_Add_MemberAlertAdd, action: { - if payload.isEmpty { - if peerId.namespace == Namespaces.Peer.CloudGroup { - let _ = (context.engine.peers.addGroupMember(peerId: peerId, memberId: botPeerId) - |> deliverOnMainQueue).startStandalone(completed: { - controller?.dismiss() - }) - } else { - let _ = (context.engine.peers.addChannelMember(peerId: peerId, memberId: botPeerId) - |> deliverOnMainQueue).startStandalone(completed: { - controller?.dismiss() - }) - } - } else { - let _ = (context.engine.messages.requestStartBotInGroup(botPeerId: botPeerId, groupPeerId: peerId, payload: payload) - |> deliverOnMainQueue).startStandalone(next: { result in - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> deliverOnMainQueue).startStandalone(next: { peer in - guard let peer = peer else { - return + let alertController = textAlertController( + context: context, + title: strings.Bot_AddToChat_Add_MemberAlertTitle, + text: text, + actions: [ + TextAlertAction(type: .defaultAction, title: strings.Bot_AddToChat_Add_MemberAlertAdd, action: { + if payload.isEmpty { + if peerId.namespace == Namespaces.Peer.CloudGroup { + let _ = (context.engine.peers.addGroupMember(peerId: peerId, memberId: botPeerId) + |> deliverOnMainQueue).startStandalone(completed: { + controller?.dismiss() + }) + } else { + let _ = (context.engine.peers.addChannelMember(peerId: peerId, memberId: botPeerId) + |> deliverOnMainQueue).startStandalone(completed: { + controller?.dismiss() + }) } - if let navigationController = navigationController { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) - } - switch result { - case let .channelParticipant(participant): - context.peerChannelMemberCategoriesContextsManager.externallyAdded(peerId: peerId, participant: participant) - case .none: - break - } - controller?.dismiss() - }) - }, error: { _ in - - }) - } - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - })], actionLayout: .vertical) - present(controller, nil) + } else { + let _ = (context.engine.messages.requestStartBotInGroup(botPeerId: botPeerId, groupPeerId: peerId, payload: payload) + |> deliverOnMainQueue).startStandalone(next: { result in + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).startStandalone(next: { peer in + guard let peer = peer else { + return + } + if let navigationController = navigationController { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) + } + switch result { + case let .channelParticipant(participant): + context.peerChannelMemberCategoriesContextsManager.externallyAdded(peerId: peerId, participant: participant) + case .none: + break + } + controller?.dismiss() + }) + }, error: { _ in + + }) + } + }), + TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: {}) + ] + ) + present(alertController, nil) } if case let .channel(peer) = peer { @@ -1340,15 +1343,21 @@ func openResolvedUrlImpl( storyProgressPauseContext.update(controller) } } else { - let controller = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Chat_ErrorCantBoost, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) - present(controller, nil) - - controller.dismissed = { _ in + let alertController = AlertScreen( + context: context, + title: nil, + text: presentationData.strings.Chat_ErrorCantBoost, + actions: [ + .init(title: presentationData.strings.Common_OK, type: .default) + ] + ) + alertController.dismissed = { _ in dismissedImpl?() } + present(alertController, nil) if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext { - storyProgressPauseContext.update(controller) + storyProgressPauseContext.update(alertController) } } case let .premiumGiftCode(slug): @@ -1515,6 +1524,7 @@ func openResolvedUrlImpl( let controller = context.sharedContext.makeGiftAuctionViewScreen( context: context, auctionContext: auctionContext, + peerId: nil, completion: { [weak navigationController] acquiredGifts, upgradeAttributes in if let upgradeAttributes { let controller = context.sharedContext.makeGiftAuctionWearPreviewScreen(context: context, auctionContext: auctionContext, acquiredGifts: acquiredGifts, attributes: upgradeAttributes, completion: { diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 73d0f665..bc32dcc7 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -561,9 +561,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur } } if isToken { - context.sharedContext.presentGlobalController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.AuthSessions_AddDevice_UrlLoginHint, actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { - }), + context.sharedContext.presentGlobalController(textAlertController(context: context, title: nil, text: presentationData.strings.AuthSessions_AddDevice_UrlLoginHint, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {}), ], parseMarkdown: true), nil) return } @@ -770,6 +768,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur var startApp: String? var text: String? var profile: Bool = false + var direct: Bool = false var referrer: String? var albumId: Int64? var collectionId: Int64? @@ -823,6 +822,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur startChannel = "" } else if queryItem.name == "profile" { profile = true + } else if queryItem.name == "direct" { + direct = true } else if queryItem.name == "startapp" { startApp = "" } @@ -918,6 +919,13 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur convertedUrl = current + "?profile" } } + if direct, let current = convertedUrl { + if current.contains("?") { + convertedUrl = current + "&direct" + } else { + convertedUrl = current + "?direct" + } + } } } else if parsedUrl.host == "hostOverride" { if let components = URLComponents(string: "/?" + query) { diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 83114722..399419c8 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -259,7 +259,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu let chatLocationContextHolder = Atomic(value: nil) - self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil }) + self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil }) self.historyNode.clipsToBounds = true super.init() @@ -881,7 +881,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu } let chatLocationContextHolder = Atomic(value: nil) - let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil }) + let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil }) historyNode.clipsToBounds = true historyNode.preloadPages = true historyNode.stackFromBottom = true diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 90cafd6c..3d727ce1 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -10,6 +10,7 @@ import TelegramCallsUI import TelegramUIPreferences import AccountContext import DeviceLocationManager +import ItemListUI import LegacyUI import ChatListUI import PeersNearbyUI @@ -92,6 +93,8 @@ import AttachmentFileController import NewContactScreen import PasskeysScreen import GiftDemoScreen +import ChatTextLinkEditUI +import CocoonInfoScreen private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -2242,6 +2245,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return ChatHistoryListNodeImpl( context: context, updatedPresentationData: updatedPresentationData, + systemStyle: .glass, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, @@ -3191,6 +3195,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let controller, case let .starGiftTransfer(_, _, gift, transferStars, _, _) = source else { return } + controller.view.window?.endEditing(true) + var dismissAlertImpl: (() -> Void)? let alertController = giftTransferAlertController( context: context, @@ -3317,7 +3323,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { controller.present(alertController, in: .current) dismissAlertImpl = { [weak alertController] in - alertController?.dismissAnimated() + alertController?.dismiss() } } @@ -3713,7 +3719,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return stickerMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed) } - public func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController { + public func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?) { return avatarMediaPickerController(context: context, getSourceRect: getSourceRect, canDelete: canDelete, performDelete: performDelete, completion: completion, dismissed: dismissed) } @@ -3857,20 +3863,20 @@ public final class SharedAccountContextImpl: SharedAccountContext { return GiftAuctionBidScreen(context: context, toPeerId: toPeerId, text: text, entities: entities, hideName: hideName, auctionContext: auctionContext, acquiredGifts: acquiredGifts) } - public func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController { - return GiftAuctionViewScreen(context: context, auctionContext: auctionContext, completion: completion) + public func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, peerId: EnginePeer.Id?, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController { + return GiftAuctionViewScreen(context: context, auctionContext: auctionContext, peerId: peerId, completion: completion) } public func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController { return GiftAuctionActiveBidsScreen(context: context) } - public func makeGiftOfferScreen(context: AccountContext, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController { - return giftOfferAlertController(context: context, gift: gift, peer: peer, amount: amount, commit: commit) + public func makeGiftOfferScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController { + return giftOfferAlertController(context: context, updatedPresentationData: updatedPresentationData, gift: gift, peer: peer, amount: amount, commit: commit) } - public func makeGiftUpgradeVariantsPreviewScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute]) -> ViewController { - return GiftUpgradePreviewScreen(context: context, gift: gift, attributes: attributes) + public func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController { + return GiftUpgradeVariantsScreen(context: context, gift: gift, attributes: attributes, selectedAttributes: selectedAttributes, focusedAttribute: focusedAttribute) } public func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController { @@ -4023,6 +4029,14 @@ public final class SharedAccountContextImpl: SharedAccountContext { return SendInviteLinkScreen(context: context, subject: subject, peers: peers, theme: theme) } + public func makeCocoonInfoScreen(context: AccountContext) -> ViewController { + return CocoonInfoScreen(context: context) + } + + public func makeLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, text: String, link: String?, apply: @escaping (String?) -> Void) -> ViewController { + return chatTextLinkEditController(context: context, updatedPresentationData: updatedPresentationData, text: text, link: link, apply: apply) + } + @available(iOS 13.0, *) public func makePostSuggestionsSettingsScreen(context: AccountContext, peerId: EnginePeer.Id) async -> ViewController { return await PostSuggestionsSettingsScreen(context: context, peerId: peerId, completion: {}) diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index c0f1427e..8b4fee9e 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -31,7 +31,6 @@ import PeerInfoScreen import PeerInfoStoryGridScreen import ShareWithPeersScreen import ChatEmptyNode -import UndoUI private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode { private var presentationData: PresentationData @@ -42,7 +41,7 @@ private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceh init(context: AccountContext) { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: true) self.emptyNode = ChatEmptyNode(context: context, interaction: nil) @@ -55,7 +54,7 @@ private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceh func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.presentationInterfaceState.limitsConfiguration, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.presentationInterfaceState.accountPeerId, mode: .standard(.default), chatLocation: self.presentationInterfaceState.chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.presentationInterfaceState.limitsConfiguration, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.presentationInterfaceState.accountPeerId, mode: .standard(.default), chatLocation: self.presentationInterfaceState.chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) self.wallpaperBackgroundNode.update(wallpaper: presentationData.chatWallpaper, animated: false) } @@ -188,7 +187,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon } public func addRootControllers(showCallsTab: Bool) { - let tabBarController = TabBarControllerImpl(theme: self.presentationData.theme) + let tabBarController = TabBarControllerImpl(theme: self.presentationData.theme, strings: self.presentationData.strings) tabBarController.navigationPresentation = .master let chatListController = self.context.sharedContext.makeChatListController(context: self.context, location: .chatList(groupId: .root), controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild) if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl { diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift index f79a3a6d..9e944a28 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift @@ -96,7 +96,7 @@ final class VerticalListContextResultsChatInputPanelButtonItemNode: ListViewItem self.titleNode = TextNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.separatorNode) diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift index 274d87db..86c40fa8 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift @@ -118,7 +118,7 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { self.iconImageNode.isLayerBacked = !smartInvertColorsEnabled() self.iconImageNode.displaysAsynchronously = false - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.separatorNode) diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index 319cb169..77f46844 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -32,7 +32,6 @@ public struct ExperimentalUISettings: Codable, Equatable { public var chatListPhotos: Bool public var knockoutWallpaper: Bool public var foldersTabAtBottom: Bool - public var playerEmbedding: Bool public var preferredVideoCodec: String? public var disableVideoAspectScaling: Bool public var enableVoipTcp: Bool @@ -80,7 +79,6 @@ public struct ExperimentalUISettings: Codable, Equatable { chatListPhotos: false, knockoutWallpaper: false, foldersTabAtBottom: false, - playerEmbedding: false, preferredVideoCodec: nil, disableVideoAspectScaling: false, enableVoipTcp: false, @@ -129,7 +127,6 @@ public struct ExperimentalUISettings: Codable, Equatable { chatListPhotos: Bool, knockoutWallpaper: Bool, foldersTabAtBottom: Bool, - playerEmbedding: Bool, preferredVideoCodec: String?, disableVideoAspectScaling: Bool, enableVoipTcp: Bool, @@ -175,7 +172,6 @@ public struct ExperimentalUISettings: Codable, Equatable { self.chatListPhotos = chatListPhotos self.knockoutWallpaper = knockoutWallpaper self.foldersTabAtBottom = foldersTabAtBottom - self.playerEmbedding = playerEmbedding self.preferredVideoCodec = preferredVideoCodec self.disableVideoAspectScaling = disableVideoAspectScaling self.enableVoipTcp = enableVoipTcp @@ -225,7 +221,6 @@ public struct ExperimentalUISettings: Codable, Equatable { self.chatListPhotos = (try container.decodeIfPresent(Int32.self, forKey: "chatListPhotos") ?? 0) != 0 self.knockoutWallpaper = (try container.decodeIfPresent(Int32.self, forKey: "knockoutWallpaper") ?? 0) != 0 self.foldersTabAtBottom = (try container.decodeIfPresent(Int32.self, forKey: "foldersTabAtBottom") ?? 0) != 0 - self.playerEmbedding = (try container.decodeIfPresent(Int32.self, forKey: "playerEmbedding") ?? 0) != 0 self.preferredVideoCodec = try container.decodeIfPresent(String.self.self, forKey: "preferredVideoCodec") self.disableVideoAspectScaling = (try container.decodeIfPresent(Int32.self, forKey: "disableVideoAspectScaling") ?? 0) != 0 self.enableVoipTcp = (try container.decodeIfPresent(Int32.self, forKey: "enableVoipTcp") ?? 0) != 0 @@ -275,7 +270,6 @@ public struct ExperimentalUISettings: Codable, Equatable { try container.encode((self.chatListPhotos ? 1 : 0) as Int32, forKey: "chatListPhotos") try container.encode((self.knockoutWallpaper ? 1 : 0) as Int32, forKey: "knockoutWallpaper") try container.encode((self.foldersTabAtBottom ? 1 : 0) as Int32, forKey: "foldersTabAtBottom") - try container.encode((self.playerEmbedding ? 1 : 0) as Int32, forKey: "playerEmbedding") try container.encodeIfPresent(self.preferredVideoCodec, forKey: "preferredVideoCodec") try container.encode((self.disableVideoAspectScaling ? 1 : 0) as Int32, forKey: "disableVideoAspectScaling") try container.encode((self.enableVoipTcp ? 1 : 0) as Int32, forKey: "enableVoipTcp") diff --git a/submodules/TelegramUpdateUI/Sources/UpdateInfoItem.swift b/submodules/TelegramUpdateUI/Sources/UpdateInfoItem.swift index d64c98a5..f0794345 100644 --- a/submodules/TelegramUpdateUI/Sources/UpdateInfoItem.swift +++ b/submodules/TelegramUpdateUI/Sources/UpdateInfoItem.swift @@ -152,7 +152,7 @@ class UpdateInfoItemNode: ListViewItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.addSubnode(self.iconNode) self.addSubnode(self.overlayNode) diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index aceef6e4..50ee9631 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -412,6 +412,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable { case ton(tinted: Bool) case animation(name: String) case verification + case dice } public let interactivelySelectedFromPackId: ItemCollectionId? diff --git a/submodules/TranslateUI/BUILD b/submodules/TranslateUI/BUILD index 6de2b55b..376c7130 100644 --- a/submodules/TranslateUI/BUILD +++ b/submodules/TranslateUI/BUILD @@ -31,7 +31,6 @@ swift_library( "//submodules/Components/BundleIconComponent:BundleIconComponent", "//submodules/UndoUI:UndoUI", "//submodules/ActivityIndicator:ActivityIndicator", - "//submodules/ChatListSearchItemNode:ChatListSearchItemNode", "//submodules/ShimmerEffect:ShimmerEffect", ], visibility = [ diff --git a/submodules/TranslateUI/Sources/ChatTranslation.swift b/submodules/TranslateUI/Sources/ChatTranslation.swift index f247b69c..f7d16c25 100644 --- a/submodules/TranslateUI/Sources/ChatTranslation.swift +++ b/submodules/TranslateUI/Sources/ChatTranslation.swift @@ -270,47 +270,55 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id, var count = 0 for message in messages { if message.effectivelyIncoming(context.account.peerId), message.text.count >= 10 { - var text = String(message.text.prefix(256)) - if var entities = message.textEntitiesAttribute?.entities.filter({ entity in - switch entity.type { - case .Pre, .Code, .Url, .Email, .Mention, .Hashtag, .BotCommand: - return true - default: - return false + if let summaryAttribute = message.attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute, !summaryAttribute.fromLang.isEmpty { + let fromLang = normalizeTranslationLanguage(summaryAttribute.fromLang) + if supportedTranslationLanguages.contains(fromLang) { + fromLangs[fromLang] = (fromLangs[fromLang] ?? 0) + message.text.count + count += 1 } - }) { - entities = entities.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) - var ranges: [Range] = [] - for entity in entities { - if entity.range.lowerBound > text.count || entity.range.upperBound > text.count { - continue + } else { + var text = String(message.text.prefix(256)) + if var entities = message.textEntitiesAttribute?.entities.filter({ entity in + switch entity.type { + case .Pre, .Code, .Url, .Email, .Mention, .Hashtag, .BotCommand: + return true + default: + return false } - ranges.append(text.index(text.startIndex, offsetBy: entity.range.lowerBound) ..< text.index(text.startIndex, offsetBy: entity.range.upperBound)) - } - for range in ranges { - if range.upperBound < text.endIndex { - text.removeSubrange(range) + }) { + entities = entities.sorted(by: { $0.range.lowerBound > $1.range.lowerBound }) + var ranges: [Range] = [] + for entity in entities { + if entity.range.lowerBound > text.count || entity.range.upperBound > text.count { + continue + } + ranges.append(text.index(text.startIndex, offsetBy: entity.range.lowerBound) ..< text.index(text.startIndex, offsetBy: entity.range.upperBound)) + } + for range in ranges { + if range.upperBound < text.endIndex { + text.removeSubrange(range) + } } } - } - - if message.text.count < 10 { - continue - } - - languageRecognizer.processString(text) - let hypotheses = languageRecognizer.languageHypotheses(withMaximum: 4) - languageRecognizer.reset() - - let filteredLanguages = hypotheses.filter { supportedTranslationLanguages.contains(normalizeTranslationLanguage($0.key.rawValue)) }.sorted(by: { $0.value > $1.value }) - if let language = filteredLanguages.first { - let fromLang = normalizeTranslationLanguage(language.key.rawValue) - if loggingEnabled && !["en", "ru"].contains(fromLang) && !dontTranslateLanguages.contains(fromLang) { - Logger.shared.log("ChatTranslation", "\(text)") - Logger.shared.log("ChatTranslation", "Recognized as: \(fromLang), other hypotheses: \(hypotheses.map { $0.key.rawValue }.joined(separator: ",")) ") + + if message.text.count < 10 { + continue + } + + languageRecognizer.processString(text) + let hypotheses = languageRecognizer.languageHypotheses(withMaximum: 4) + languageRecognizer.reset() + + let filteredLanguages = hypotheses.filter { supportedTranslationLanguages.contains(normalizeTranslationLanguage($0.key.rawValue)) }.sorted(by: { $0.value > $1.value }) + if let language = filteredLanguages.first { + let fromLang = normalizeTranslationLanguage(language.key.rawValue) + if loggingEnabled && !["en", "ru"].contains(fromLang) && !dontTranslateLanguages.contains(fromLang) { + Logger.shared.log("ChatTranslation", "\(text)") + Logger.shared.log("ChatTranslation", "Recognized as: \(fromLang), other hypotheses: \(hypotheses.map { $0.key.rawValue }.joined(separator: ",")) ") + } + fromLangs[fromLang] = (fromLangs[fromLang] ?? 0) + message.text.count + count += 1 } - fromLangs[fromLang] = (fromLangs[fromLang] ?? 0) + message.text.count - count += 1 } } if count >= 16 { diff --git a/submodules/TranslateUI/Sources/LocalizationListItem.swift b/submodules/TranslateUI/Sources/LocalizationListItem.swift index 5ea365bc..07ba6eba 100644 --- a/submodules/TranslateUI/Sources/LocalizationListItem.swift +++ b/submodules/TranslateUI/Sources/LocalizationListItem.swift @@ -7,7 +7,6 @@ import TelegramPresentationData import ItemListUI import PresentationDataUtils import ActivityIndicator -import ChatListSearchItemNode import ShimmerEffect public struct LocalizationListItemEditing: Equatable { @@ -185,7 +184,7 @@ class LocalizationListItemNode: ItemListRevealOptionsItemNode { self.activateArea = AccessibilityAreaNode() - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.containerNode) @@ -314,7 +313,7 @@ class LocalizationListItemNode: ItemListRevealOptionsItemNode { if strongSelf.maskNode.supernode == nil { strongSelf.addSubnode(strongSelf.maskNode) } - let hasCorners = itemListHasRoundedBlockLayout(params) + let hasCorners = itemListHasRoundedBlockLayout(params) && !item.alwaysPlain var hasTopCorners = false var hasBottomCorners = false switch neighbors.top { diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index e408d910..24ed6920 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -18,7 +18,7 @@ public enum UndoOverlayContent { case swipeToReply(title: String, text: String) case actionSucceeded(title: String?, text: String, cancel: String?, destructive: Bool) case stickersModified(title: String, text: String, undo: Bool, info: StickerPackCollectionInfo, topItem: StickerPackItem?, context: AccountContext) - case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?) + case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?, changeAction: String?) case chatAddedToFolder(context: AccountContext, chatTitle: String, folderTitle: NSAttributedString) case chatRemovedFromFolder(context: AccountContext, chatTitle: String, folderTitle: NSAttributedString) case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 44be88e5..0360f3cf 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -53,6 +53,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { private let buttonNode: HighlightTrackingButtonNode private let undoButtonTextNode: ImmediateTextNode private let undoButtonNode: HighlightTrackingButtonNode + + private let changeButtonBackground: ASImageNode + private let changeButtonTextNode: ImmediateTextNode + private let panelNode: ASDisplayNode private let panelWrapperNode: ASDisplayNode private let action: (UndoOverlayAction) -> Bool @@ -103,6 +107,14 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.buttonNode = HighlightTrackingButtonNode() + self.changeButtonBackground = ASImageNode() + self.changeButtonBackground.displaysAsynchronously = false + self.changeButtonBackground.isUserInteractionEnabled = false + + self.changeButtonTextNode = ImmediateTextNode() + self.changeButtonTextNode.displaysAsynchronously = false + self.changeButtonTextNode.isUserInteractionEnabled = false + var displayUndo = true var undoText = presentationData.strings.Undo_Undo var undoTextColor = presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0) @@ -624,7 +636,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: resource._asResource(), isVideo: isVideo), width: 80, height: 80, mode: .direct(cachePathPrefix: nil)) } } - case let .dice(dice, context, text, action): + case let .dice(dice, context, text, action, changeAction): self.avatarNode = nil self.iconNode = nil self.iconCheckNode = nil @@ -633,7 +645,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural) + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural).mutableCopy() as! NSMutableAttributedString + if let range = attributedText.string.range(of: "$"), let icon = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonMedium"), color: .white) { + attributedText.addAttribute(.attachment, value: icon, range: NSRange(range, in: attributedText.string)) + attributedText.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedText.string)) + } self.textNode.attributedText = attributedText if let action = action { displayUndo = true @@ -651,6 +667,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { default: break } + + if let changeAction { + self.changeButtonTextNode.attributedText = NSAttributedString(string: changeAction, font: Font.regular(12.0), textColor: undoTextColor) + self.changeButtonBackground.image = generateStretchableFilledCircleImage(diameter: 18.0, color: undoTextColor.withMultipliedAlpha(0.1)) + } if dice.emoji == "๐ŸŽฐ" { let slotMachineNode = SlotMachineAnimationNode(account: context.account, size: CGSize(width: 42.0, height: 42.0)) @@ -670,8 +691,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { switch stickerPack { case let .result(_, items, _): let item = items[Int(value)] - - animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: item.file._parse().resource), width: 120, height: 120, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: item.file._parse().resource), width: 120, height: 120, playbackMode: .still(.end), mode: .direct(cachePathPrefix: nil)) default: break } @@ -1553,7 +1573,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.panelNode.backgroundColor = .clear } self.panelNode.clipsToBounds = true - self.panelNode.cornerRadius = 14.0 + self.panelNode.cornerRadius = 25.0 self.panelWrapperNode = ASDisplayNode() @@ -1625,6 +1645,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.panelWrapperNode.addSubnode(self.undoButtonTextNode) self.panelWrapperNode.addSubnode(self.undoButtonNode) } + if self.changeButtonBackground.image != nil { + self.panelWrapperNode.addSubnode(self.changeButtonBackground) + self.panelWrapperNode.addSubnode(self.changeButtonTextNode) + } self.addSubnode(self.panelNode) self.addSubnode(self.panelWrapperNode) @@ -1962,7 +1986,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } contentHeight += textSize.height - contentHeight = max(49.0, contentHeight) + contentHeight = max(50.0, contentHeight) var insets = layout.insets(options: [.input]) switch self.placementPosition { @@ -2024,6 +2048,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { transition.updateFrame(node: self.textNode, frame: textFrame) } + if self.changeButtonTextNode.supernode != nil { + let changeButtonTextSize = self.changeButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) + let changeButtonFrame = CGRect(origin: CGPoint(x: textFrame.maxX + 10.0, y: floorToScreenPixels(textFrame.midY - changeButtonTextSize.height * 0.5)), size: changeButtonTextSize) + transition.updateFrame(node: self.changeButtonTextNode, frame: changeButtonFrame) + transition.updateFrame(node: self.changeButtonBackground, frame: changeButtonFrame.insetBy(dx: -6.0, dy: -2.0)) + } + if let iconNode = self.iconNode { let iconSize: CGSize if let size = self.iconImageSize { diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index f691900a..82ace727 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -85,6 +85,7 @@ public enum ParsedInternalPeerUrlParameter { case boost case text(String) case profile + case direct case referrer(String) case storyFolder(Int64) case giftCollection(Int64) @@ -393,6 +394,8 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou return .peer(.name(peerName), .boost) } else if queryItem.name == "profile" { return .peer(.name(peerName), .profile) + } else if queryItem.name == "direct" { + return .peer(.name(peerName), .direct) } else if queryItem.name == "startapp" { var mode: ResolvedStartAppMode = .generic if let queryItems = components.queryItems { @@ -836,6 +839,26 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) switch parameter { case .profile: return .single(.result(.peer(peer._asPeer(), .info(nil)))) + case .direct: + if case let .channel(channel) = peer, let monoforumId = channel.linkedMonoforumId { + return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: monoforumId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return context.engine.peers.findChannelById(channelId: monoforumId.id._internalGetInt64Value()) + } + } + |> map { peer -> ResolveInternalUrlResult in + if let peer { + return .result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))) + } else { + return .result(.peer(nil, .info(nil))) + } + } + } else { + return .single(.result(.peer(nil, .info(nil)))) + } case let .text(text): var textInputState: ChatTextInputState? if !text.isEmpty { @@ -1238,9 +1261,9 @@ public func isTelegramMeLink(_ url: String) -> Bool { public func isTelegraPhLink(_ url: String) -> Bool { let schemes = ["http://", "https://", ""] - for basePath in baseTelegramMePaths { + for basePath in baseTelegraPhPaths { for scheme in schemes { - let basePrefix = scheme + basePath + "/" + let basePrefix = scheme + basePath if url.lowercased().hasPrefix(basePrefix) { return true } diff --git a/submodules/Utils/DeviceModel/Sources/DeviceModel.swift b/submodules/Utils/DeviceModel/Sources/DeviceModel.swift index c2392f3d..2717d827 100644 --- a/submodules/Utils/DeviceModel/Sources/DeviceModel.swift +++ b/submodules/Utils/DeviceModel/Sources/DeviceModel.swift @@ -52,7 +52,12 @@ public enum DeviceModel: CaseIterable, Equatable { .iPhone16, .iPhone16Plus, .iPhone16Pro, - .iPhone16ProMax + .iPhone16ProMax, + .iPhone16e, + .iPhone17, + .iPhone17Pro, + .iPhone17ProMax, + .iPhoneAir ] } diff --git a/submodules/WallpaperBackgroundNode/BUILD b/submodules/WallpaperBackgroundNode/BUILD index ccd9dff8..ccba786f 100644 --- a/submodules/WallpaperBackgroundNode/BUILD +++ b/submodules/WallpaperBackgroundNode/BUILD @@ -68,6 +68,9 @@ swift_library( "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 8c96748f..80b974c6 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -16,6 +16,7 @@ import AppBundle import AnimatedStickerNode import TelegramAnimatedStickerNode import HierarchyTrackingLayer +import EdgeEffect private let motionAmount: CGFloat = 32.0 @@ -32,7 +33,7 @@ private func generateBlurredContents(image: UIImage, dimColor: UIColor?) -> UIIm telegramFastBlurMore(Int32(context.size.width), Int32(context.size.height), Int32(context.bytesPerRow), context.bytes) adjustSaturationInContext(context: context, saturation: 1.7) - + if let dimColor { context.withFlippedContext { c in c.setFillColor(dimColor.cgColor) @@ -43,6 +44,64 @@ private func generateBlurredContents(image: UIImage, dimColor: UIColor?) -> UIIm return context.generateImage() } +private func calculateWallpaperBrightness(from colors: [UInt32]) -> CGFloat { + guard !colors.isEmpty else { + return 1.0 + } + return UIColor.average(of: colors.map(UIColor.init(rgb:))).hsb.b +} + +private func calculateWallpaperBrightness(from image: UIImage) -> CGFloat { + guard let cgImage = image.cgImage else { + return 1.0 + } + + let sourceWidth = cgImage.width + let sourceHeight = cgImage.height + let topRegionHeight = max(1, Int(CGFloat(sourceHeight) * 0.1)) + let cropRect = CGRect(x: 0, y: 0, width: sourceWidth, height: topRegionHeight) + + guard let croppedImage = cgImage.cropping(to: cropRect) else { + return 1.0 + } + + let targetSize = CGSize(width: 10.0, height: 10.0) + let width = Int(targetSize.width) + let height = Int(targetSize.height) + let bytesPerPixel = 4 + let bytesPerRow = bytesPerPixel * width + let bitsPerComponent = 8 + + var pixelData = [UInt8](repeating: 0, count: width * height * bytesPerPixel) + + guard let context = CGContext( + data: &pixelData, + width: width, + height: height, + bitsPerComponent: bitsPerComponent, + bytesPerRow: bytesPerRow, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue + ) else { + return 1.0 + } + + context.draw(croppedImage, in: CGRect(origin: .zero, size: targetSize)) + + var totalLuminance: CGFloat = 0.0 + let pixelCount = width * height + + for i in 0 ..< pixelCount { + let offset = i * bytesPerPixel + let r = CGFloat(pixelData[offset]) / 255.0 + let g = CGFloat(pixelData[offset + 1]) / 255.0 + let b = CGFloat(pixelData[offset + 2]) / 255.0 + totalLuminance += 0.299 * r + 0.587 * g + 0.114 * b + } + + return totalLuminance / CGFloat(pixelCount) +} + public enum WallpaperBubbleType { case incoming case outgoing @@ -97,12 +156,14 @@ public struct WallpaperEdgeEffectEdge: Equatable { } public protocol WallpaperEdgeEffectNode: ASDisplayNode { - func update(rect: CGRect, edge: WallpaperEdgeEffectEdge, containerSize: CGSize, transition: ContainedViewLayoutTransition) + func update(rect: CGRect, edge: WallpaperEdgeEffectEdge, blur: Bool, containerSize: CGSize, transition: ContainedViewLayoutTransition) } public protocol WallpaperBackgroundNode: ASDisplayNode { var isReady: Signal { get } var rotation: CGFloat { get set } + var isDark: Bool? { get } + var isDarkUpdated: (() -> Void)? { get set } func update(wallpaper: TelegramWallpaper, animated: Bool) func update(wallpaper: TelegramWallpaper, starGift: StarGift?, animated: Bool) @@ -122,7 +183,7 @@ public protocol WallpaperBackgroundNode: ASDisplayNode { func makeEdgeEffectNode() -> WallpaperEdgeEffectNode? } -private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOverlayLayer { +final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOverlayLayer { final class CloneLayer: SimpleLayer { private weak var parentLayer: EffectImageLayer? private var index: SparseBag>.Index? @@ -785,9 +846,9 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou private let context: AccountContext private let useSharedAnimationPhase: Bool - private let contentNode: ASDisplayNode + let contentNode: ASDisplayNode - fileprivate let edgeEffectNodes = SparseBag>() + let edgeEffectNodes = SparseBag>() private var blurredBackgroundContents: UIImage? @@ -836,9 +897,9 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou } } - fileprivate var gradientBackgroundNode: GradientBackgroundNode? + var gradientBackgroundNode: GradientBackgroundNode? private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode? - fileprivate let patternImageLayer: EffectImageLayer + let patternImageLayer: EffectImageLayer private let dimLayer: SimpleLayer private var isGeneratingPatternImage: Bool = false @@ -980,11 +1041,21 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou } private static var cachedSharedPattern: (PatternKey, UIImage)? + public private(set) var isDark: Bool? + public var isDarkUpdated: (() -> Void)? + + private func updateIsDark(_ isDark: Bool?) { + if self.isDark != isDark { + self.isDark = isDark + self.isDarkUpdated?() + } + } + private let _isReady = ValuePromise(false, ignoreRepeated: true) public var isReady: Signal { return self._isReady.get() } - + init(context: AccountContext, useSharedAnimationPhase: Bool) { self.context = context self.useSharedAnimationPhase = useSharedAnimationPhase @@ -1132,6 +1203,12 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou self.blurredBackgroundContents = nil self.motionEnabled = false self.wallpaperDisposable.set(nil) + + if case let .file(file) = wallpaper, file.isPattern { + self.updateIsDark(nil) + } else { + self.updateIsDark(calculateWallpaperBrightness(from: gradientColors) <= 0.3) + } } else { if let gradientBackgroundNode = self.gradientBackgroundNode { self.gradientBackgroundNode = nil @@ -1160,17 +1237,20 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou self.contentNode.contents = image?.cgImage self.blurredBackgroundContents = image self.wallpaperDisposable.set(nil) + self.updateIsDark(calculateWallpaperBrightness(from: gradientColors) <= 0.3) } else if gradientColors.count >= 1 { self.contentNode.backgroundColor = UIColor(rgb: gradientColors[0]) self.contentNode.contents = nil self.blurredBackgroundContents = nil self.wallpaperDisposable.set(nil) + self.updateIsDark(calculateWallpaperBrightness(from: gradientColors) <= 0.3) } else { self.contentNode.backgroundColor = .white if let image = chatControllerBackgroundImage(theme: nil, wallpaper: wallpaper, mediaBox: self.context.sharedContext.accountManager.mediaBox, knockoutMode: false) { self.contentNode.contents = image.cgImage self.blurredBackgroundContents = generateBlurredContents(image: image, dimColor: wallpaperDimColor) self.wallpaperDisposable.set(nil) + self.updateIsDark(calculateWallpaperBrightness(from: image) <= 0.55) Queue.mainQueue().justDispatch { self._isReady.set(true) } @@ -1178,6 +1258,7 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou self.contentNode.contents = image.cgImage self.blurredBackgroundContents = generateBlurredContents(image: image, dimColor: wallpaperDimColor) self.wallpaperDisposable.set(nil) + self.updateIsDark(calculateWallpaperBrightness(from: image) <= 0.55) Queue.mainQueue().justDispatch { self._isReady.set(true) } @@ -1190,10 +1271,16 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou strongSelf.contentNode.contents = image?.0?.cgImage if let image = image?.0 { strongSelf.blurredBackgroundContents = generateBlurredContents(image: image, dimColor: wallpaperDimColor) + strongSelf.updateIsDark(calculateWallpaperBrightness(from: image) <= 0.55) } else { strongSelf.blurredBackgroundContents = nil } strongSelf.updateBubbles() + for edgeEffectNode in strongSelf.edgeEffectNodes { + if let edgeEffectNode = edgeEffectNode.value { + edgeEffectNode.updateContents() + } + } strongSelf._isReady.set(true) })) } @@ -1227,8 +1314,13 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou } } self.updateBubbles() - self.updateDimming() + + for edgeEffectNode in self.edgeEffectNodes { + if let edgeEffectNode = edgeEffectNode.value { + edgeEffectNode.updateContents() + } + } } public func _internalUpdateIsSettingUpWallpaper() { @@ -1792,156 +1884,6 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou } } -private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode { - private struct Params: Equatable { - let rect: CGRect - let edge: WallpaperEdgeEffectEdge - let containerSize: CGSize - - init(rect: CGRect, edge: WallpaperEdgeEffectEdge, containerSize: CGSize) { - self.rect = rect - self.edge = edge - self.containerSize = containerSize - } - } - - private var gradientNode: GradientBackgroundNode.CloneNode? - private let patternImageLayer: EffectImageLayer.CloneLayer - - private let containerNode: ASDisplayNode - private let containerMaskingNode: ASDisplayNode - private let overlayNode: ASDisplayNode - private let maskView: UIImageView - - private weak var parentNode: WallpaperBackgroundNodeImpl? - private var index: Int? - private var params: Params? - - private var isInverted: Bool = false - - init(parentNode: WallpaperBackgroundNodeImpl) { - self.parentNode = parentNode - - if let gradientBackgroundNode = parentNode.gradientBackgroundNode { - self.gradientNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false) - } else { - self.gradientNode = nil - } - - self.patternImageLayer = EffectImageLayer.CloneLayer(parentLayer: parentNode.patternImageLayer) - - self.containerNode = ASDisplayNode() - self.containerNode.anchorPoint = CGPoint() - self.containerNode.clipsToBounds = true - - self.containerMaskingNode = ASDisplayNode() - self.containerMaskingNode.addSubnode(self.containerNode) - - self.overlayNode = ASDisplayNode() - - self.maskView = UIImageView() - - super.init() - - if let gradientNode = self.gradientNode { - self.containerNode.addSubnode(gradientNode) - } - //self.layer.addSublayer(self.patternImageLayer) - - self.addSubnode(self.containerMaskingNode) - self.containerMaskingNode.view.mask = self.maskView - - self.containerNode.addSubnode(self.overlayNode) - - self.index = parentNode.edgeEffectNodes.add(Weak(self)) - } - - deinit { - if let index = self.index, let parentNode = self.parentNode { - parentNode.edgeEffectNodes.remove(index) - } - } - - func updateGradientNode() { - if let gradientBackgroundNode = self.parentNode?.gradientBackgroundNode { - if self.gradientNode == nil { - let gradientNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false) - self.gradientNode = gradientNode - self.containerNode.insertSubnode(gradientNode, at: 0) - - if let params = self.params { - self.updateImpl(rect: params.rect, edge: params.edge, containerSize: params.containerSize, transition: .immediate) - } - } - } else { - if let gradientNode = self.gradientNode { - self.gradientNode = nil - gradientNode.removeFromSupernode() - } - } - } - - func updatePattern(isInverted: Bool) { - if self.isInverted != isInverted { - self.isInverted = isInverted - - self.overlayNode.backgroundColor = isInverted ? .black : .clear - } - } - - func update(rect: CGRect, edge: WallpaperEdgeEffectEdge, containerSize: CGSize, transition: ContainedViewLayoutTransition) { - let params = Params(rect: rect, edge: edge, containerSize: containerSize) - if self.params != params { - self.params = params - self.updateImpl(rect: params.rect, edge: params.edge, containerSize: params.containerSize, transition: transition) - } - } - - private func updateImpl(rect: CGRect, edge: WallpaperEdgeEffectEdge, containerSize: CGSize, transition: ContainedViewLayoutTransition) { - transition.updateFrame(node: self.containerMaskingNode, frame: CGRect(origin: CGPoint(), size: rect.size)) - transition.updateBounds(node: self.containerNode, bounds: CGRect(origin: CGPoint(x: rect.minX, y: rect.minY), size: rect.size)) - - if self.maskView.image?.size.height != edge.size { - let baseGradientAlpha: CGFloat = 0.75 - let numSteps = 8 - let firstStep = 1 - let firstLocation = 0.0 - let colors: [UIColor] = (0 ..< numSteps).map { i in - if i < firstStep { - return UIColor(white: 1.0, alpha: 1.0) - } else { - let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) - let value: CGFloat = bezierPoint(0.42, 0.0, 0.58, 1.0, step) - return UIColor(white: 1.0, alpha: baseGradientAlpha * value) - } - } - let locations: [CGFloat] = (0 ..< numSteps).map { i in - if i < firstStep { - return 0.0 - } else { - let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) - return (firstLocation + (1.0 - firstLocation) * step) - } - } - - self.maskView.image = generateGradientImage( - size: CGSize(width: 8.0, height: edge.size), - colors: colors, - locations: locations - )?.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(edge.size)) - } - - transition.updateFrame(view: self.maskView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: rect.size)) - - transition.updateFrame(node: self.overlayNode, frame: CGRect(origin: CGPoint(), size: containerSize)) - - if let gradientNode = self.gradientNode { - transition.updateFrame(node: gradientNode, frame: CGRect(origin: CGPoint(), size: containerSize)) - } - transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(), size: containerSize)) - } -} - private protocol WallpaperComponentView: AnyObject { var view: UIView { get } diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperEdgeEffectNodeImpl.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperEdgeEffectNodeImpl.swift new file mode 100644 index 00000000..8898e7f8 --- /dev/null +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperEdgeEffectNodeImpl.swift @@ -0,0 +1,228 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import GradientBackground +import EdgeEffect +import SwiftSignalKit +import ComponentFlow +import ComponentDisplayAdapters + +final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode { + private struct Params: Equatable { + let rect: CGRect + let edge: WallpaperEdgeEffectEdge + let blur: Bool + let containerSize: CGSize + + init(rect: CGRect, edge: WallpaperEdgeEffectEdge, blur: Bool, containerSize: CGSize) { + self.rect = rect + self.edge = edge + self.blur = blur + self.containerSize = containerSize + } + } + + private var gradientNode: GradientBackgroundNode.CloneNode? + private let patternImageLayer: EffectImageLayer.CloneLayer + private let contentNode: ASDisplayNode + + private let containerNode: ASDisplayNode + private let containerMaskingNode: ASDisplayNode + private let overlayNode: ASDisplayNode + private let maskView: UIImageView + + private var blurView: VariableBlurView? + + private weak var parentNode: WallpaperBackgroundNodeImpl? + private var index: Int? + private var params: Params? + + private var isInverted: Bool = false + + init(parentNode: WallpaperBackgroundNodeImpl) { + self.parentNode = parentNode + + if let gradientBackgroundNode = parentNode.gradientBackgroundNode { + self.gradientNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false) + } else { + self.gradientNode = nil + } + + self.patternImageLayer = EffectImageLayer.CloneLayer(parentLayer: parentNode.patternImageLayer) + + self.contentNode = ASDisplayNode() + + self.containerNode = ASDisplayNode() + self.containerNode.anchorPoint = CGPoint() + self.containerNode.clipsToBounds = true + + self.containerMaskingNode = ASDisplayNode() + self.containerMaskingNode.addSubnode(self.containerNode) + + self.overlayNode = ASDisplayNode() + + self.maskView = UIImageView() + + super.init() + + self.containerNode.addSubnode(self.contentNode) + if let gradientNode = self.gradientNode { + self.containerNode.addSubnode(gradientNode) + } + //self.containerMaskingNode.layer.addSublayer(self.patternImageLayer) + + self.addSubnode(self.containerMaskingNode) + self.containerMaskingNode.view.mask = self.maskView + + self.containerNode.addSubnode(self.overlayNode) + + self.index = parentNode.edgeEffectNodes.add(Weak(self)) + } + + deinit { + if let index = self.index, let parentNode = self.parentNode { + parentNode.edgeEffectNodes.remove(index) + } + } + + func updateGradientNode() { + if let gradientBackgroundNode = self.parentNode?.gradientBackgroundNode { + if self.gradientNode == nil { + let gradientNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false) + self.gradientNode = gradientNode + self.containerNode.insertSubnode(gradientNode, at: 0) + + if let params = self.params { + self.updateImpl(rect: params.rect, edge: params.edge, blur: params.blur, containerSize: params.containerSize, transition: .immediate) + } + } + } else { + if let gradientNode = self.gradientNode { + self.gradientNode = nil + gradientNode.removeFromSupernode() + } + } + } + + func updatePattern(isInverted: Bool) { + if self.isInverted != isInverted { + self.isInverted = isInverted + + self.overlayNode.backgroundColor = isInverted ? .black : .clear + } + } + + func updateContents() { + guard let parentNode = self.parentNode else { + return + } + self.contentNode.contents = parentNode.contentNode.contents + self.contentNode.backgroundColor = parentNode.contentNode.backgroundColor + self.contentNode.alpha = parentNode.contentNode.alpha + self.contentNode.isHidden = parentNode.contentNode.isHidden + } + + func update(rect: CGRect, edge: WallpaperEdgeEffectEdge, blur: Bool, containerSize: CGSize, transition: ContainedViewLayoutTransition) { + let params = Params(rect: rect, edge: edge, blur: blur, containerSize: containerSize) + if self.params != params { + self.params = params + self.updateImpl(rect: params.rect, edge: params.edge, blur: params.blur, containerSize: params.containerSize, transition: transition) + } + } + + private func updateImpl(rect: CGRect, edge: WallpaperEdgeEffectEdge, blur: Bool, containerSize: CGSize, transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.containerMaskingNode, frame: CGRect(origin: CGPoint(), size: rect.size)) + transition.updateBounds(node: self.containerNode, bounds: CGRect(origin: CGPoint(x: rect.minX, y: rect.minY), size: rect.size)) + + if self.maskView.image?.size.height != edge.size { + let baseAlpha: CGFloat = 0.8 + let expSteps = 6 + let totalSteps = 18 + let expEndValue: CGFloat = 0.6 + + var colors: [UIColor] = [] + for i in 0 ..< expSteps { + let step = CGFloat(i) / CGFloat(expSteps - 1) + colors.append(UIColor(white: 1.0, alpha: bezierPoint(0.42, 0.0, 0.58, 1.0, step) * expEndValue)) + } + for i in 0 ..< (totalSteps - expSteps) { + let step = CGFloat(i) / CGFloat((totalSteps - expSteps) - 1) + colors.append(UIColor(white: 1.0, alpha: expEndValue * (1.0 - step) + 1.0 * step)) + } + + let locations: [CGFloat] = (0 ..< colors.count).map { i in + return CGFloat(i) / CGFloat(colors.count - 1) + } + + self.maskView.image = generateGradientImage( + size: CGSize(width: 8.0, height: edge.size), + colors: colors.map { $0.withMultipliedAlpha(baseAlpha) }, + locations: locations + )?.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(edge.size)) + } + + let maskFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: rect.size) + ComponentTransition(transition).setPosition(view: self.maskView, position: maskFrame.center) + ComponentTransition(transition).setBounds(view: self.maskView, bounds: CGRect(origin: CGPoint(), size: maskFrame.size)) + if case .top = edge.edge { + self.maskView.transform = CGAffineTransformMakeScale(1.0, -1.0) + } else { + self.maskView.transform = CGAffineTransformIdentity + } + + transition.updateFrame(node: self.overlayNode, frame: CGRect(origin: CGPoint(), size: containerSize)) + + if let gradientNode = self.gradientNode { + transition.updateFrame(node: gradientNode, frame: CGRect(origin: CGPoint(), size: containerSize)) + } + transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)) + + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: containerSize)) + + if blur { + let blurView: VariableBlurView + if let current = self.blurView { + blurView = current + } else { + let gradientMaskLayer = SimpleGradientLayer() + let baseGradientAlpha: CGFloat = 1.0 + let numSteps = 8 + let firstStep = 1 + let firstLocation = 0.8 + gradientMaskLayer.colors = (0 ..< numSteps).map { i in + if i < firstStep { + return UIColor(white: 1.0, alpha: 1.0).cgColor + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step) + return UIColor(white: 1.0, alpha: baseGradientAlpha * value).cgColor + } + } + gradientMaskLayer.locations = (0 ..< numSteps).map { i -> NSNumber in + if i < firstStep { + return 0.0 as NSNumber + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + return (firstLocation + (1.0 - firstLocation) * step) as NSNumber + } + } + + blurView = VariableBlurView(gradientMask: self.maskView.image ?? UIImage(), maxBlurRadius: 8.0) + blurView.layer.mask = gradientMaskLayer + self.view.insertSubview(blurView, at: 0) + self.blurView = blurView + } + blurView.update(size: bounds.size, transition: transition) + transition.updateFrame(view: blurView, frame: bounds) + if let maskLayer = blurView.layer.mask { + transition.updateFrame(layer: maskLayer, frame: bounds) + maskLayer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) + } + blurView.transform = self.maskView.transform + } else if let blurView = self.blurView { + self.blurView = nil + blurView.removeFromSuperview() + } + } +} diff --git a/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift b/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift index 28452c54..dba4d511 100644 --- a/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift @@ -60,7 +60,7 @@ final class WebSearchGalleryControllerPresentationArguments { } class WebSearchGalleryController: ViewController { - private static let navigationTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) + private static let navigationTheme = NavigationBarTheme(overallDarkAppearance: false, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) private var galleryNode: GalleryControllerNode { return self.displayNode as! GalleryControllerNode diff --git a/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift b/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift index 16649aa3..f7af0678 100644 --- a/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift +++ b/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift @@ -22,7 +22,7 @@ final class WebSearchNavigationContentNode: NavigationBarContentNode { self.theme = theme self.strings = strings - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: !attachment) + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), presentationTheme: theme, strings: strings, fieldStyle: .modern, displayBackground: !attachment) self.searchBar.hasCancelButton = attachment self.searchBar.placeholderString = NSAttributedString(string: attachment ? strings.Attachment_SearchWeb : strings.Common_Search, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) @@ -54,10 +54,12 @@ final class WebSearchNavigationContentNode: NavigationBarContentNode { return 56.0 } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0)) self.searchBar.frame = searchBarFrame self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + + return size } func activate(select: Bool = false) { diff --git a/submodules/WebSearchUI/Sources/WebSearchRecentQueryItem.swift b/submodules/WebSearchUI/Sources/WebSearchRecentQueryItem.swift index 071baf87..a6f0d79f 100644 --- a/submodules/WebSearchUI/Sources/WebSearchRecentQueryItem.swift +++ b/submodules/WebSearchUI/Sources/WebSearchRecentQueryItem.swift @@ -89,7 +89,7 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + super.init(layerBacked: false, rotated: false, seeThrough: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.separatorNode) diff --git a/submodules/WebUI/BUILD b/submodules/WebUI/BUILD index a812278b..23c32514 100644 --- a/submodules/WebUI/BUILD +++ b/submodules/WebUI/BUILD @@ -57,6 +57,10 @@ swift_library( "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/Components/BundleIconComponent", "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AvatarComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/WebUI/Sources/WebAppAddToAttachmentController.swift b/submodules/WebUI/Sources/WebAppAddToAttachmentController.swift new file mode 100644 index 00000000..5c96fd41 --- /dev/null +++ b/submodules/WebUI/Sources/WebAppAddToAttachmentController.swift @@ -0,0 +1,174 @@ +import Foundation +import UIKit +import SwiftSignalKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import AppBundle +import PhotoResources +import ComponentFlow +import AlertComponent +import AlertCheckComponent +import BundleIconComponent + +public func addWebAppToAttachmentController(context: AccountContext, peerName: String, icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile], requestWriteAccess: Bool, completion: @escaping (Bool) -> Void) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + let checkState = AlertCheckComponent.ExternalState() + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertWebAppAttachmentHeaderComponent(context: context, icons: icons) + ) + )) + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.WebApp_AddToAttachmentTitle) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.WebApp_AddToAttachmentText(peerName).string)) + ) + )) + if requestWriteAccess { + content.append(AnyComponentWithIdentity( + id: "check", + component: AnyComponent( + AlertCheckComponent(title: strings.WebApp_AddToAttachmentAllowMessages(peerName).string, initialValue: false, externalState: checkState) + ) + )) + } + + let alertController = AlertScreen( + context: context, + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.WebApp_AddToAttachmentAdd, type: .default, action: { + completion(requestWriteAccess && checkState.value) + }) + ] + ) + return alertController +} + +private final class AlertWebAppAttachmentHeaderComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + let context: AccountContext + let icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] + + public init( + context: AccountContext, + icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] + ) { + self.context = context + self.icons = icons + } + + public static func ==(lhs: AlertWebAppAttachmentHeaderComponent, rhs: AlertWebAppAttachmentHeaderComponent) -> Bool { + return true + } + + public final class View: UIView { + private let appIcon = ComponentView() + private let icon = ComponentView() + + private var appIconImage: UIImage? + private var appIconDisposable: Disposable? + + private var component: AlertWebAppAttachmentHeaderComponent? + private weak var state: EmptyComponentState? + + deinit { + self.appIconDisposable?.dispose() + } + + func update(component: AlertWebAppAttachmentHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + if self.component == nil { + var peerIcon: TelegramMediaFile? + if let icon = component.icons[.iOSStatic] { + peerIcon = icon + } else if let icon = component.icons[.default] { + peerIcon = icon + } + + if let peerIcon { + let _ = freeMediaFileInteractiveFetched(account: component.context.account, userLocation: .other, fileReference: .standalone(media: peerIcon)).start() + self.appIconDisposable = (svgIconImageFile(account: component.context.account, fileReference: .standalone(media: peerIcon)) + |> deliverOnMainQueue).start(next: { [weak self] transform in + if let self { + let availableSize = CGSize(width: 48.0, height: 48.0) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: availableSize, boundingSize: availableSize, intrinsicInsets: UIEdgeInsets()) + let drawingContext = transform(arguments) + self.appIconImage = drawingContext?.generateImage()?.withRenderingMode(.alwaysTemplate) + + self.state?.updated() + } + }) + } + } + + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let appIconSize = CGSize(width: 42.0, height: 42.0) + let _ = self.appIcon.update( + transition: .immediate, + component: AnyComponent( + Image(image: self.appIconImage, tintColor: environment.theme.actionSheet.controlAccentColor) + ), + environment: {}, + containerSize: appIconSize + ) + let iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent( + BundleIconComponent(name: "Chat/Attach Menu/BotPlus", tintColor: environment.theme.actionSheet.controlAccentColor) + ), + environment: {}, + containerSize: availableSize + ) + + let totalWidth: CGFloat = 42.0 + iconSize.width + + let appIconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - totalWidth) / 2.0) - 2.0, y: 3.0), size: appIconSize) + if let imageView = self.appIcon.view { + if imageView.superview == nil { + self.addSubview(imageView) + } + imageView.frame = appIconFrame + } + + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - totalWidth) / 2.0) + appIconSize.width, y: 0.0), size: iconSize) + if let imageView = self.icon.view { + if imageView.superview == nil { + self.addSubview(imageView) + } + imageView.frame = iconFrame + } + + return CGSize(width: availableSize.width, height: appIconSize.height + 17.0) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 4cee7d24..bc04472f 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -43,6 +43,7 @@ import GlassBarButtonComponent import BundleIconComponent import LottieComponent import CryptoKit +import AlertComponent private let durgerKingBotIds: [Int64] = [5104055776, 2200339955] @@ -685,7 +686,14 @@ public final class WebAppController: ViewController, AttachmentContainable { if let data = try? Data(contentsOf: url), let pass = try? PKPass(data: data) { let passLibrary = PKPassLibrary() if passLibrary.containsPass(pass) { - let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: self.presentationData.strings.WebBrowser_PassExistsError, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_OK, action: {})]) + let alertController = AlertScreen( + context: self.context, + title: nil, + text: self.presentationData.strings.WebBrowser_PassExistsError, + actions: [ + .init(title: self.presentationData.strings.Common_OK, type: .default) + ] + ) self.controller?.present(alertController, in: .window(.root)) } else if let controller = PKAddPassesViewController(pass: pass) { self.controller?.view.window?.rootViewController?.present(controller, animated: true) @@ -761,12 +769,19 @@ public final class WebAppController: ViewController, AttachmentContainable { func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { var completed = false - let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: message, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { - if !completed { - completed = true - completionHandler() - } - })]) + let alertController = AlertScreen( + context: self.context, + title: nil, + text: message, + actions: [ + .init(title: self.presentationData.strings.Common_OK, action: { + if !completed { + completed = true + completionHandler() + } + }) + ] + ) alertController.dismissed = { byOutsideTap in if byOutsideTap { if !completed { @@ -780,17 +795,25 @@ public final class WebAppController: ViewController, AttachmentContainable { func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { var completed = false - let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: message, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { - if !completed { - completed = true - completionHandler(false) - } - }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { - if !completed { - completed = true - completionHandler(true) - } - })]) + let alertController = AlertScreen( + context: self.context, + title: nil, + text: message, + actions: [ + .init(title: self.presentationData.strings.Common_Cancel, action: { + if !completed { + completed = true + completionHandler(false) + } + }), + .init(title: self.presentationData.strings.Common_OK, type: .default, action: { + if !completed { + completed = true + completionHandler(true) + } + }) + ] + ) alertController.dismissed = { byOutsideTap in if byOutsideTap { if !completed { @@ -804,24 +827,28 @@ public final class WebAppController: ViewController, AttachmentContainable { func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { var completed = false - let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: self.controller?.updatedPresentationData, text: prompt, value: defaultText, apply: { value in - if !completed { - completed = true - if let value = value { - completionHandler(value) - } else { - completionHandler(nil) + let promptController = promptController( + context: self.context, + updatedPresentationData: self.controller?.updatedPresentationData, + text: prompt, + value: defaultText, + apply: { value in + if !completed { + completed = true + if let value = value { + completionHandler(value) + } else { + completionHandler(nil) + } } - } - }) - promptController.dismissed = { byOutsideTap in - if byOutsideTap { + }, + dismissed: { if !completed { completed = true completionHandler(nil) } } - } + ) self.controller?.present(promptController, in: .window(.root)) } @@ -1385,7 +1412,7 @@ public final class WebAppController: ViewController, AttachmentContainable { let presentationData = self.presentationData let title = json["title"] as? String - var alertButtons: [TextAlertAction] = [] + var actions: [AlertScreen.Action] = [] for buttonJson in buttons.reversed() { if let button = buttonJson as? [String: Any], let id = button["id"] as? String, let type = button["type"] as? String { @@ -1395,27 +1422,27 @@ public final class WebAppController: ViewController, AttachmentContainable { let text = button["text"] as? String switch type { case "default": - if let text = text { - alertButtons.append(TextAlertAction(type: .genericAction, title: text, action: { + if let text { + actions.append(AlertScreen.Action(title: text, action: { buttonAction() })) } case "destructive": - if let text = text { - alertButtons.append(TextAlertAction(type: .destructiveAction, title: text, action: { + if let text { + actions.append(AlertScreen.Action(title: text, type: .destructive, action: { buttonAction() })) } case "ok": - alertButtons.append(TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + actions.append(AlertScreen.Action(title: presentationData.strings.Common_OK, type: .default, action: { buttonAction() })) case "cancel": - alertButtons.append(TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + actions.append(AlertScreen.Action(title: presentationData.strings.Common_Cancel, action: { buttonAction() })) case "close": - alertButtons.append(TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Close, action: { + actions.append(AlertScreen.Action(title: presentationData.strings.Common_Close, action: { buttonAction() })) default: @@ -1424,12 +1451,18 @@ public final class WebAppController: ViewController, AttachmentContainable { } } - var actionLayout: TextAlertContentActionLayout = .horizontal - if alertButtons.count > 2 { + var actionLayout: AlertScreen.ActionAligmnent = .default + if actions.count > 2 { actionLayout = .vertical - alertButtons = Array(alertButtons.reversed()) + actions = Array(actions.reversed()) } - let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: title, text: message, actions: alertButtons, actionLayout: actionLayout) + let alertController = AlertScreen( + context: self.context, + configuration: AlertScreen.Configuration(actionAlignment: actionLayout, dismissOnOutsideTap: true, allowInputInset: false), + title: title, + text: message, + actions: actions + ) alertController.dismissed = { byOutsideTap in if byOutsideTap { self.sendAlertButtonEvent(id: nil) @@ -2113,18 +2146,26 @@ public final class WebAppController: ViewController, AttachmentContainable { if result { sendEvent(true) } else { - let alertController = textAlertController(context: self.context, updatedPresentationData: controller.updatedPresentationData, title: self.presentationData.strings.WebApp_AllowWriteTitle, text: self.presentationData.strings.WebApp_AllowWriteConfirmation(controller.botName).string, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { - sendEvent(false) - }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { [weak self] in - guard let self else { - return - } - - let _ = (self.context.engine.messages.allowBotSendMessages(botId: controller.botId) - |> deliverOnMainQueue).start(completed: { - sendEvent(true) - }) - })], parseMarkdown: true) + let alertController = AlertScreen( + context: self.context, + title: self.presentationData.strings.WebApp_AllowWriteTitle, + text: self.presentationData.strings.WebApp_AllowWriteConfirmation(controller.botName).string, + actions: [ + .init(title: self.presentationData.strings.Common_Cancel, action: { + sendEvent(false) + }), + .init(title: self.presentationData.strings.Common_OK, type: .default, action: { [weak self] in + guard let self else { + return + } + + let _ = (self.context.engine.messages.allowBotSendMessages(botId: controller.botId) + |> deliverOnMainQueue).start(completed: { + sendEvent(true) + }) + }) + ] + ) alertController.dismissed = { byOutsideTap in if byOutsideTap { sendEvent(false) @@ -2166,48 +2207,56 @@ public final class WebAppController: ViewController, AttachmentContainable { text = self.presentationData.strings.WebApp_SharePhoneConfirmation(botName).string } - let alertController = textAlertController(context: self.context, updatedPresentationData: controller.updatedPresentationData, title: self.presentationData.strings.WebApp_SharePhoneTitle, text: text, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { - sendEvent(false) - }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { [weak self] in - guard let self, case let .user(user) = accountPeer, let phone = user.phone, !phone.isEmpty else { - return - } - - let sendMessageSignal = enqueueMessages(account: self.context.account, peerId: botId, messages: [ - .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaContact(firstName: user.firstName ?? "", lastName: user.lastName ?? "", phoneNumber: phone, peerId: user.id, vCardData: nil)), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - ]) - |> mapToSignal { messageIds in - if let maybeMessageId = messageIds.first, let messageId = maybeMessageId { - return context.account.pendingMessageManager.pendingMessageStatus(messageId) - |> mapToSignal { status, _ -> Signal in - if status != nil { - return .never() + let alertController = AlertScreen( + context: self.context, + title: self.presentationData.strings.WebApp_SharePhoneTitle, + text: text, + actions: [ + .init(title: self.presentationData.strings.Common_Cancel, action: { + sendEvent(false) + }), + .init(title: self.presentationData.strings.Common_OK, type: .default, action: { [weak self] in + guard let self, case let .user(user) = accountPeer, let phone = user.phone, !phone.isEmpty else { + return + } + + let sendMessageSignal = enqueueMessages(account: self.context.account, peerId: botId, messages: [ + .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaContact(firstName: user.firstName ?? "", lastName: user.lastName ?? "", phoneNumber: phone, peerId: user.id, vCardData: nil)), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + ]) + |> mapToSignal { messageIds in + if let maybeMessageId = messageIds.first, let messageId = maybeMessageId { + return context.account.pendingMessageManager.pendingMessageStatus(messageId) + |> mapToSignal { status, _ -> Signal in + if status != nil { + return .never() + } else { + return .single(true) + } + } + |> take(1) } else { - return .single(true) + return .complete() } } - |> take(1) - } else { - return .complete() - } - } - - let sendMessage = { - let _ = (sendMessageSignal - |> deliverOnMainQueue).start(completed: { - sendEvent(true) + + let sendMessage = { + let _ = (sendMessageSignal + |> deliverOnMainQueue).start(completed: { + sendEvent(true) + }) + } + + if requiresUnblock { + let _ = (context.engine.privacy.requestUpdatePeerIsBlocked(peerId: botId, isBlocked: false) + |> deliverOnMainQueue).start(completed: { + sendMessage() + }) + } else { + sendMessage() + } }) - } - - if requiresUnblock { - let _ = (context.engine.privacy.requestUpdatePeerIsBlocked(peerId: botId, isBlocked: false) - |> deliverOnMainQueue).start(completed: { - sendMessage() - }) - } else { - sendMessage() - } - })], parseMarkdown: true) + ] + ) alertController.dismissed = { byOutsideTap in if byOutsideTap { sendEvent(false) @@ -2328,14 +2377,21 @@ public final class WebAppController: ViewController, AttachmentContainable { alertText = self.presentationData.strings.WebApp_AlertBiometryAccessText(botPeer.compactDisplayTitle).string } } - controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: alertTitle, text: alertText, actions: [ - TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_No, action: { - updateAccessGranted(false) - }), - TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Yes, action: { - updateAccessGranted(true) - }) - ], parseMarkdown: false), in: .window(.root)) + + let alertController = AlertScreen( + context: self.context, + title: alertTitle, + text: alertText, + actions: [ + .init(title: self.presentationData.strings.Common_No, action: { + updateAccessGranted(false) + }), + .init(title: self.presentationData.strings.Common_Yes, type: .default, action: { + updateAccessGranted(true) + }) + ] + ) + controller.present(alertController, in: .window(.root)) }) } @@ -2774,17 +2830,23 @@ public final class WebAppController: ViewController, AttachmentContainable { } let text: String = self.presentationData.strings.WebApp_Download_Text(controller.botName, fileName, fileSizeString).string - let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: title, text: text, actions: [ - TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { [weak self] in - let data: JSON = [ - "status": "cancelled" - ] - self?.webView?.sendEvent(name: "file_download_requested", data: data.string) - }), - TextAlertAction(type: .defaultAction, title: self.presentationData.strings.WebApp_Download_Download, action: { [weak self] in - self?.startDownload(url: url, fileName: fileName, fileSize: fileSize, isMedia: isMedia) - }) - ], parseMarkdown: true) + + let alertController = AlertScreen( + context: self.context, + title: title, + text: text, + actions: [ + .init(title: self.presentationData.strings.Common_Cancel, action: { [weak self] in + let data: JSON = [ + "status": "cancelled" + ] + self?.webView?.sendEvent(name: "file_download_requested", data: data.string) + }), + .init(title: self.presentationData.strings.WebApp_Download_Download, type: .default, action: { [weak self] in + self?.startDownload(url: url, fileName: fileName, fileSize: fileSize, isMedia: isMedia) + }) + ] + ) alertController.dismissed = { [weak self] byOutsideTap in let data: JSON = [ "status": "cancelled" @@ -2962,7 +3024,7 @@ public final class WebAppController: ViewController, AttachmentContainable { accountPeer: accountPeer, botName: controller.botName, icons: iconStatusEmoji, - completion: { [weak self] result in + completion: { [weak self] result, byOutsideTap in guard let self, let controller = self.controller else { return } @@ -3017,17 +3079,13 @@ public final class WebAppController: ViewController, AttachmentContainable { self.webView?.sendEvent(name: "emoji_status_access_requested", data: data.string) } - let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: botId) { current in - return WebAppPermissionsState(location: current?.location, emojiStatus: WebAppPermissionsState.EmojiStatus(isRequested: true)) - }.startStandalone() + if !byOutsideTap { + let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: botId) { current in + return WebAppPermissionsState(location: current?.location, emojiStatus: WebAppPermissionsState.EmojiStatus(isRequested: true)) + }.startStandalone() + } } ) - alertController.dismissed = { [weak self] byOutsideTap in - let data: JSON = [ - "status": "cancelled" - ] - self?.webView?.sendEvent(name: "emoji_status_access_requested", data: data.string) - } controller.present(alertController, in: .window(.root)) }) } @@ -3510,7 +3568,7 @@ public final class WebAppController: ViewController, AttachmentContainable { private func updateNavigationButtons() { if case .attachMenu = self.source { - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let closeComponent: AnyComponentWithIdentity = AnyComponentWithIdentity( id: "close", component: AnyComponent(GlassBarButtonComponent( @@ -3521,7 +3579,7 @@ public final class WebAppController: ViewController, AttachmentContainable { component: AnyComponentWithIdentity(id: self.controllerNode.hasBackButton ? "back" : "close", component: AnyComponent( BundleIconComponent( name: self.controllerNode.hasBackButton ? "Navigation/Back" : "Navigation/Close", - tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor ) )), action: { [weak self] _ in @@ -3542,7 +3600,7 @@ public final class WebAppController: ViewController, AttachmentContainable { content: LottieComponent.AppBundleContent( name: "anim_morewide" ), - color: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor, + color: self.presentationData.theme.chat.inputPanel.panelControlColor, size: CGSize(width: 34.0, height: 34.0), playOnce: self.moreButtonPlayOnce ) @@ -3621,6 +3679,7 @@ public final class WebAppController: ViewController, AttachmentContainable { if let backgroundColor = self.controllerNode.headerColor, let textColor = self.controllerNode.headerPrimaryTextColor { navigationBarPresentationData = NavigationBarPresentationData( theme: NavigationBarTheme( + overallDarkAppearance: false, buttonColor: textColor, disabledButtonColor: textColor, primaryTextColor: textColor, @@ -3639,7 +3698,7 @@ public final class WebAppController: ViewController, AttachmentContainable { strings: NavigationBarStrings(back: "", close: "") ) } - self.navigationBar?.updatePresentationData(navigationBarPresentationData) + self.navigationBar?.updatePresentationData(navigationBarPresentationData, transition: .immediate) } @objc fileprivate func cancelPressed() { @@ -3857,13 +3916,22 @@ public final class WebAppController: ViewController, AttachmentContainable { private func removeAttachBot() { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - self.present(textAlertController(context: context, title: presentationData.strings.WebApp_RemoveConfirmationTitle, text: presentationData.strings.WebApp_RemoveAllConfirmationText(self.botName).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { [weak self] in - guard let self else { - return - } - let _ = self.context.engine.messages.removeBotFromAttachMenu(botId: self.botId).start() - self.dismiss() - })], parseMarkdown: true), in: .window(.root)) + let alertController = AlertScreen( + context: self.context, + title: presentationData.strings.WebApp_RemoveConfirmationTitle, + text: presentationData.strings.WebApp_RemoveAllConfirmationText(self.botName).string, + actions: [ + .init(title: presentationData.strings.Common_Cancel), + .init(title: presentationData.strings.Common_OK, type: .default, action: { [weak self] in + guard let self else { + return + } + let _ = self.context.engine.messages.removeBotFromAttachMenu(botId: self.botId).start() + self.dismiss() + }) + ] + ) + self.present(alertController, in: .window(.root)) } override public func loadDisplayNode() { diff --git a/submodules/WebUI/Sources/WebAppEmojiStatusAlertController.swift b/submodules/WebUI/Sources/WebAppEmojiStatusAlertController.swift index 316962fb..ea8dc4ed 100644 --- a/submodules/WebUI/Sources/WebAppEmojiStatusAlertController.swift +++ b/submodules/WebUI/Sources/WebAppEmojiStatusAlertController.swift @@ -4,6 +4,7 @@ import SwiftSignalKit import AsyncDisplayKit import Display import Postbox +import ComponentFlow import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -13,352 +14,236 @@ import AvatarNode import EmojiTextAttachmentView import TextFormat import Markdown - -private final class IconsNode: ASDisplayNode { - private let context: AccountContext - private var animationLayer: InlineStickerItemLayer? - - private var files: [TelegramMediaFile.Accessor] - private var currentIndex = 0 - private var switchingToNext = false - - private var timer: SwiftSignalKit.Timer? - - private var currentParams: (size: CGSize, theme: PresentationTheme)? - - init(context: AccountContext, files: [TelegramMediaFile.Accessor]) { - self.context = context - self.files = files - - super.init() - } - - deinit { - self.timer?.invalidate() - } - - func updateLayout(size: CGSize, theme: PresentationTheme) { - self.currentParams = (size, theme) - - if self.timer == nil { - self.timer = SwiftSignalKit.Timer(timeout: 2.5, repeat: true, completion: { [weak self] in - guard let self else { - return - } - self.switchingToNext = true - if let (size, theme) = self.currentParams { - self.updateLayout(size: size, theme: theme) - } - }, queue: Queue.mainQueue()) - self.timer?.start() - } - - let animationLayer: InlineStickerItemLayer - var disappearingAnimationLayer: InlineStickerItemLayer? - if let current = self.animationLayer, !self.switchingToNext { - animationLayer = current - } else { - if self.switchingToNext { - self.currentIndex = (self.currentIndex + 1) % self.files.count - disappearingAnimationLayer = self.animationLayer - self.switchingToNext = false - } - let file = self.files[self.currentIndex]._parse() - let emoji = ChatTextInputTextCustomEmojiAttribute( - interactivelySelectedFromPackId: nil, - fileId: file.fileId.id, - file: file - ) - animationLayer = InlineStickerItemLayer( - context: .account(self.context), - userLocation: .other, - attemptSynchronousLoad: false, - emoji: emoji, - file: file, - cache: self.context.animationCache, - renderer: self.context.animationRenderer, - unique: true, - placeholderColor: theme.list.mediaPlaceholderColor, - pointSize: CGSize(width: 20.0, height: 20.0), - loopCount: 1 - ) - animationLayer.isVisibleForAnimations = true - animationLayer.dynamicColor = theme.actionSheet.controlAccentColor - self.view.layer.addSublayer(animationLayer) - self.animationLayer = animationLayer - - animationLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - animationLayer.animatePosition(from: CGPoint(x: 0.0, y: 10.0), to: .zero, duration: 0.2, additive: true) - animationLayer.animateScale(from: 0.01, to: 1.0, duration: 0.2) - } - - animationLayer.frame = CGRect(origin: .zero, size: CGSize(width: 20.0, height: 20.0)) - - if let disappearingAnimationLayer { - disappearingAnimationLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in - disappearingAnimationLayer.removeFromSuperlayer() - }) - disappearingAnimationLayer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -10.0), duration: 0.2, removeOnCompletion: false, additive: true) - disappearingAnimationLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) - } - } -} - -private final class WebAppEmojiStatusAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - private let presentationTheme: PresentationTheme - private let botName: String - - private let textNode: ASTextNode - private let iconBackgroundNode: ASImageNode - private let iconAvatarNode: AvatarNode - private let iconNameNode: ASTextNode - private let iconAnimationNode: IconsNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init( - context: AccountContext, - theme: AlertControllerTheme, - ptheme: PresentationTheme, - strings: PresentationStrings, - accountPeer: EnginePeer, - botName: String, - icons: [TelegramMediaFile.Accessor], - actions: [TextAlertAction] - ) { - self.strings = strings - self.presentationTheme = ptheme - self.botName = botName - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.iconBackgroundNode = ASImageNode() - self.iconBackgroundNode.displaysAsynchronously = false - self.iconBackgroundNode.image = generateStretchableFilledCircleImage(radius: 16.0, color: theme.separatorColor) - - self.iconAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0)) - self.iconAvatarNode.setPeer(context: context, theme: ptheme, peer: accountPeer) - - self.iconNameNode = ASTextNode() - self.iconNameNode.attributedText = NSAttributedString(string: accountPeer.compactDisplayTitle, font: Font.medium(15.0), textColor: theme.primaryColor) - - self.iconAnimationNode = IconsNode(context: context, files: icons) - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.textNode) - self.addSubnode(self.iconBackgroundNode) - self.addSubnode(self.iconAvatarNode) - self.addSubnode(self.iconNameNode) - self.addSubnode(self.iconAnimationNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - let string = self.strings.WebApp_EmojiPermission_Text(self.botName, self.botName).string - let attributedText = parseMarkdownIntoAttributedString(string, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - linkAttribute: { url in - return ("URL", url) - } - ), textAlignment: .center) - self.textNode.attributedText = attributedText - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width , 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let iconSpacing: CGFloat = 6.0 - let iconSize = CGSize(width: 32.0, height: 32.0) - let nameSize = self.iconNameNode.measure(size) - let totalIconWidth = iconSize.width + iconSpacing + nameSize.width + 4.0 + iconSize.width - - let iconBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalIconWidth) / 2.0), y: origin.y), size: CGSize(width: totalIconWidth, height: iconSize.height)) - transition.updateFrame(node: self.iconBackgroundNode, frame: iconBackgroundFrame) - transition.updateFrame(node: self.iconAvatarNode, frame: CGRect(origin: iconBackgroundFrame.origin, size: iconSize).insetBy(dx: 1.0, dy: 1.0)) - transition.updateFrame(node: self.iconNameNode, frame: CGRect(origin: CGPoint(x: iconBackgroundFrame.minX + iconSize.width + iconSpacing, y: iconBackgroundFrame.minY + floorToScreenPixels((iconBackgroundFrame.height - nameSize.height) / 2.0)), size: nameSize)) - - self.iconAnimationNode.updateLayout(size: CGSize(width: 20.0, height: 20.0), theme: self.presentationTheme) - self.iconAnimationNode.frame = CGRect(origin: CGPoint(x: iconBackgroundFrame.maxX - iconSize.width - 3.0, y: iconBackgroundFrame.minY), size: iconSize).insetBy(dx: 6.0, dy: 6.0) - - origin.y += iconSize.height + 16.0 - - let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - var contentWidth = minActionsWidth - contentWidth = max(contentWidth, 234.0) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultWidth = contentWidth + insets.left + insets.right - let resultSize = CGSize(width: resultWidth, height: iconSize.height + textSize.height + actionsHeight + 16.0 + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} +import AlertComponent +import AvatarComponent +import MultilineTextComponent func webAppEmojiStatusAlertController( context: AccountContext, accountPeer: EnginePeer, botName: String, icons: [TelegramMediaFile.Accessor], - completion: @escaping (Bool) -> Void -) -> AlertController { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let theme = presentationData.theme - let strings = presentationData.strings - - var dismissImpl: ((Bool) -> Void)? - var contentNode: WebAppEmojiStatusAlertContentNode? - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: strings.WebApp_EmojiPermission_Decline, action: { - dismissImpl?(true) + completion: @escaping (Bool, Bool) -> Void +) -> ViewController { + let strings = context.sharedContext.currentPresentationData.with { $0 }.strings - completion(false) - }), TextAlertAction(type: .defaultAction, title: strings.WebApp_EmojiPermission_Allow, action: { - dismissImpl?(true) - - completion(true) - })] + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "status", + component: AnyComponent( + AlertEmojiStatusComponent(context: context, peer: accountPeer, files: icons) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.WebApp_EmojiPermission_Text(botName, botName).string)) + ) + )) - contentNode = WebAppEmojiStatusAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, accountPeer: accountPeer, botName: botName, icons: icons, actions: actions) - - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() + let alertController = AlertScreen( + context: context, + content: content, + actions: [ + .init(title: strings.WebApp_EmojiPermission_Decline, action: { + completion(false, false) + }), + .init(title: strings.WebApp_EmojiPermission_Allow, type: .default, action: { + completion(true, false) + }) + ] + ) + alertController.dismissed = { byOutsideTap in + if byOutsideTap { + completion(false, true) } } - return controller + return alertController +} + +private final class AlertEmojiStatusComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + let context: AccountContext + let peer: EnginePeer + let files: [TelegramMediaFile.Accessor] + + public init( + context: AccountContext, + peer: EnginePeer, + files: [TelegramMediaFile.Accessor] + ) { + self.context = context + self.peer = peer + self.files = files + } + + public static func ==(lhs: AlertEmojiStatusComponent, rhs: AlertEmojiStatusComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.peer != rhs.peer { + return false + } + if lhs.files != rhs.files { + return false + } + return true + } + + final class View: UIView { + private let background = ComponentView() + private let title = ComponentView() + private let avatar = ComponentView() + + private var animationLayer: InlineStickerItemLayer? + + private var currentIndex = 0 + private var switchingToNext = false + + private var timer: SwiftSignalKit.Timer? + + private var component: AlertEmojiStatusComponent? + private weak var state: EmptyComponentState? + + func update(component: AlertEmojiStatusComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.peer.compactDisplayTitle, + font: Font.medium(15.0), + textColor: environment.theme.actionSheet.primaryTextColor + )), + maximumNumberOfLines: 0 + )), + environment: {}, + containerSize: availableSize + ) + + let avatarSize = CGSize(width: 30.0, height: 30.0) + let iconSize = CGSize(width: 20.0, height: 20.0) + let avatarMargin: CGFloat = 1.0 + let avatarSpacing: CGFloat = 7.0 + let titleSpacing: CGFloat = 4.0 + let statusMargin: CGFloat = 12.0 + + let backgroundSize = CGSize(width: avatarMargin + avatarSize.width + avatarSpacing + titleSize.width + titleSpacing + iconSize.width + statusMargin, height: 32.0) + + let _ = self.background.update( + transition: transition, + component: AnyComponent(FilledRoundedRectangleComponent(color: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1), cornerRadius: .minEdge, smoothCorners: false)), + environment: {}, + containerSize: backgroundSize + ) + let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - backgroundSize.width) / 2.0), y: 0.0), size: backgroundSize) + if let backgroundView = self.background.view { + if backgroundView.superview == nil { + self.addSubview(backgroundView) + } + transition.setFrame(view: backgroundView, frame: backgroundFrame) + } + + let _ = self.avatar.update( + transition: transition, + component: AnyComponent(AvatarComponent( + context: component.context, + theme: environment.theme, + peer: component.peer + )), + environment: {}, + containerSize: avatarSize + ) + let avatarFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + avatarMargin, y: backgroundFrame.minY + avatarMargin), size: avatarSize) + if let avatarView = self.avatar.view { + if avatarView.superview == nil { + self.addSubview(avatarView) + } + transition.setFrame(view: avatarView, frame: avatarFrame) + } + + let titleFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + avatarMargin + avatarSize.width + avatarSpacing, y: backgroundFrame.minY + floorToScreenPixels((backgroundSize.height - titleSize.height) / 2.0)), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + if self.timer == nil { + self.timer = SwiftSignalKit.Timer(timeout: 2.5, repeat: true, completion: { [weak self] in + guard let self else { + return + } + self.switchingToNext = true + self.state?.updated() + }, queue: Queue.mainQueue()) + self.timer?.start() + } + + let animationLayer: InlineStickerItemLayer + var disappearingAnimationLayer: InlineStickerItemLayer? + if let current = self.animationLayer, !self.switchingToNext { + animationLayer = current + } else { + if self.switchingToNext { + self.currentIndex = (self.currentIndex + 1) % component.files.count + disappearingAnimationLayer = self.animationLayer + self.switchingToNext = false + } + let file = component.files[self.currentIndex]._parse() + let emoji = ChatTextInputTextCustomEmojiAttribute( + interactivelySelectedFromPackId: nil, + fileId: file.fileId.id, + file: file + ) + animationLayer = InlineStickerItemLayer( + context: .account(component.context), + userLocation: .other, + attemptSynchronousLoad: false, + emoji: emoji, + file: file, + cache: component.context.animationCache, + renderer: component.context.animationRenderer, + unique: true, + placeholderColor: environment.theme.list.mediaPlaceholderColor, + pointSize: iconSize, + loopCount: 1 + ) + animationLayer.isVisibleForAnimations = true + animationLayer.dynamicColor = environment.theme.actionSheet.controlAccentColor + self.layer.addSublayer(animationLayer) + self.animationLayer = animationLayer + + animationLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + animationLayer.animatePosition(from: CGPoint(x: 0.0, y: 10.0), to: .zero, duration: 0.2, additive: true) + animationLayer.animateScale(from: 0.01, to: 1.0, duration: 0.2) + } + + animationLayer.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - iconSize.width - statusMargin, y: backgroundFrame.minY + floorToScreenPixels((backgroundFrame.height - iconSize.height) / 2.0)), size: iconSize) + + if let disappearingAnimationLayer { + disappearingAnimationLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + disappearingAnimationLayer.removeFromSuperlayer() + }) + disappearingAnimationLayer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -10.0), duration: 0.2, removeOnCompletion: false, additive: true) + disappearingAnimationLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + } + + return CGSize(width: availableSize.width, height: backgroundSize.height + 12.0) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } } diff --git a/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift b/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift index ba371f96..2fa118db 100644 --- a/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift +++ b/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift @@ -10,365 +10,13 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext import AppBundle -import AvatarNode -import CheckNode -import Markdown import EmojiStatusComponent - -private let textFont = Font.regular(13.0) -private let boldTextFont = Font.semibold(13.0) - -private func formattedText(_ text: String, color: UIColor, linkColor: UIColor, textAlignment: NSTextAlignment = .natural) -> NSAttributedString { - return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: color), bold: MarkdownAttributeSet(font: boldTextFont, textColor: color), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { _ in return nil}), textAlignment: textAlignment) -} - -private final class WebAppLaunchConfirmationAlertContentNode: AlertContentNode { - private let context: AccountContext - private let presentationTheme: PresentationTheme - private let strings: PresentationStrings - private let peer: EnginePeer - private let title: String - private let text: String - private let showMore: Bool - - private let titleNode: ImmediateTextNode - private var titleCredibilityIconView: ComponentHostView? - private let textNode: ASTextNode - private let avatarNode: AvatarNode - - private let moreButton: HighlightableButtonNode - private let arrowNode: ASImageNode - - private let allowWriteCheckNode: InteractiveCheckNode - private let allowWriteLabelNode: ASTextNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - private let morePressed: () -> Void - private let termsPressed: () -> Void - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - var allowWriteAccess: Bool = true { - didSet { - self.allowWriteCheckNode.setSelected(self.allowWriteAccess, animated: true) - } - } - - init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer, title: String, text: String, showMore: Bool, requestWriteAccess: Bool, actions: [TextAlertAction], morePressed: @escaping () -> Void, termsPressed: @escaping () -> Void) { - self.context = context - self.strings = strings - self.presentationTheme = ptheme - self.peer = peer - self.title = title - self.text = text - self.showMore = showMore - self.morePressed = morePressed - self.termsPressed = termsPressed - - self.titleNode = ImmediateTextNode() - self.titleNode.displaysAsynchronously = false - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.textAlignment = .center - - self.textNode = ASTextNode() - self.textNode.displaysAsynchronously = false - self.textNode.maximumNumberOfLines = 0 - - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) - - self.moreButton = HighlightableButtonNode() - - self.arrowNode = ASImageNode() - self.arrowNode.displaysAsynchronously = false - self.arrowNode.displayWithoutProcessing = true - self.arrowNode.isHidden = !showMore - self.arrowNode.contentMode = .scaleAspectFit - - self.allowWriteCheckNode = InteractiveCheckNode(theme: CheckNodeTheme(backgroundColor: theme.accentColor, strokeColor: theme.contrastColor, borderColor: theme.controlBorderColor, overlayBorder: false, hasInset: false, hasShadow: false)) - self.allowWriteCheckNode.setSelected(true, animated: false) - self.allowWriteLabelNode = ASTextNode() - self.allowWriteLabelNode.maximumNumberOfLines = 4 - self.allowWriteLabelNode.isUserInteractionEnabled = true - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - self.addSubnode(self.avatarNode) - self.addSubnode(self.moreButton) - self.moreButton.addSubnode(self.arrowNode) - - if requestWriteAccess { - self.addSubnode(self.allowWriteCheckNode) - self.addSubnode(self.allowWriteLabelNode) - } - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.allowWriteCheckNode.valueChanged = { [weak self] value in - if let strongSelf = self { - strongSelf.allowWriteAccess = !strongSelf.allowWriteAccess - } - } - - self.updateTheme(theme) - - self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer) - - self.moreButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside) - } - - override func didLoad() { - super.didLoad() - - self.allowWriteLabelNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.allowWriteTap(_:)))) - - self.textNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.termsTap(_:)))) - } - - @objc private func allowWriteTap(_ gestureRecognizer: UITapGestureRecognizer) { - if self.allowWriteCheckNode.isUserInteractionEnabled { - self.allowWriteAccess = !self.allowWriteAccess - } - } - - @objc private func termsTap(_ gestureRecognizer: UITapGestureRecognizer) { - self.termsPressed() - } - - @objc private func moreButtonPressed() { - self.morePressed() - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = formattedText(self.text, color: theme.primaryColor, linkColor: theme.accentColor, textAlignment: .center) - - self.moreButton.setAttributedTitle(NSAttributedString(string: self.strings.WebApp_LaunchMoreInfo, font: Font.regular(13.0), textColor: theme.accentColor), for: .normal) - self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.accentColor) - - self.allowWriteLabelNode.attributedText = formattedText(strings.WebApp_AddToAttachmentAllowMessages(self.peer.compactDisplayTitle).string, color: theme.primaryColor, linkColor: theme.primaryColor) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let avatarSize = CGSize(width: 60.0, height: 60.0) - self.avatarNode.updateSize(size: avatarSize) - - let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0), y: origin.y), size: avatarSize) - transition.updateFrame(node: self.avatarNode, frame: avatarFrame) - - origin.y += avatarSize.height + 17.0 - - if let arrowImage = self.arrowNode.image { - let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) - transition.updateFrame(node: self.arrowNode, frame: arrowFrame) - } - - let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 32.0, height: size.height)) - var totalWidth = titleSize.width - - var statusContent: EmojiStatusComponent.Content? - if self.peer.isScam { - statusContent = .text(color: self.presentationTheme.list.itemDestructiveColor, string: self.strings.Message_ScamAccount.uppercased()) - } else if self.peer.isFake { - statusContent = .text(color: self.presentationTheme.list.itemDestructiveColor, string: self.strings.Message_FakeAccount.uppercased()) - } else if self.peer.isVerified { - statusContent = .verified(fillColor: self.presentationTheme.list.itemCheckColors.fillColor, foregroundColor: self.presentationTheme.list.itemCheckColors.foregroundColor, sizeType: .large) - } - - if let statusContent { - let titleCredibilityIconTransition: ComponentTransition = .immediate - - let titleCredibilityIconView: ComponentHostView - if let current = self.titleCredibilityIconView { - titleCredibilityIconView = current - } else { - titleCredibilityIconView = ComponentHostView() - self.titleCredibilityIconView = titleCredibilityIconView - self.view.addSubview(titleCredibilityIconView) - } - - let titleIconSize = titleCredibilityIconView.update( - transition: titleCredibilityIconTransition, - component: AnyComponent(EmojiStatusComponent( - context: self.context, - animationCache: self.context.animationCache, - animationRenderer: self.context.animationRenderer, - content: statusContent, - isVisibleForAnimations: true, - action: { - } - )), - environment: {}, - containerSize: CGSize(width: 20.0, height: 20.0) - ) - - totalWidth += titleIconSize.width + 2.0 - titleCredibilityIconTransition.setFrame(view: titleCredibilityIconView, frame: CGRect(origin: CGPoint(x:floorToScreenPixels((size.width - totalWidth) / 2.0) + titleSize.width + 2.0, y: origin.y), size: titleIconSize)) - } - - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 6.0 - - var entriesHeight: CGFloat = 0.0 - if self.showMore { - let moreButtonSize = self.moreButton.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.moreButton, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - moreButtonSize.width) / 2.0) - 5.0, y: origin.y), size: moreButtonSize)) - transition.updateFrame(node: self.arrowNode, frame: CGRect(origin: CGPoint(x: moreButtonSize.width + 3.0, y: 4.0), size: CGSize(width: 9.0, height: 9.0))) - origin.y += moreButtonSize.height + 22.0 - entriesHeight += 37.0 - } - - let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height - - if self.allowWriteLabelNode.supernode != nil { - origin.y += 16.0 - entriesHeight += 16.0 - - let checkSize = CGSize(width: 22.0, height: 22.0) - let condensedSize = CGSize(width: size.width - 76.0, height: size.height) - - let allowWriteSize = self.allowWriteLabelNode.measure(condensedSize) - transition.updateFrame(node: self.allowWriteLabelNode, frame: CGRect(origin: CGPoint(x: 46.0, y: origin.y), size: allowWriteSize)) - transition.updateFrame(node: self.allowWriteCheckNode, frame: CGRect(origin: CGPoint(x: 12.0, y: origin.y - 2.0), size: checkSize)) - origin.y += allowWriteSize.height - entriesHeight += allowWriteSize.height - } - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.vertical - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + entriesHeight + actionsHeight + 25.0 + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} +import AlertComponent +import AlertCheckComponent +import AvatarComponent +import MultilineTextComponent +import BundleIconComponent +import PlainButtonComponent public func webAppLaunchConfirmationController( context: AccountContext, @@ -378,50 +26,209 @@ public func webAppLaunchConfirmationController( completion: @escaping (Bool) -> Void, showMore: (() -> Void)?, openTerms: @escaping () -> Void -) -> AlertController { - let theme = defaultDarkColorPresentationTheme - let presentationData: PresentationData - if let updatedPresentationData { - presentationData = updatedPresentationData.initial - } else { - presentationData = context.sharedContext.currentPresentationData.with { $0 } - } +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings + + let checkState = AlertCheckComponent.ExternalState() - var dismissImpl: ((Bool) -> Void)? - var getContentNodeImpl: (() -> WebAppLaunchConfirmationAlertContentNode?)? - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_LaunchOpenApp, action: { - if requestWriteAccess, let allowWriteAccess = getContentNodeImpl?()?.allowWriteAccess { - completion(allowWriteAccess) - } else { - completion(false) - } - dismissImpl?(true) - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissImpl?(true) - })] - - let title = peer.compactDisplayTitle - let text = presentationData.strings.WebApp_LaunchTermsConfirmation - - let contentNode = WebAppLaunchConfirmationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peer: peer, title: title, text: text, showMore: showMore != nil, requestWriteAccess: requestWriteAccess, actions: actions, morePressed: { - dismissImpl?(true) - showMore?() - }, termsPressed: { - dismissImpl?(true) - openTerms() - }) - getContentNodeImpl = { [weak contentNode] in - return contentNode + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertWebAppHeaderComponent(context: context, peer: peer, showMore: showMore) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.WebApp_LaunchTermsConfirmation)) + ) + )) + if requestWriteAccess { + content.append(AnyComponentWithIdentity( + id: "check", + component: AnyComponent( + AlertCheckComponent(title: strings.WebApp_AddToAttachmentAllowMessages(peer.compactDisplayTitle).string, initialValue: false, externalState: checkState) + ) + )) } - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + let alertController = AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + content: content, + actions: [ + .init(title: strings.WebApp_LaunchOpenApp, type: .default, action: { + completion(requestWriteAccess && checkState.value) + }), + .init(title: strings.Common_Cancel) + ] + ) + return alertController +} + +private final class AlertWebAppHeaderComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + let context: AccountContext + let peer: EnginePeer + let showMore: (() -> Void)? + + public init( + context: AccountContext, + peer: EnginePeer, + showMore: (() -> Void)? + ) { + self.context = context + self.peer = peer + self.showMore = showMore + } + + public static func ==(lhs: AlertWebAppHeaderComponent, rhs: AlertWebAppHeaderComponent) -> Bool { + return true + } + + public final class View: UIView { + private let avatar = ComponentView() + private let title = ComponentView() + private let titleIcon = ComponentView() + private let showMore = ComponentView() + + private var component: AlertWebAppHeaderComponent? + private weak var state: EmptyComponentState? + + func update(component: AlertWebAppHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[AlertComponentEnvironment.self] + + var contentHeight: CGFloat = 0.0 + let avatarSize = self.avatar.update( + transition: .immediate, + component: AnyComponent( + AvatarComponent( + context: component.context, + theme: environment.theme, + peer: component.peer + ) + ), + environment: {}, + containerSize: CGSize(width: 60.0, height: 60.0) + ) + let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - avatarSize.width) / 2.0), y: contentHeight), size: avatarSize) + if let avatarView = self.avatar.view { + if avatarView.superview == nil { + self.addSubview(avatarView) + } + avatarView.frame = avatarFrame + } + contentHeight += avatarSize.height + contentHeight += 17.0 + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.peer.compactDisplayTitle, + font: Font.bold(17.0), + textColor: environment.theme.actionSheet.primaryTextColor + )), + horizontalAlignment: .natural, + maximumNumberOfLines: 0 + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - 32.0, height: availableSize.height) + ) + + var totalWidth = titleSize.width + + var statusContent: EmojiStatusComponent.Content? + if component.peer.isScam { + statusContent = .text(color: environment.theme.list.itemDestructiveColor, string: environment.strings.Message_ScamAccount.uppercased()) + } else if component.peer.isFake { + statusContent = .text(color: environment.theme.list.itemDestructiveColor, string: environment.strings.Message_FakeAccount.uppercased()) + } else if component.peer.isVerified { + statusContent = .verified(fillColor: environment.theme.list.itemCheckColors.fillColor, foregroundColor: environment.theme.list.itemCheckColors.foregroundColor, sizeType: .large) + } + if let statusContent { + let titleIconSize = self.titleIcon.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + content: statusContent, + isVisibleForAnimations: true, + action: { + } + )), + environment: {}, + containerSize: CGSize(width: 20.0, height: 20.0) + ) + totalWidth += titleIconSize.width + 2.0 + if let titleIconView = self.titleIcon.view { + if titleIconView.superview == nil { + self.addSubview(titleIconView) + } + transition.setFrame(view: titleIconView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - totalWidth) / 2.0) + titleSize.width + 2.0, y: contentHeight), size: titleIconSize)) + } + } + + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - totalWidth) / 2.0), y: contentHeight), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + titleView.frame = titleFrame + } + contentHeight += titleSize.height + + if let showMore = component.showMore { + contentHeight += 6.0 + + let showMoreSize = self.showMore.update( + transition: .immediate, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + HStack([ + AnyComponentWithIdentity(id: "label", component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.WebApp_LaunchMoreInfo, font: Font.regular(14.0), textColor: environment.theme.actionSheet.controlAccentColor))))), + AnyComponentWithIdentity(id: "arrow", component: AnyComponent(BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: environment.theme.actionSheet.controlAccentColor))) + ], spacing: 3.0) + ), + action: { + showMore() + }, + animateScale: false + ) + ), + environment: {}, + containerSize: availableSize + ) + let showMoreFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - showMoreSize.width) / 2.0), y: contentHeight), size: showMoreSize) + if let showMoreView = self.showMore.view { + if showMoreView.superview == nil { + self.addSubview(showMoreView) + } + showMoreView.frame = showMoreFrame + } + contentHeight += showMoreSize.height + } + contentHeight += 12.0 + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } } diff --git a/submodules/WebUI/Sources/WebAppLocationAlertController.swift b/submodules/WebUI/Sources/WebAppLocationAlertController.swift index 867841fc..1fe62d60 100644 --- a/submodules/WebUI/Sources/WebAppLocationAlertController.swift +++ b/submodules/WebUI/Sources/WebAppLocationAlertController.swift @@ -10,17 +10,18 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext import AppBundle -import AvatarNode -import Markdown -import CheckNode +import ComponentFlow +import AlertComponent +import AvatarComponent +import AlertTransferHeaderComponent -private func generateBoostIcon(theme: PresentationTheme) -> UIImage? { - let size = CGSize(width: 28.0, height: 28.0) +private func generateLocationIcon() -> UIImage? { + let size = CGSize(width: 24.0, height: 24.0) return generateImage(size, contextGenerator: { size, context in let bounds = CGRect(origin: .zero, size: size) context.clear(bounds) - context.addEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0)) + context.addEllipse(in: bounds) context.clip() var locations: [CGFloat] = [1.0, 0.0] @@ -32,261 +33,61 @@ private func generateBoostIcon(theme: PresentationTheme) -> UIImage? { context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions()) if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Location"), color: .white), let cgImage = image.cgImage { - context.draw(cgImage, in: bounds.insetBy(dx: 6.0, dy: 6.0)) + context.draw(cgImage, in: bounds.insetBy(dx: 4.0, dy: 4.0)) } context.resetClip() - - let lineWidth = 2.0 - UIScreenPixel - context.setLineWidth(lineWidth) - context.setStrokeColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor) - context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0 + UIScreenPixel, dy: lineWidth / 2.0 + UIScreenPixel)) }, opaque: false) } -private final class WebAppLocationAlertContentNode: AlertContentNode { - private let strings: PresentationStrings - private let text: String - - private let textNode: ASTextNode - private let avatarNode: AvatarNode - private let arrowNode: ASImageNode - private let secondAvatarNode: AvatarNode - private let iconNode: ASImageNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, accountPeer: EnginePeer, botPeer: EnginePeer, text: String, actions: [TextAlertAction]) { - self.strings = strings - self.text = text - - self.textNode = ASTextNode() - self.textNode.maximumNumberOfLines = 0 - - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) - - self.arrowNode = ASImageNode() - self.arrowNode.displaysAsynchronously = false - self.arrowNode.displayWithoutProcessing = true - - self.secondAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) - - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - self.iconNode.image = generateBoostIcon(theme: ptheme) - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.textNode) - self.addSubnode(self.avatarNode) - self.addSubnode(self.arrowNode) - self.addSubnode(self.secondAvatarNode) - self.addSubnode(self.iconNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.updateTheme(theme) - - self.avatarNode.setPeer(context: context, theme: ptheme, peer: accountPeer) - self.secondAvatarNode.setPeer(context: context, theme: ptheme, peer: botPeer) - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), - linkAttribute: { url in - return ("URL", url) - } - ), textAlignment: .center) - self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) - - let avatarSize = CGSize(width: 60.0, height: 60.0) - self.avatarNode.updateSize(size: avatarSize) - - let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize) - transition.updateFrame(node: self.avatarNode, frame: avatarFrame) - - if let arrowImage = self.arrowNode.image { - let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) - transition.updateFrame(node: self.arrowNode, frame: arrowFrame) - } - - let secondAvatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize) - transition.updateFrame(node: self.secondAvatarNode, frame: secondAvatarFrame) - - if let icon = self.iconNode.image { - let iconFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX + 4.0 - icon.size.width, y: avatarFrame.maxY + 4.0 - icon.size.height), size: icon.size) - transition.updateFrame(node: self.iconNode, frame: iconFrame) - } - - origin.y += avatarSize.height + 10.0 - - let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height + 10.0 - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.horizontal - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultSize = CGSize(width: contentWidth, height: avatarSize.height + textSize.height + actionsHeight + 16.0 + insets.top + insets.bottom) - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} - -func webAppLocationAlertController(context: AccountContext, accountPeer: EnginePeer, botPeer: EnginePeer, completion: @escaping (Bool) -> Void) -> AlertController { +func webAppLocationAlertController(context: AccountContext, accountPeer: EnginePeer, botPeer: EnginePeer, completion: @escaping (Bool) -> Void) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings - - let text = strings.WebApp_LocationPermission_Text(botPeer.compactDisplayTitle, botPeer.compactDisplayTitle).string - - var dismissImpl: ((Bool) -> Void)? - var contentNode: WebAppLocationAlertContentNode? - let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: strings.WebApp_LocationPermission_Decline, action: { - dismissImpl?(true) - completion(false) - }), TextAlertAction(type: .defaultAction, title: strings.WebApp_LocationPermission_Allow, action: { - dismissImpl?(true) - completion(true) - })] - contentNode = WebAppLocationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, accountPeer: accountPeer, botPeer: botPeer, text: text, actions: actions) + let locationIcon = generateLocationIcon() - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertTransferHeaderComponent( + fromComponent: AnyComponentWithIdentity(id: "user", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: accountPeer, + icon: AnyComponent(Image(image: locationIcon, contentMode: .center)) + ) + )), + toComponent: AnyComponentWithIdentity(id: "bot", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: botPeer + ) + )), + type: .transfer + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.WebApp_LocationPermission_Text(botPeer.compactDisplayTitle, botPeer.compactDisplayTitle).string)) + ) + )) + + let alertController = AlertScreen( + context: context, + content: content, + actions: [ + .init(title: strings.WebApp_LocationPermission_Decline, action: { + completion(false) + }), + .init(title: strings.WebApp_LocationPermission_Allow, type: .default, action: { + completion(true) + }) + ] + ) + return alertController } diff --git a/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift b/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift index 9f6133b1..68f30500 100644 --- a/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift +++ b/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift @@ -160,7 +160,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode { self.containerNode = ASDisplayNode() self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) - super.init(layerBacked: false, dynamicBounce: false) + super.init(layerBacked: false) self.clipsToBounds = true self.isUserInteractionEnabled = false diff --git a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift index a9090e04..3d65b1f3 100644 --- a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift +++ b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift @@ -23,6 +23,7 @@ import ListItemComponentAdaptor import TelegramStringFormatting import UndoUI import ChatMessagePaymentAlertController +import GlassBarButtonComponent private final class SheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -55,7 +56,7 @@ private final class SheetContent: CombinedComponent { } static var body: Body { - let closeButton = Child(Button.self) + let closeButton = Child(GlassBarButtonComponent.self) let title = Child(Text.self) let amountSection = Child(ListSectionComponent.self) let button = Child(ButtonComponent.self) @@ -71,22 +72,31 @@ private final class SheetContent: CombinedComponent { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let sideInset: CGFloat = 16.0 - var contentSize = CGSize(width: context.availableSize.width, height: 18.0) + var contentSize = CGSize(width: context.availableSize.width, height: 36.0) let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0 let closeButton = closeButton.update( - component: Button( - content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)), - action: { + component: GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { _ in component.dismiss() } ), - availableSize: CGSize(width: 120.0, height: 30.0), + availableSize: CGSize(width: 40.0, height: 40.0), transition: .immediate ) context.add(closeButton - .position(CGPoint(x: closeButton.size.width / 2.0 + sideInset, y: 28.0)) + .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0)) ) let title = title.update( @@ -95,7 +105,7 @@ private final class SheetContent: CombinedComponent { transition: .immediate ) context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height)) ) contentSize.height += title.size.height contentSize.height += 40.0 @@ -246,13 +256,14 @@ private final class SheetContent: CombinedComponent { let buttonString: String = environment.strings.WebApp_ShareMessage_Share let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let button = button.update( component: ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: theme.list.itemCheckColors.fillColor, foreground: theme.list.itemCheckColors.foregroundColor, pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 ), content: AnyComponentWithIdentity( id: AnyHashable(0), @@ -266,7 +277,7 @@ private final class SheetContent: CombinedComponent { } } ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50), + availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), transition: .immediate ) context.add(button @@ -275,10 +286,8 @@ private final class SheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) ) contentSize.height += button.size.height - contentSize.height += 15.0 + contentSize.height += buttonInsets.bottom - contentSize.height += max(environment.inputHeight, environment.safeInsets.bottom) - return contentSize } } @@ -328,6 +337,7 @@ private final class WebAppMessagePreviewSheetComponent: CombinedComponent { let controller = environment.controller + let theme = environment.theme.withModalBlocksBackground() let sheet = sheet.update( component: SheetComponent( content: AnyComponent(SheetContent( @@ -344,7 +354,8 @@ private final class WebAppMessagePreviewSheetComponent: CombinedComponent { }) } )), - backgroundColor: .color(environment.theme.list.blocksBackgroundColor), + style: .glass, + backgroundColor: .color(theme.list.blocksBackgroundColor), followContentSizeChanges: false, clipsContent: true, isScrollEnabled: false, diff --git a/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift b/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift index bde5fba5..55cd331d 100644 --- a/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift +++ b/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift @@ -116,7 +116,7 @@ private final class SheetContent: CombinedComponent { component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", - tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + tintColor: theme.chat.inputPanel.panelControlColor ) )), action: { _ in @@ -198,6 +198,7 @@ private final class SheetContent: CombinedComponent { let controller = environment.controller() as? WebAppSetEmojiStatusScreen + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let button = button.update( component: ButtonComponent( background: ButtonComponent.Background( @@ -217,7 +218,7 @@ private final class SheetContent: CombinedComponent { controller?.dismissAnimated() } ), - availableSize: CGSize(width: context.availableSize.width - 30.0 * 2.0, height: 52.0), + availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), transition: .immediate ) context.add(button @@ -226,8 +227,7 @@ private final class SheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) ) contentSize.height += button.size.height - - contentSize.height += 48.0 + contentSize.height += buttonInsets.bottom return contentSize } diff --git a/submodules/WebUI/Sources/WebAppTermsAlertController.swift b/submodules/WebUI/Sources/WebAppTermsAlertController.swift index 88f3a717..ba305d3b 100644 --- a/submodules/WebUI/Sources/WebAppTermsAlertController.swift +++ b/submodules/WebUI/Sources/WebAppTermsAlertController.swift @@ -9,345 +9,9 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext import AppBundle -import AvatarNode -import CheckNode -import Markdown -import TextFormat - -private let textFont = Font.regular(13.0) -private let boldTextFont = Font.semibold(13.0) - -private func formattedText(_ text: String, fontSize: CGFloat, color: UIColor, linkColor: UIColor, textAlignment: NSTextAlignment = .natural) -> NSAttributedString { - return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(fontSize), textColor: color), bold: MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: color), link: MarkdownAttributeSet(font: Font.regular(fontSize), textColor: linkColor), linkAttribute: { _ in return (TelegramTextAttributes.URL, "") }), textAlignment: textAlignment) -} - -private final class WebAppTermsAlertContentNode: AlertContentNode, ASGestureRecognizerDelegate { - private let strings: PresentationStrings - private let title: String - private let text: String - private let additionalText: String? - - private let titleNode: ImmediateTextNode - private let textNode: ImmediateTextNode - private let additionalTextNode: ImmediateTextNode - - private let acceptTermsCheckNode: InteractiveCheckNode - private let acceptTermsLabelNode: ImmediateTextNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private var validLayout: CGSize? - - override var dismissOnOutsideTap: Bool { - return self.isUserInteractionEnabled - } - - var acceptedTerms: Bool = false { - didSet { - self.acceptTermsCheckNode.setSelected(self.acceptedTerms, animated: true) - if let firstAction = self.actionNodes.first { - firstAction.actionEnabled = self.acceptedTerms - } - } - } - - var openTerms: () -> Void = {} - - init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, title: String, text: String, additionalText: String?, actions: [TextAlertAction]) { - self.strings = strings - self.title = title - self.text = text - self.additionalText = additionalText - - self.titleNode = ImmediateTextNode() - self.titleNode.displaysAsynchronously = false - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.textAlignment = .center - - self.textNode = ImmediateTextNode() - self.textNode.maximumNumberOfLines = 0 - self.textNode.displaysAsynchronously = false - self.textNode.lineSpacing = 0.1 - self.textNode.textAlignment = .center - - self.additionalTextNode = ImmediateTextNode() - self.additionalTextNode.maximumNumberOfLines = 0 - self.additionalTextNode.displaysAsynchronously = false - self.additionalTextNode.lineSpacing = 0.1 - self.additionalTextNode.textAlignment = .center - - self.acceptTermsCheckNode = InteractiveCheckNode(theme: CheckNodeTheme(backgroundColor: theme.accentColor, strokeColor: theme.contrastColor, borderColor: theme.controlBorderColor, overlayBorder: false, hasInset: false, hasShadow: false)) - self.acceptTermsLabelNode = ImmediateTextNode() - self.acceptTermsLabelNode.maximumNumberOfLines = 4 - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - - self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(theme: theme, action: action) - } - - var actionVerticalSeparators: [ASDisplayNode] = [] - if actions.count > 1 { - for _ in 0 ..< actions.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - self.addSubnode(self.additionalTextNode) - - self.addSubnode(self.acceptTermsCheckNode) - self.addSubnode(self.acceptTermsLabelNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.acceptTermsCheckNode.valueChanged = { [weak self] value in - if let strongSelf = self { - strongSelf.acceptedTerms = !strongSelf.acceptedTerms - } - } - - self.acceptTermsLabelNode.highlightAttributeAction = { attributes in - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { - return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) - } else { - return nil - } - } - self.acceptTermsLabelNode.tapAttributeAction = { [weak self] attributes, _ in - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { - self?.openTerms() - } - } - - self.updateTheme(theme) - } - - override func didLoad() { - super.didLoad() - - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.acceptTap(_:))) - tapGesture.delegate = self.wrappedGestureRecognizerDelegate - self.view.addGestureRecognizer(tapGesture) - - if let firstAction = self.actionNodes.first { - firstAction.actionEnabled = false - } - } - - override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - let location = gestureRecognizer.location(in: self.acceptTermsLabelNode.view) - if self.acceptTermsLabelNode.bounds.contains(location) { - return true - } - return false - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if !self.bounds.contains(point) { - return nil - } - - if let (_, attributes) = self.acceptTermsLabelNode.attributesAtPoint(self.view.convert(point, to: self.acceptTermsLabelNode.view)) { - if attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] == nil { - return self.view - } - } - - return super.hitTest(point, with: event) - } - - @objc private func acceptTap(_ gestureRecognizer: UITapGestureRecognizer) { - let location = gestureRecognizer.location(in: self.acceptTermsLabelNode.view) - if self.acceptTermsCheckNode.isUserInteractionEnabled { - if let attributes = self.acceptTermsLabelNode.attributesAtPoint(location)?.1 { - if attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] != nil { - return - } - } - self.acceptedTerms = !self.acceptedTerms - } - } - - override func updateTheme(_ theme: AlertControllerTheme) { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - self.textNode.attributedText = formattedText(self.text, fontSize: 13.0, color: theme.primaryColor, linkColor: theme.accentColor, textAlignment: .center) - if let additionalText = self.additionalText { - self.additionalTextNode.attributedText = formattedText(additionalText, fontSize: 13.0, color: theme.primaryColor, linkColor: theme.accentColor, textAlignment: .center) - } else { - self.additionalTextNode.attributedText = nil - } - - let attributedAgreeText = parseMarkdownIntoAttributedString( - self.strings.WebApp_DisclaimerAgree, - attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: textFont, textColor: theme.primaryColor), - bold: MarkdownAttributeSet(font: boldTextFont, textColor: theme.primaryColor), - link: MarkdownAttributeSet(font: textFont, textColor: theme.accentColor), - linkAttribute: { contents in - return (TelegramTextAttributes.URL, contents) - } - ) - ) - - self.acceptTermsLabelNode.attributedText = attributedAgreeText - self.acceptTermsLabelNode.linkHighlightColor = theme.accentColor.withAlphaComponent(0.2) - - self.actionNodesSeparator.backgroundColor = theme.separatorColor - for actionNode in self.actionNodes { - actionNode.updateTheme(theme) - } - for separatorNode in self.actionVerticalSeparators { - separatorNode.backgroundColor = theme.separatorColor - } - - if let size = self.validLayout { - _ = self.updateLayout(size: size, transition: .immediate) - } - } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - var size = size - size.width = min(size.width, 270.0) - - self.validLayout = size - - var origin: CGPoint = CGPoint(x: 0.0, y: 17.0) - - let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 32.0, height: size.height)) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) - origin.y += titleSize.height + 4.0 - - var entriesHeight: CGFloat = 0.0 - - let textSize = self.textNode.updateLayout(CGSize(width: size.width - 48.0, height: size.height)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) - origin.y += textSize.height - - if self.acceptTermsLabelNode.supernode != nil { - origin.y += 21.0 - entriesHeight += 21.0 - - let checkSize = CGSize(width: 22.0, height: 22.0) - let condensedSize = CGSize(width: size.width - 76.0, height: size.height) - - let spacing: CGFloat = 12.0 - let acceptTermsSize = self.acceptTermsLabelNode.updateLayout(condensedSize) - let acceptTermsTotalWidth = checkSize.width + spacing + acceptTermsSize.width - let acceptTermsOriginX = floorToScreenPixels((size.width - acceptTermsTotalWidth) / 2.0) - - transition.updateFrame(node: self.acceptTermsCheckNode, frame: CGRect(origin: CGPoint(x: acceptTermsOriginX, y: origin.y - 3.0), size: checkSize)) - transition.updateFrame(node: self.acceptTermsLabelNode, frame: CGRect(origin: CGPoint(x: acceptTermsOriginX + checkSize.width + spacing, y: origin.y), size: acceptTermsSize)) - origin.y += acceptTermsSize.height - entriesHeight += acceptTermsSize.height - origin.y += 21.0 - } - - let additionalTextSize = self.additionalTextNode.updateLayout(CGSize(width: size.width - 48.0, height: size.height)) - transition.updateFrame(node: self.additionalTextNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - additionalTextSize.width) / 2.0), y: origin.y), size: additionalTextSize)) - origin.y += additionalTextSize.height - if additionalTextSize.height > 0.0 { - entriesHeight += 20.0 - } - - let actionButtonHeight: CGFloat = 44.0 - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - - var effectiveActionLayout = TextAlertContentActionLayout.vertical - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) - if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { - effectiveActionLayout = .vertical - } - switch effectiveActionLayout { - case .horizontal: - minActionsWidth += actionTitleSize.width + actionTitleInsets - case .vertical: - minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) - } - } - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let contentWidth = max(size.width, minActionsWidth) - - var actionsHeight: CGFloat = 0.0 - switch effectiveActionLayout { - case .horizontal: - actionsHeight = actionButtonHeight - case .vertical: - actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) - } - - let resultSize = CGSize(width: contentWidth, height: titleSize.height + textSize.height + additionalTextSize.height + entriesHeight + actionsHeight + 3.0 + insets.top + insets.bottom) - - transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch effectiveActionLayout { - case .horizontal: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - case .vertical: - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) - } - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - switch effectiveActionLayout { - case .horizontal: - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - case .vertical: - currentActionWidth = resultSize.width - } - - let actionNodeFrame: CGRect - switch effectiveActionLayout { - case .horizontal: - actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += currentActionWidth - case .vertical: - actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) - actionOffset += actionButtonHeight - } - - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - return resultSize - } -} +import ComponentFlow +import AlertComponent +import AlertCheckComponent public func webAppTermsAlertController( context: AccountContext, @@ -355,46 +19,51 @@ public func webAppTermsAlertController( bot: AttachMenuBot, completion: @escaping (Bool) -> Void, dismissed: @escaping () -> Void = {} -) -> AlertController { - let theme = defaultDarkColorPresentationTheme - let presentationData: PresentationData - if let updatedPresentationData { - presentationData = updatedPresentationData.initial - } else { - presentationData = context.sharedContext.currentPresentationData.with { $0 } - } +) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings + + let checkState = AlertCheckComponent.ExternalState() - var dismissImpl: ((Bool) -> Void)? - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_DisclaimerContinue, action: { - completion(true) - dismissImpl?(true) - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - dismissed() - dismissImpl?(true) - })] + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.WebApp_DisclaimerTitle) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.WebApp_DisclaimerText)) + ) + )) + content.append(AnyComponentWithIdentity( + id: "check", + component: AnyComponent( + AlertCheckComponent(title: strings.WebApp_DisclaimerAgree, initialValue: false, externalState: checkState, linkAction: { + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: strings.WebApp_Disclaimer_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) + }) + ) + )) - let title = presentationData.strings.WebApp_DisclaimerTitle - let text = presentationData.strings.WebApp_DisclaimerText - let additionalText: String? = nil + var effectiveUpdatedPresentationData: (PresentationData, Signal) + if let updatedPresentationData { + effectiveUpdatedPresentationData = updatedPresentationData + } else { + effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) + } - let contentNode = WebAppTermsAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, additionalText: additionalText, actions: actions) - contentNode.openTerms = { - context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.WebApp_Disclaimer_URL, forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: { - }) - } - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) - controller.dismissed = { outside in - if outside { - dismissed() - } - } - dismissImpl = { [weak controller] animated in - if animated { - controller?.dismissAnimated() - } else { - controller?.dismiss() - } - } - return controller + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + content: content, + actions: [ + .init(title: strings.WebApp_DisclaimerContinue, type: .default, action: { + completion(checkState.value) + }, isEnabled: checkState.valueSignal), + .init(title: strings.Common_Cancel) + ], + updatedPresentationData: effectiveUpdatedPresentationData + ) + return alertController } diff --git a/submodules/ffmpeg/BUILD b/submodules/ffmpeg/BUILD index ee50f168..2c1758de 100644 --- a/submodules/ffmpeg/BUILD +++ b/submodules/ffmpeg/BUILD @@ -314,6 +314,11 @@ objc_library( "libiconv", "z", ], + sdk_frameworks = [ + "AudioToolbox", + "CoreAudio", + "VideoToolbox" + ], deps = [ ":ffmpeg_lib", "//third-party/libvpx:vpx", diff --git a/third-party/libyuv/BUILD b/third-party/libyuv/BUILD index 402dc978..14e2255c 100644 --- a/third-party/libyuv/BUILD +++ b/third-party/libyuv/BUILD @@ -25,6 +25,7 @@ arch_specific_cflags = select({ "@build_bazel_rules_apple//apple:ios_arm64": common_flags + arm64_specific_flags, "//build-system:ios_sim_arm64": common_flags + arm64_specific_flags, "@build_bazel_rules_apple//apple:ios_x86_64": common_flags + x86_64_specific_flags, + "//conditions:default": common_flags, }) cc_library( diff --git a/third-party/openh264/BUILD b/third-party/openh264/BUILD index 845e670c..34e4f075 100644 --- a/third-party/openh264/BUILD +++ b/third-party/openh264/BUILD @@ -57,18 +57,21 @@ arch_specific_sources = select({ "@build_bazel_rules_apple//apple:ios_arm64": arm64_specific_sources, "//build-system:ios_sim_arm64": arm64_specific_sources, "@build_bazel_rules_apple//apple:ios_x86_64": [], + "//conditions:default": [], }) arch_specific_copts = select({ "@build_bazel_rules_apple//apple:ios_arm64": arm64_specific_copts, "//build-system:ios_sim_arm64": arm64_specific_copts, "@build_bazel_rules_apple//apple:ios_x86_64": [], + "//conditions:default": [], }) arch_specific_textual_hdrs = select({ "@build_bazel_rules_apple//apple:ios_arm64": arm64_specific_textual_hdrs, "//build-system:ios_sim_arm64": arm64_specific_textual_hdrs, "@build_bazel_rules_apple//apple:ios_x86_64": [], + "//conditions:default": [], }) all_sources = arch_specific_sources + [ diff --git a/third-party/recaptcha/BUILD b/third-party/recaptcha/BUILD index bb626687..8f828772 100644 --- a/third-party/recaptcha/BUILD +++ b/third-party/recaptcha/BUILD @@ -6,8 +6,8 @@ load( ) apple_static_xcframework_import( - name = "RecaptchaEnterprise", - xcframework_imports = glob(["RecaptchaEnterprise.xcframework/**"]), + name = "RecaptchaEnterpriseSDK", + xcframework_imports = glob(["RecaptchaEnterpriseSDK.xcframework/**"]), features = [ ], visibility = [ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/Info.plist b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/Info.plist new file mode 100755 index 00000000..ac58f76e --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/Info.plist @@ -0,0 +1,40 @@ + + + + + AvailableLibraries + + + LibraryIdentifier + ios-arm64 + LibraryPath + RecaptchaEnterpriseSDK.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + RecaptchaEnterpriseSDK.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeDirectory b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeDirectory new file mode 100644 index 00000000..0bb6997e Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeDirectory differ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeRequirements b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeRequirements new file mode 100644 index 00000000..52af9dc1 Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeRequirements differ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeRequirements-1 b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeRequirements-1 new file mode 100644 index 00000000..914b68dc Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeRequirements-1 differ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeResources b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeResources new file mode 100644 index 00000000..527530ce --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeResources @@ -0,0 +1,338 @@ + + + + + files + + ios-arm64/RecaptchaEnterpriseSDK.framework/Headers/RecaptchaEnterpriseSDK.h + + xWRr/E/wYy7Sp0yB6Gp1DD/dLrU= + + ios-arm64/RecaptchaEnterpriseSDK.framework/Info.plist + + HFvkvcH2FGyO+kbXbemKG0YstR4= + + ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftdoc + + jUzZ70ucRZDB9eI+3lsf8228LVE= + + ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftinterface + + d25y94i+fAFY0AJf1eu+rMi5XqI= + + ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/module.modulemap + + ox0fYYZal5uL7KVNXMSAdPPA+Rk= + + ios-arm64/RecaptchaEnterpriseSDK.framework/PrivacyInfo.xcprivacy + + d34/WdITFALLLJjkSHGiQSA6Z3Y= + + ios-arm64/RecaptchaEnterpriseSDK.framework/RecaptchaEnterpriseSDK + + QO1vgAPOySSuI66Twl5ak8ovskE= + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Headers/RecaptchaEnterpriseSDK.h + + xWRr/E/wYy7Sp0yB6Gp1DD/dLrU= + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Info.plist + + XPpp9kxxu6L0eMmszTmEEo6s1m8= + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftdoc + + brFI5kERGCy+UjGz2+Bnvt2l0AU= + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftinterface + + N24znSq5aAdvb3FvPFG2JQXqtZ4= + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/x86_64.swiftdoc + + HcFRMc8FqqbOXybSV9KwUlhbBL0= + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/x86_64.swiftinterface + + C6izcS0172Keg5BbaKrBQZFcmLk= + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/module.modulemap + + ox0fYYZal5uL7KVNXMSAdPPA+Rk= + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/PrivacyInfo.xcprivacy + + d34/WdITFALLLJjkSHGiQSA6Z3Y= + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/RecaptchaEnterpriseSDK + + IiGksJB+DWq7iLUrzsImEIQgw34= + + + files2 + + ios-arm64/RecaptchaEnterpriseSDK.framework/Headers/RecaptchaEnterpriseSDK.h + + hash + + xWRr/E/wYy7Sp0yB6Gp1DD/dLrU= + + hash2 + + DN7TwwReF8YggeH+U0TMRMzA+hAtRkT8GSp4WdIfERw= + + + ios-arm64/RecaptchaEnterpriseSDK.framework/Info.plist + + hash + + HFvkvcH2FGyO+kbXbemKG0YstR4= + + hash2 + + HG9mguXmwUgwDI3kYNoKB1kNJsrXCrZsQz1PHuolEQU= + + + ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftdoc + + hash + + jUzZ70ucRZDB9eI+3lsf8228LVE= + + hash2 + + 4odzfGT3faCpJG3NWt3hfRz6hJae8UU+aCp0ewG9gSQ= + + + ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftinterface + + hash + + d25y94i+fAFY0AJf1eu+rMi5XqI= + + hash2 + + 57iLgSycgpTwN+RwjZ5Ae8RLMoAokRLkIvxOBQNoyLw= + + + ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/module.modulemap + + hash + + ox0fYYZal5uL7KVNXMSAdPPA+Rk= + + hash2 + + fTtRtwQr7CIyNnBuKrVcw3vj1GVThIJhGK83oIOaOx0= + + + ios-arm64/RecaptchaEnterpriseSDK.framework/PrivacyInfo.xcprivacy + + hash + + d34/WdITFALLLJjkSHGiQSA6Z3Y= + + hash2 + + 9ogbL0FljSMHAXmM5vR4v1xZZKB2oE8aTODyTk0qUj8= + + + ios-arm64/RecaptchaEnterpriseSDK.framework/RecaptchaEnterpriseSDK + + hash + + QO1vgAPOySSuI66Twl5ak8ovskE= + + hash2 + + Wj/ZAiLqd7I0t4P9sqkBGyBdqRw5F1Uk8WCqLAHkqHc= + + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Headers/RecaptchaEnterpriseSDK.h + + hash + + xWRr/E/wYy7Sp0yB6Gp1DD/dLrU= + + hash2 + + DN7TwwReF8YggeH+U0TMRMzA+hAtRkT8GSp4WdIfERw= + + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Info.plist + + hash + + XPpp9kxxu6L0eMmszTmEEo6s1m8= + + hash2 + + jNmz2Ms0G4kzG3nA4uuN32s0hI0xZegXtFs+RdawsAw= + + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftdoc + + hash + + brFI5kERGCy+UjGz2+Bnvt2l0AU= + + hash2 + + MQ/hBDf541HqUnmhOfBwcIB06j7VZz1VRLhZReMjeuo= + + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftinterface + + hash + + N24znSq5aAdvb3FvPFG2JQXqtZ4= + + hash2 + + 349kl6FePmc+ifxSuhyz6bybqAvQ+M4S5vu5kRh18vM= + + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/x86_64.swiftdoc + + hash + + HcFRMc8FqqbOXybSV9KwUlhbBL0= + + hash2 + + xDrbEvkrmidMdLeIbDEjuQcR9+XSoVKLLlBgXHb5PFU= + + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/x86_64.swiftinterface + + hash + + C6izcS0172Keg5BbaKrBQZFcmLk= + + hash2 + + nEvIMb3VJ14kpMhYGWZ66qkdddhaYtOtXAjAhtcyKFA= + + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/module.modulemap + + hash + + ox0fYYZal5uL7KVNXMSAdPPA+Rk= + + hash2 + + fTtRtwQr7CIyNnBuKrVcw3vj1GVThIJhGK83oIOaOx0= + + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/PrivacyInfo.xcprivacy + + hash + + d34/WdITFALLLJjkSHGiQSA6Z3Y= + + hash2 + + 9ogbL0FljSMHAXmM5vR4v1xZZKB2oE8aTODyTk0qUj8= + + + ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/RecaptchaEnterpriseSDK + + hash + + IiGksJB+DWq7iLUrzsImEIQgw34= + + hash2 + + 4JmnqfaRg73MZZaYZNeHYUUZyixtalbnGDoH1CU9vME= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeSignature b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeSignature new file mode 100644 index 00000000..216e65c1 Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/_CodeSignature/CodeSignature differ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Headers/RecaptchaEnterpriseSDK.h b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Headers/RecaptchaEnterpriseSDK.h new file mode 100755 index 00000000..faa7f5ff --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Headers/RecaptchaEnterpriseSDK.h @@ -0,0 +1,519 @@ +// Generated by Apple Swift version 6.1.2 (swiftlang-6.1.2.1.2 clang-1700.0.13.5) +#ifndef RECAPTCHAENTERPRISESDK_SWIFT_H +#define RECAPTCHAENTERPRISESDK_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module" +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#pragma clang diagnostic pop +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef unsigned char char8_t; +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if 1 /* #if __has_feature(objc_modules) */ +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +// Rewritten: @import Foundation; +// From module Foundation +#import +// Rewritten: @import ObjectiveC; +// From module ObjectiveC +// From module ObjectiveC.NSObjCRuntime +#import +// From module ObjectiveC.NSObject +#import +// From module ObjectiveC.message +#import +// From module ObjectiveC.objc +#import +// From module ObjectiveC.objc_api +#import +// From module ObjectiveC.objc_auto +#import +// From module ObjectiveC.objc_exception +#import +// From module ObjectiveC.objc_sync +#import +// From module ObjectiveC.runtime +#import +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="RecaptchaEnterpriseSDK",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) + +@class NSString; +@class RecaptchaClient; +@class NSError; +/// Interface to interact with reCAPTCHA. +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK9Recaptcha") +@interface Recaptcha : NSObject +/// Builds a new reCAPTCHA Client for the given Site Key. +/// The SDK accepts one Site Key. Passing a different Site Key will throw an exception. +/// \param siteKey reCAPTCHA Site Key for the app. +/// +/// \param completion Callback function to return the RecaptchaClient or an error. +/// ++ (void)fetchClientWithSiteKey:(NSString * _Nonnull)siteKey completion:(void (^ _Nonnull)(RecaptchaClient * _Nullable, NSError * _Nullable))completion; +/// Builds a new reCAPTCHA Client for the given Site Key and timeout. +/// The SDK accepts one Site Key. Passing a different Site Key will +/// throw an exception. +/// At least a 10000 milliseconds timeout is suggested to allow for slow +/// networking, though in some cases longer timeouts may be necessary. The +/// minimum allowable value is 5000 milliseconds. +/// \param siteKey reCAPTCHA Site Key for the app. +/// +/// \param timeout Timeout for getClient in milliseconds. +/// +/// \param completion Callback function to return the RecaptchaClient or an error. +/// ++ (void)getClientWithSiteKey:(NSString * _Nonnull)siteKey withTimeout:(double)timeout completion:(void (^ _Nonnull)(RecaptchaClient * _Nullable, NSError * _Nullable))completion SWIFT_DEPRECATED_MSG("Use the new api `fetchClient(withSiteKey:completion:)` instead."); +/// Builds a new reCAPTCHA Client for the given Site Key and timeout. +/// The SDK accepts one Site Key. Passing a different Site Key will +/// throw an exception. +/// This function will timeout after 10 seconds. +/// \param siteKey reCAPTCHA Site Key for the app. +/// +/// \param completion Callback function to return the RecaptchaClient or an error. +/// ++ (void)getClientWithSiteKey:(NSString * _Nonnull)siteKey completion:(void (^ _Nonnull)(RecaptchaClient * _Nullable, NSError * _Nullable))completion SWIFT_DEPRECATED_MSG("Use the new api `fetchClient(withSiteKey:completion:)` instead."); +/// Builds a new reCAPTCHA Client for the given SiteKey. +/// This function will timeout after 10 seconds. +/// \param siteKey reCAPTCHA Site Key for the app. +/// +/// \param completionHandler Callback function to return the RecaptchaClient or an error. +/// ++ (void)getClientWithSiteKey:(NSString * _Nonnull)siteKey completionHandler:(void (^ _Nonnull)(RecaptchaClient * _Nullable, NSError * _Nullable))completionHandler SWIFT_DEPRECATED_MSG("Use the new api `fetchClient(withSiteKey:completion:)` instead."); +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + +enum RecaptchaActionType : NSInteger; +/// Action intended to be protected by reCAPTCHA. This object should be passed +/// to RecaptchaClient.execute. +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK15RecaptchaAction") +@interface RecaptchaAction : NSObject +/// Creates an object with a predefined reCAPTCHA action. +/// \param action The type of the action. +/// +/// +/// returns: +/// A RecaptchaAction object with the given action type. +- (nonnull instancetype)initWithAction:(enum RecaptchaActionType)action OBJC_DESIGNATED_INITIALIZER SWIFT_DEPRECATED_MSG("Please use customAction with the regular RecaptchaAction.custom() function"); +/// Indicates that the protected action is a Login lwPz0qSe. +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) RecaptchaAction * _Nonnull login;) ++ (RecaptchaAction * _Nonnull)login SWIFT_WARN_UNUSED_RESULT; +/// Indicates that the protected action is a Signup lwPz0qSe. +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) RecaptchaAction * _Nonnull signup;) ++ (RecaptchaAction * _Nonnull)signup SWIFT_WARN_UNUSED_RESULT; +/// Creates a custom action from a String. ++ (RecaptchaAction * _Nonnull)custom:(NSString * _Nonnull)action SWIFT_WARN_UNUSED_RESULT; +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + +/// Action type intended to be protected by reCAPTCHA. +typedef SWIFT_ENUM(NSInteger, RecaptchaActionType, open) { +/// Indicates that the protected action is a Login lwPz0qSe. + RecaptchaActionTypeLogin = 0, +/// Indicates that the protected action is a Signup lwPz0qSe. + RecaptchaActionTypeSignup = 1, +/// When a custom action is specified, reCAPTCHA uses this value automatically. + RecaptchaActionTypeOther = 2, +}; + +@class RecaptchaToken; +@class RecaptchaError; +/// Interface to interact with reCAPTCHA. +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK15RecaptchaClient") +@interface RecaptchaClient : NSObject +/// Executes reCAPTCHA on a user action. +/// It is suggested the usage of 10 seconds for the timeout. The minimum value +/// 5 seconds. +/// \param action The user action to protect. +/// +/// \param timeout Timeout for execute in milliseconds. +/// +/// \param completion Callback function to return the execute response. +/// +- (void)executeWithAction:(RecaptchaAction * _Nonnull)action withTimeout:(double)timeout completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion; +/// Executes reCAPTCHA on a user action. +/// This function throws a timeout exception after 10 seconds. +/// \param action The user action to protect. +/// +/// \param completion Callback function to return the execute response. +/// +- (void)executeWithAction:(RecaptchaAction * _Nonnull)action completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion; +/// Executes reCAPTCHA on a user action. +/// This function throws a timeout exception after 10 seconds. +/// \param action The user action to protect. +/// +/// \param completion Callback function to return the execute response. +/// +- (void)execute:(RecaptchaAction * _Nonnull)action completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion; +/// Executes reCAPTCHA on a user action. +/// This function throws a timeout exception after 10 seconds. +/// \param action The user action to protect. +/// +/// \param completionHandler Callback function to return the execute response. +/// +- (void)execute:(RecaptchaAction * _Nonnull)action completionHandler:(void (^ _Nonnull)(RecaptchaToken * _Nullable, RecaptchaError * _Nullable))completionHandler SWIFT_DEPRECATED_MSG("Use `execute(withAction:completion:)` instead."); +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK17RecaptchaConstant") +@interface RecaptchaConstant : NSObject +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull clientVersion;) ++ (NSString * _Nonnull)clientVersion SWIFT_WARN_UNUSED_RESULT; +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly) double defaultTimeoutExecute;) ++ (double)defaultTimeoutExecute SWIFT_WARN_UNUSED_RESULT; +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly) double defaultTimeoutInit;) ++ (double)defaultTimeoutInit SWIFT_WARN_UNUSED_RESULT; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +enum RecaptchaErrorCode : NSInteger; +@class NSCoder; +/// Error class for reCAPTCHA Events. +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK14RecaptchaError") +@interface RecaptchaError : NSError +/// Code relative to the error that was thrown. It maps to RecaptchaErrorCode. +@property (nonatomic, readonly) enum RecaptchaErrorCode errorCode; +/// Human readable error message. +@property (nonatomic, readonly, copy) NSString * _Nonnull errorMessage; +/// Required by NSError but should not be used. +- (nonnull instancetype)initWithCoder:(NSCoder * _Nonnull)coder SWIFT_UNAVAILABLE; +- (nonnull instancetype)initWithDomain:(NSString * _Nonnull)domain code:(NSInteger)code userInfo:(NSDictionary * _Nullable)dict SWIFT_UNAVAILABLE; +@end + +/// List of errors that can be returned from the SDK. +/// IMPORTANT: This list is add-only. Never change any existing value, since this class is +/// publicly visible and customers rely on these values to do error checking. +typedef SWIFT_ENUM(NSInteger, RecaptchaErrorCode, open) { +/// Unknown error occurred during the lwPz0qSe. + RecaptchaErrorCodeErrorCodeUnknown = 0, +/// reCAPTCHA cannot connect to Google servers, please make sure the app has network access. + RecaptchaErrorCodeErrorNetworkError = 1, +/// The site key used to call reCAPTCHA is invalid. + RecaptchaErrorCodeErrorInvalidSiteKey = 2, +/// Cannot create a reCAPTCHA interface because the key used cannot be used on iOS. +/// Please register new site key with the key type set to โ€œiOS Appโ€ via +/// Create Key. + RecaptchaErrorCodeErroInvalidKeyType = 3, +/// Cannot create a reCAPTCHA interface because the site key used doesnโ€™t support the calling +/// package. + RecaptchaErrorCodeErrorInvalidPackageName = 4, +/// reCAPTCHA cannot accept the action used, see custom +/// action guidelines. + RecaptchaErrorCodeErrorInvalidAction = 5, +/// reCAPTCHA cannot accept timeout provided, see +/// timeout guidelines. + RecaptchaErrorCodeErrorInvalidTimeout = 6, +/// No network was found in the device. + RecaptchaErrorCodeErrorNoNetwork = 7, +/// reCAPTCHA has faced an internal error, please try again in a bit. + RecaptchaErrorCodeErrorCodeInternalError = 100, +}; + +/// Swift implementation for RecaptchaTokenSwift that holds the response of a successful +/// execute call. +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK14RecaptchaToken") SWIFT_DEPRECATED_MSG("Newer implementations return the Token as a string.") +@interface RecaptchaToken : NSObject +/// The Token to be used for verification. +@property (nonatomic, readonly, copy) NSString * _Nonnull recaptchaToken; +- (nonnull instancetype)init:(NSString * _Nonnull)mobiletecqlyzr OBJC_DESIGNATED_INITIALIZER SWIFT_DEPRECATED_MSG("Newer implementations return the Token as a string."); +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Info.plist b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Info.plist new file mode 100755 index 00000000..c049078e Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Info.plist differ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftdoc b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftdoc new file mode 100755 index 00000000..d3d178d5 Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftdoc differ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftinterface b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftinterface new file mode 100755 index 00000000..46c8ede3 --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftinterface @@ -0,0 +1,107 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 6.1.2 (swiftlang-6.1.2.1.2 clang-1700.0.13.5) +// swift-module-flags: -target arm64-apple-ios15.0 -enable-objc-interop -enable-library-evolution -swift-version 6 -enforce-exclusivity=checked -O -enable-experimental-feature AccessLevelOnImport -enable-bare-slash-regex -module-name RecaptchaEnterpriseSDK -package-name googlemac/iPhone/recaptcha/enterprise +// swift-module-flags-ignorable: -no-verify-emitted-module-interface -interface-compiler-version 6.1.2 +import CryptoKit +import DeviceCheck +import Foundation +import Network +import Security +import Swift +import UIKit +import WebKit +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims +@objc @_inheritsConvenienceInitializers @objcMembers public class RecaptchaConstant : ObjectiveC.NSObject { + @objc public static let clientVersion: Swift.String + @objc public static let defaultTimeoutExecute: Swift.Double + @objc public static let defaultTimeoutInit: Swift.Double + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc public class Recaptcha : ObjectiveC.NSObject { + @objc public static func fetchClient(withSiteKey siteKey: Swift.String, completion: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + public static func fetchClient(withSiteKey siteKey: Swift.String) async throws -> RecaptchaEnterpriseSDK.RecaptchaClient + @available(*, deprecated, message: "Use the new api `fetchClient(withSiteKey:completion:)` instead.") + @objc public static func getClient(withSiteKey siteKey: Swift.String, withTimeout timeout: Swift.Double, completion: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + @available(*, deprecated, message: "Use the new api `fetchClient(withSiteKey:completion:)` instead.") + @objc public static func getClient(withSiteKey siteKey: Swift.String, completion: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + @available(*, deprecated, message: "Use the new api `fetchClient(withSiteKey:completion:)` instead.") + @objc public static func getClient(siteKey: Swift.String, completionHandler: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + @objc deinit +} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +@objc public enum RecaptchaActionType : Swift.Int { + case login + case signup + case other + public init?(rawValue: Swift.Int) + @available(*, deprecated, message: "Use RecaptchaAction enums instead.") + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@_hasMissingDesignatedInitializers @objc final public class RecaptchaAction : ObjectiveC.NSObject, Swift.Sendable { + convenience public init(customAction: Swift.String) + @available(*, deprecated, message: "Please use customAction with the regular RecaptchaAction.custom() function") + @objc public init(action: RecaptchaEnterpriseSDK.RecaptchaActionType) + @objc public static let login: RecaptchaEnterpriseSDK.RecaptchaAction + @objc public static let signup: RecaptchaEnterpriseSDK.RecaptchaAction + @objc public static func custom(_ action: Swift.String) -> RecaptchaEnterpriseSDK.RecaptchaAction + @objc deinit +} +@_hasMissingDesignatedInitializers @objc final public class RecaptchaClient : ObjectiveC.NSObject, Swift.Sendable { + @objc final public func execute(withAction action: RecaptchaEnterpriseSDK.RecaptchaAction, withTimeout timeout: Swift.Double, completion: @escaping @Sendable (Swift.String?, Foundation.NSError?) -> Swift.Void) + final public func execute(withAction action: RecaptchaEnterpriseSDK.RecaptchaAction, withTimeout timeout: Swift.Double = RecaptchaConstant.defaultTimeoutExecute) async throws -> Swift.String + @objc final public func execute(withAction action: RecaptchaEnterpriseSDK.RecaptchaAction, completion: @escaping @Sendable (Swift.String?, Foundation.NSError?) -> Swift.Void) + @objc final public func execute(_ action: RecaptchaEnterpriseSDK.RecaptchaAction, completion: @escaping @Sendable (Swift.String?, Foundation.NSError?) -> Swift.Void) + @available(*, deprecated, message: "Use `execute(withAction:completion:)` instead.") + @objc final public func execute(_ action: RecaptchaEnterpriseSDK.RecaptchaAction, completionHandler: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaToken?, RecaptchaEnterpriseSDK.RecaptchaError?) -> Swift.Void) + @objc deinit +} +@objc public enum RecaptchaErrorCode : Swift.Int { + case errorCodeUnknown = 0 + case errorNetworkError = 1 + case errorInvalidSiteKey = 2 + case erroInvalidKeyType = 3 + case errorInvalidPackageName = 4 + case errorInvalidAction = 5 + case errorInvalidTimeout = 6 + case errorNoNetwork = 7 + case errorCodeInternalError = 100 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@_hasMissingDesignatedInitializers @objc public class RecaptchaError : Foundation.NSError, @unchecked Swift.Sendable { + @objc public var errorCode: RecaptchaEnterpriseSDK.RecaptchaErrorCode { + @objc get + } + @objc public var errorMessage: Swift.String { + @objc get + } + public init(errorCode: RecaptchaEnterpriseSDK.RecaptchaErrorCode, errorMessage: Swift.String) + @objc deinit +} +@available(*, deprecated, message: "Newer implementations return the Token as a string.") +@objc public class RecaptchaToken : ObjectiveC.NSObject { + @objc final public let recaptchaToken: Swift.String + @available(*, deprecated, message: "Newer implementations return the Token as a string.") + @objc public init(_ mobiletecqlyzr: Swift.String) + @objc deinit +} +extension Foundation.UserDefaults : @unchecked Swift.Sendable { +} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +extension RecaptchaEnterpriseSDK.RecaptchaActionType : Swift.Equatable {} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +extension RecaptchaEnterpriseSDK.RecaptchaActionType : Swift.Hashable {} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +extension RecaptchaEnterpriseSDK.RecaptchaActionType : Swift.RawRepresentable {} +extension RecaptchaEnterpriseSDK.RecaptchaErrorCode : Swift.Equatable {} +extension RecaptchaEnterpriseSDK.RecaptchaErrorCode : Swift.Hashable {} +extension RecaptchaEnterpriseSDK.RecaptchaErrorCode : Swift.RawRepresentable {} diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/module.modulemap b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/module.modulemap new file mode 100755 index 00000000..8b67ccf9 --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module RecaptchaEnterpriseSDK { + header "RecaptchaEnterpriseSDK.h" + + requires objc +} diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/PrivacyInfo.xcprivacy b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/PrivacyInfo.xcprivacy new file mode 100755 index 00000000..74847dde --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/PrivacyInfo.xcprivacy @@ -0,0 +1,56 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePerformanceData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + \ No newline at end of file diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/RecaptchaEnterpriseSDK b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/RecaptchaEnterpriseSDK new file mode 100755 index 00000000..e15f64e1 Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64/RecaptchaEnterpriseSDK.framework/RecaptchaEnterpriseSDK differ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Headers/RecaptchaEnterpriseSDK.h b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Headers/RecaptchaEnterpriseSDK.h new file mode 100755 index 00000000..faa7f5ff --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Headers/RecaptchaEnterpriseSDK.h @@ -0,0 +1,519 @@ +// Generated by Apple Swift version 6.1.2 (swiftlang-6.1.2.1.2 clang-1700.0.13.5) +#ifndef RECAPTCHAENTERPRISESDK_SWIFT_H +#define RECAPTCHAENTERPRISESDK_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module" +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#pragma clang diagnostic pop +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef unsigned char char8_t; +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if 1 /* #if __has_feature(objc_modules) */ +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +// Rewritten: @import Foundation; +// From module Foundation +#import +// Rewritten: @import ObjectiveC; +// From module ObjectiveC +// From module ObjectiveC.NSObjCRuntime +#import +// From module ObjectiveC.NSObject +#import +// From module ObjectiveC.message +#import +// From module ObjectiveC.objc +#import +// From module ObjectiveC.objc_api +#import +// From module ObjectiveC.objc_auto +#import +// From module ObjectiveC.objc_exception +#import +// From module ObjectiveC.objc_sync +#import +// From module ObjectiveC.runtime +#import +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="RecaptchaEnterpriseSDK",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) + +@class NSString; +@class RecaptchaClient; +@class NSError; +/// Interface to interact with reCAPTCHA. +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK9Recaptcha") +@interface Recaptcha : NSObject +/// Builds a new reCAPTCHA Client for the given Site Key. +/// The SDK accepts one Site Key. Passing a different Site Key will throw an exception. +/// \param siteKey reCAPTCHA Site Key for the app. +/// +/// \param completion Callback function to return the RecaptchaClient or an error. +/// ++ (void)fetchClientWithSiteKey:(NSString * _Nonnull)siteKey completion:(void (^ _Nonnull)(RecaptchaClient * _Nullable, NSError * _Nullable))completion; +/// Builds a new reCAPTCHA Client for the given Site Key and timeout. +/// The SDK accepts one Site Key. Passing a different Site Key will +/// throw an exception. +/// At least a 10000 milliseconds timeout is suggested to allow for slow +/// networking, though in some cases longer timeouts may be necessary. The +/// minimum allowable value is 5000 milliseconds. +/// \param siteKey reCAPTCHA Site Key for the app. +/// +/// \param timeout Timeout for getClient in milliseconds. +/// +/// \param completion Callback function to return the RecaptchaClient or an error. +/// ++ (void)getClientWithSiteKey:(NSString * _Nonnull)siteKey withTimeout:(double)timeout completion:(void (^ _Nonnull)(RecaptchaClient * _Nullable, NSError * _Nullable))completion SWIFT_DEPRECATED_MSG("Use the new api `fetchClient(withSiteKey:completion:)` instead."); +/// Builds a new reCAPTCHA Client for the given Site Key and timeout. +/// The SDK accepts one Site Key. Passing a different Site Key will +/// throw an exception. +/// This function will timeout after 10 seconds. +/// \param siteKey reCAPTCHA Site Key for the app. +/// +/// \param completion Callback function to return the RecaptchaClient or an error. +/// ++ (void)getClientWithSiteKey:(NSString * _Nonnull)siteKey completion:(void (^ _Nonnull)(RecaptchaClient * _Nullable, NSError * _Nullable))completion SWIFT_DEPRECATED_MSG("Use the new api `fetchClient(withSiteKey:completion:)` instead."); +/// Builds a new reCAPTCHA Client for the given SiteKey. +/// This function will timeout after 10 seconds. +/// \param siteKey reCAPTCHA Site Key for the app. +/// +/// \param completionHandler Callback function to return the RecaptchaClient or an error. +/// ++ (void)getClientWithSiteKey:(NSString * _Nonnull)siteKey completionHandler:(void (^ _Nonnull)(RecaptchaClient * _Nullable, NSError * _Nullable))completionHandler SWIFT_DEPRECATED_MSG("Use the new api `fetchClient(withSiteKey:completion:)` instead."); +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + +enum RecaptchaActionType : NSInteger; +/// Action intended to be protected by reCAPTCHA. This object should be passed +/// to RecaptchaClient.execute. +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK15RecaptchaAction") +@interface RecaptchaAction : NSObject +/// Creates an object with a predefined reCAPTCHA action. +/// \param action The type of the action. +/// +/// +/// returns: +/// A RecaptchaAction object with the given action type. +- (nonnull instancetype)initWithAction:(enum RecaptchaActionType)action OBJC_DESIGNATED_INITIALIZER SWIFT_DEPRECATED_MSG("Please use customAction with the regular RecaptchaAction.custom() function"); +/// Indicates that the protected action is a Login lwPz0qSe. +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) RecaptchaAction * _Nonnull login;) ++ (RecaptchaAction * _Nonnull)login SWIFT_WARN_UNUSED_RESULT; +/// Indicates that the protected action is a Signup lwPz0qSe. +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) RecaptchaAction * _Nonnull signup;) ++ (RecaptchaAction * _Nonnull)signup SWIFT_WARN_UNUSED_RESULT; +/// Creates a custom action from a String. ++ (RecaptchaAction * _Nonnull)custom:(NSString * _Nonnull)action SWIFT_WARN_UNUSED_RESULT; +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + +/// Action type intended to be protected by reCAPTCHA. +typedef SWIFT_ENUM(NSInteger, RecaptchaActionType, open) { +/// Indicates that the protected action is a Login lwPz0qSe. + RecaptchaActionTypeLogin = 0, +/// Indicates that the protected action is a Signup lwPz0qSe. + RecaptchaActionTypeSignup = 1, +/// When a custom action is specified, reCAPTCHA uses this value automatically. + RecaptchaActionTypeOther = 2, +}; + +@class RecaptchaToken; +@class RecaptchaError; +/// Interface to interact with reCAPTCHA. +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK15RecaptchaClient") +@interface RecaptchaClient : NSObject +/// Executes reCAPTCHA on a user action. +/// It is suggested the usage of 10 seconds for the timeout. The minimum value +/// 5 seconds. +/// \param action The user action to protect. +/// +/// \param timeout Timeout for execute in milliseconds. +/// +/// \param completion Callback function to return the execute response. +/// +- (void)executeWithAction:(RecaptchaAction * _Nonnull)action withTimeout:(double)timeout completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion; +/// Executes reCAPTCHA on a user action. +/// This function throws a timeout exception after 10 seconds. +/// \param action The user action to protect. +/// +/// \param completion Callback function to return the execute response. +/// +- (void)executeWithAction:(RecaptchaAction * _Nonnull)action completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion; +/// Executes reCAPTCHA on a user action. +/// This function throws a timeout exception after 10 seconds. +/// \param action The user action to protect. +/// +/// \param completion Callback function to return the execute response. +/// +- (void)execute:(RecaptchaAction * _Nonnull)action completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion; +/// Executes reCAPTCHA on a user action. +/// This function throws a timeout exception after 10 seconds. +/// \param action The user action to protect. +/// +/// \param completionHandler Callback function to return the execute response. +/// +- (void)execute:(RecaptchaAction * _Nonnull)action completionHandler:(void (^ _Nonnull)(RecaptchaToken * _Nullable, RecaptchaError * _Nullable))completionHandler SWIFT_DEPRECATED_MSG("Use `execute(withAction:completion:)` instead."); +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK17RecaptchaConstant") +@interface RecaptchaConstant : NSObject +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull clientVersion;) ++ (NSString * _Nonnull)clientVersion SWIFT_WARN_UNUSED_RESULT; +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly) double defaultTimeoutExecute;) ++ (double)defaultTimeoutExecute SWIFT_WARN_UNUSED_RESULT; +SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly) double defaultTimeoutInit;) ++ (double)defaultTimeoutInit SWIFT_WARN_UNUSED_RESULT; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +@end + +enum RecaptchaErrorCode : NSInteger; +@class NSCoder; +/// Error class for reCAPTCHA Events. +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK14RecaptchaError") +@interface RecaptchaError : NSError +/// Code relative to the error that was thrown. It maps to RecaptchaErrorCode. +@property (nonatomic, readonly) enum RecaptchaErrorCode errorCode; +/// Human readable error message. +@property (nonatomic, readonly, copy) NSString * _Nonnull errorMessage; +/// Required by NSError but should not be used. +- (nonnull instancetype)initWithCoder:(NSCoder * _Nonnull)coder SWIFT_UNAVAILABLE; +- (nonnull instancetype)initWithDomain:(NSString * _Nonnull)domain code:(NSInteger)code userInfo:(NSDictionary * _Nullable)dict SWIFT_UNAVAILABLE; +@end + +/// List of errors that can be returned from the SDK. +/// IMPORTANT: This list is add-only. Never change any existing value, since this class is +/// publicly visible and customers rely on these values to do error checking. +typedef SWIFT_ENUM(NSInteger, RecaptchaErrorCode, open) { +/// Unknown error occurred during the lwPz0qSe. + RecaptchaErrorCodeErrorCodeUnknown = 0, +/// reCAPTCHA cannot connect to Google servers, please make sure the app has network access. + RecaptchaErrorCodeErrorNetworkError = 1, +/// The site key used to call reCAPTCHA is invalid. + RecaptchaErrorCodeErrorInvalidSiteKey = 2, +/// Cannot create a reCAPTCHA interface because the key used cannot be used on iOS. +/// Please register new site key with the key type set to โ€œiOS Appโ€ via +/// Create Key. + RecaptchaErrorCodeErroInvalidKeyType = 3, +/// Cannot create a reCAPTCHA interface because the site key used doesnโ€™t support the calling +/// package. + RecaptchaErrorCodeErrorInvalidPackageName = 4, +/// reCAPTCHA cannot accept the action used, see custom +/// action guidelines. + RecaptchaErrorCodeErrorInvalidAction = 5, +/// reCAPTCHA cannot accept timeout provided, see +/// timeout guidelines. + RecaptchaErrorCodeErrorInvalidTimeout = 6, +/// No network was found in the device. + RecaptchaErrorCodeErrorNoNetwork = 7, +/// reCAPTCHA has faced an internal error, please try again in a bit. + RecaptchaErrorCodeErrorCodeInternalError = 100, +}; + +/// Swift implementation for RecaptchaTokenSwift that holds the response of a successful +/// execute call. +SWIFT_CLASS("_TtC22RecaptchaEnterpriseSDK14RecaptchaToken") SWIFT_DEPRECATED_MSG("Newer implementations return the Token as a string.") +@interface RecaptchaToken : NSObject +/// The Token to be used for verification. +@property (nonatomic, readonly, copy) NSString * _Nonnull recaptchaToken; +- (nonnull instancetype)init:(NSString * _Nonnull)mobiletecqlyzr OBJC_DESIGNATED_INITIALIZER SWIFT_DEPRECATED_MSG("Newer implementations return the Token as a string."); +- (nonnull instancetype)init SWIFT_UNAVAILABLE; ++ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); +@end + +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Info.plist b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Info.plist new file mode 100755 index 00000000..69423c76 Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Info.plist differ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftdoc b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftdoc new file mode 100755 index 00000000..95b957f6 Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftdoc differ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftinterface b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftinterface new file mode 100755 index 00000000..b200e495 --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/arm64.swiftinterface @@ -0,0 +1,107 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 6.1.2 (swiftlang-6.1.2.1.2 clang-1700.0.13.5) +// swift-module-flags: -target arm64-apple-ios15.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 6 -enforce-exclusivity=checked -O -enable-experimental-feature AccessLevelOnImport -enable-bare-slash-regex -module-name RecaptchaEnterpriseSDK -package-name googlemac/iPhone/recaptcha/enterprise +// swift-module-flags-ignorable: -no-verify-emitted-module-interface -interface-compiler-version 6.1.2 +import CryptoKit +import DeviceCheck +import Foundation +import Network +import Security +import Swift +import UIKit +import WebKit +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims +@objc @_inheritsConvenienceInitializers @objcMembers public class RecaptchaConstant : ObjectiveC.NSObject { + @objc public static let clientVersion: Swift.String + @objc public static let defaultTimeoutExecute: Swift.Double + @objc public static let defaultTimeoutInit: Swift.Double + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc public class Recaptcha : ObjectiveC.NSObject { + @objc public static func fetchClient(withSiteKey siteKey: Swift.String, completion: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + public static func fetchClient(withSiteKey siteKey: Swift.String) async throws -> RecaptchaEnterpriseSDK.RecaptchaClient + @available(*, deprecated, message: "Use the new api `fetchClient(withSiteKey:completion:)` instead.") + @objc public static func getClient(withSiteKey siteKey: Swift.String, withTimeout timeout: Swift.Double, completion: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + @available(*, deprecated, message: "Use the new api `fetchClient(withSiteKey:completion:)` instead.") + @objc public static func getClient(withSiteKey siteKey: Swift.String, completion: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + @available(*, deprecated, message: "Use the new api `fetchClient(withSiteKey:completion:)` instead.") + @objc public static func getClient(siteKey: Swift.String, completionHandler: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + @objc deinit +} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +@objc public enum RecaptchaActionType : Swift.Int { + case login + case signup + case other + public init?(rawValue: Swift.Int) + @available(*, deprecated, message: "Use RecaptchaAction enums instead.") + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@_hasMissingDesignatedInitializers @objc final public class RecaptchaAction : ObjectiveC.NSObject, Swift.Sendable { + convenience public init(customAction: Swift.String) + @available(*, deprecated, message: "Please use customAction with the regular RecaptchaAction.custom() function") + @objc public init(action: RecaptchaEnterpriseSDK.RecaptchaActionType) + @objc public static let login: RecaptchaEnterpriseSDK.RecaptchaAction + @objc public static let signup: RecaptchaEnterpriseSDK.RecaptchaAction + @objc public static func custom(_ action: Swift.String) -> RecaptchaEnterpriseSDK.RecaptchaAction + @objc deinit +} +@_hasMissingDesignatedInitializers @objc final public class RecaptchaClient : ObjectiveC.NSObject, Swift.Sendable { + @objc final public func execute(withAction action: RecaptchaEnterpriseSDK.RecaptchaAction, withTimeout timeout: Swift.Double, completion: @escaping @Sendable (Swift.String?, Foundation.NSError?) -> Swift.Void) + final public func execute(withAction action: RecaptchaEnterpriseSDK.RecaptchaAction, withTimeout timeout: Swift.Double = RecaptchaConstant.defaultTimeoutExecute) async throws -> Swift.String + @objc final public func execute(withAction action: RecaptchaEnterpriseSDK.RecaptchaAction, completion: @escaping @Sendable (Swift.String?, Foundation.NSError?) -> Swift.Void) + @objc final public func execute(_ action: RecaptchaEnterpriseSDK.RecaptchaAction, completion: @escaping @Sendable (Swift.String?, Foundation.NSError?) -> Swift.Void) + @available(*, deprecated, message: "Use `execute(withAction:completion:)` instead.") + @objc final public func execute(_ action: RecaptchaEnterpriseSDK.RecaptchaAction, completionHandler: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaToken?, RecaptchaEnterpriseSDK.RecaptchaError?) -> Swift.Void) + @objc deinit +} +@objc public enum RecaptchaErrorCode : Swift.Int { + case errorCodeUnknown = 0 + case errorNetworkError = 1 + case errorInvalidSiteKey = 2 + case erroInvalidKeyType = 3 + case errorInvalidPackageName = 4 + case errorInvalidAction = 5 + case errorInvalidTimeout = 6 + case errorNoNetwork = 7 + case errorCodeInternalError = 100 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@_hasMissingDesignatedInitializers @objc public class RecaptchaError : Foundation.NSError, @unchecked Swift.Sendable { + @objc public var errorCode: RecaptchaEnterpriseSDK.RecaptchaErrorCode { + @objc get + } + @objc public var errorMessage: Swift.String { + @objc get + } + public init(errorCode: RecaptchaEnterpriseSDK.RecaptchaErrorCode, errorMessage: Swift.String) + @objc deinit +} +@available(*, deprecated, message: "Newer implementations return the Token as a string.") +@objc public class RecaptchaToken : ObjectiveC.NSObject { + @objc final public let recaptchaToken: Swift.String + @available(*, deprecated, message: "Newer implementations return the Token as a string.") + @objc public init(_ mobiletecqlyzr: Swift.String) + @objc deinit +} +extension Foundation.UserDefaults : @unchecked Swift.Sendable { +} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +extension RecaptchaEnterpriseSDK.RecaptchaActionType : Swift.Equatable {} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +extension RecaptchaEnterpriseSDK.RecaptchaActionType : Swift.Hashable {} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +extension RecaptchaEnterpriseSDK.RecaptchaActionType : Swift.RawRepresentable {} +extension RecaptchaEnterpriseSDK.RecaptchaErrorCode : Swift.Equatable {} +extension RecaptchaEnterpriseSDK.RecaptchaErrorCode : Swift.Hashable {} +extension RecaptchaEnterpriseSDK.RecaptchaErrorCode : Swift.RawRepresentable {} diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/x86_64.swiftdoc b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/x86_64.swiftdoc new file mode 100755 index 00000000..8823335c Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/x86_64.swiftdoc differ diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/x86_64.swiftinterface b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/x86_64.swiftinterface new file mode 100755 index 00000000..9dfb6826 --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/RecaptchaEnterpriseSDK.swiftmodule/x86_64.swiftinterface @@ -0,0 +1,107 @@ +// swift-interface-format-version: 1.0 +// swift-compiler-version: Apple Swift version 6.1.2 (swiftlang-6.1.2.1.2 clang-1700.0.13.5) +// swift-module-flags: -target x86_64-apple-ios15.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 6 -enforce-exclusivity=checked -O -enable-experimental-feature AccessLevelOnImport -enable-bare-slash-regex -module-name RecaptchaEnterpriseSDK -package-name googlemac/iPhone/recaptcha/enterprise +// swift-module-flags-ignorable: -no-verify-emitted-module-interface -interface-compiler-version 6.1.2 +import CryptoKit +import DeviceCheck +import Foundation +import Network +import Security +import Swift +import UIKit +import WebKit +import _Concurrency +import _StringProcessing +import _SwiftConcurrencyShims +@objc @_inheritsConvenienceInitializers @objcMembers public class RecaptchaConstant : ObjectiveC.NSObject { + @objc public static let clientVersion: Swift.String + @objc public static let defaultTimeoutExecute: Swift.Double + @objc public static let defaultTimeoutInit: Swift.Double + @objc override dynamic public init() + @objc deinit +} +@_inheritsConvenienceInitializers @_hasMissingDesignatedInitializers @objc public class Recaptcha : ObjectiveC.NSObject { + @objc public static func fetchClient(withSiteKey siteKey: Swift.String, completion: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + public static func fetchClient(withSiteKey siteKey: Swift.String) async throws -> RecaptchaEnterpriseSDK.RecaptchaClient + @available(*, deprecated, message: "Use the new api `fetchClient(withSiteKey:completion:)` instead.") + @objc public static func getClient(withSiteKey siteKey: Swift.String, withTimeout timeout: Swift.Double, completion: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + @available(*, deprecated, message: "Use the new api `fetchClient(withSiteKey:completion:)` instead.") + @objc public static func getClient(withSiteKey siteKey: Swift.String, completion: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + @available(*, deprecated, message: "Use the new api `fetchClient(withSiteKey:completion:)` instead.") + @objc public static func getClient(siteKey: Swift.String, completionHandler: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaClient?, Foundation.NSError?) -> Swift.Void) + @objc deinit +} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +@objc public enum RecaptchaActionType : Swift.Int { + case login + case signup + case other + public init?(rawValue: Swift.Int) + @available(*, deprecated, message: "Use RecaptchaAction enums instead.") + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@_hasMissingDesignatedInitializers @objc final public class RecaptchaAction : ObjectiveC.NSObject, Swift.Sendable { + convenience public init(customAction: Swift.String) + @available(*, deprecated, message: "Please use customAction with the regular RecaptchaAction.custom() function") + @objc public init(action: RecaptchaEnterpriseSDK.RecaptchaActionType) + @objc public static let login: RecaptchaEnterpriseSDK.RecaptchaAction + @objc public static let signup: RecaptchaEnterpriseSDK.RecaptchaAction + @objc public static func custom(_ action: Swift.String) -> RecaptchaEnterpriseSDK.RecaptchaAction + @objc deinit +} +@_hasMissingDesignatedInitializers @objc final public class RecaptchaClient : ObjectiveC.NSObject, Swift.Sendable { + @objc final public func execute(withAction action: RecaptchaEnterpriseSDK.RecaptchaAction, withTimeout timeout: Swift.Double, completion: @escaping @Sendable (Swift.String?, Foundation.NSError?) -> Swift.Void) + final public func execute(withAction action: RecaptchaEnterpriseSDK.RecaptchaAction, withTimeout timeout: Swift.Double = RecaptchaConstant.defaultTimeoutExecute) async throws -> Swift.String + @objc final public func execute(withAction action: RecaptchaEnterpriseSDK.RecaptchaAction, completion: @escaping @Sendable (Swift.String?, Foundation.NSError?) -> Swift.Void) + @objc final public func execute(_ action: RecaptchaEnterpriseSDK.RecaptchaAction, completion: @escaping @Sendable (Swift.String?, Foundation.NSError?) -> Swift.Void) + @available(*, deprecated, message: "Use `execute(withAction:completion:)` instead.") + @objc final public func execute(_ action: RecaptchaEnterpriseSDK.RecaptchaAction, completionHandler: @escaping @Sendable (RecaptchaEnterpriseSDK.RecaptchaToken?, RecaptchaEnterpriseSDK.RecaptchaError?) -> Swift.Void) + @objc deinit +} +@objc public enum RecaptchaErrorCode : Swift.Int { + case errorCodeUnknown = 0 + case errorNetworkError = 1 + case errorInvalidSiteKey = 2 + case erroInvalidKeyType = 3 + case errorInvalidPackageName = 4 + case errorInvalidAction = 5 + case errorInvalidTimeout = 6 + case errorNoNetwork = 7 + case errorCodeInternalError = 100 + public init?(rawValue: Swift.Int) + public typealias RawValue = Swift.Int + public var rawValue: Swift.Int { + get + } +} +@_hasMissingDesignatedInitializers @objc public class RecaptchaError : Foundation.NSError, @unchecked Swift.Sendable { + @objc public var errorCode: RecaptchaEnterpriseSDK.RecaptchaErrorCode { + @objc get + } + @objc public var errorMessage: Swift.String { + @objc get + } + public init(errorCode: RecaptchaEnterpriseSDK.RecaptchaErrorCode, errorMessage: Swift.String) + @objc deinit +} +@available(*, deprecated, message: "Newer implementations return the Token as a string.") +@objc public class RecaptchaToken : ObjectiveC.NSObject { + @objc final public let recaptchaToken: Swift.String + @available(*, deprecated, message: "Newer implementations return the Token as a string.") + @objc public init(_ mobiletecqlyzr: Swift.String) + @objc deinit +} +extension Foundation.UserDefaults : @unchecked Swift.Sendable { +} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +extension RecaptchaEnterpriseSDK.RecaptchaActionType : Swift.Equatable {} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +extension RecaptchaEnterpriseSDK.RecaptchaActionType : Swift.Hashable {} +@available(*, deprecated, message: "Use RecaptchaAction enums instead.") +extension RecaptchaEnterpriseSDK.RecaptchaActionType : Swift.RawRepresentable {} +extension RecaptchaEnterpriseSDK.RecaptchaErrorCode : Swift.Equatable {} +extension RecaptchaEnterpriseSDK.RecaptchaErrorCode : Swift.Hashable {} +extension RecaptchaEnterpriseSDK.RecaptchaErrorCode : Swift.RawRepresentable {} diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/module.modulemap b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/module.modulemap new file mode 100755 index 00000000..8b67ccf9 --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module RecaptchaEnterpriseSDK { + header "RecaptchaEnterpriseSDK.h" + + requires objc +} diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/PrivacyInfo.xcprivacy b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/PrivacyInfo.xcprivacy new file mode 100755 index 00000000..74847dde --- /dev/null +++ b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/PrivacyInfo.xcprivacy @@ -0,0 +1,56 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePerformanceData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + \ No newline at end of file diff --git a/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/RecaptchaEnterpriseSDK b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/RecaptchaEnterpriseSDK new file mode 100755 index 00000000..1f7a7fc4 Binary files /dev/null and b/third-party/recaptcha/RecaptchaEnterpriseSDK.xcframework/ios-arm64_x86_64-simulator/RecaptchaEnterpriseSDK.framework/RecaptchaEnterpriseSDK differ diff --git a/third-party/webrtc/BUILD b/third-party/webrtc/BUILD index 37837b16..85f2628d 100644 --- a/third-party/webrtc/BUILD +++ b/third-party/webrtc/BUILD @@ -2767,6 +2767,7 @@ arm64_specific_sources = ["webrtc/" + path for path in [ arch_specific_sources = select({ "@build_bazel_rules_apple//apple:ios_arm64": common_arm_specific_sources + arm64_specific_sources, "//build-system:ios_sim_arm64": common_arm_specific_sources + arm64_specific_sources, + "//conditions:default": common_arm_specific_sources + arm64_specific_sources, }) common_flags = [ @@ -2782,12 +2783,12 @@ common_flags = [ "-DWEBRTC_HAVE_SCTP", "-DWEBRTC_NS_FLOAT", "-DRTC_DISABLE_TRACE_EVENTS", - #"-DWEBRTC_OPUS_SUPPORT_120MS_PTIME=1", +#"-DWEBRTC_OPUS_SUPPORT_120MS_PTIME=1", "-DWEBRTC_APM_DEBUG_DUMP=0", "-DBWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0", "-DABSL_ALLOCATOR_NOTHROW=1", "-DDYNAMIC_ANNOTATIONS_ENABLED=0", - #"-DNS_BLOCK_ASSERTIONS=1", +#"-DNS_BLOCK_ASSERTIONS=1", "-DWEBRTC_ENABLE_PROTOBUF=0", "-DWEBRTC_ENABLE_AVX2", "-DWEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0", @@ -2807,6 +2808,7 @@ arm64_specific_flags = [ arch_specific_cflags = select({ "@build_bazel_rules_apple//apple:ios_arm64": common_flags + arm64_specific_flags, "//build-system:ios_sim_arm64": common_flags + arm64_specific_flags, + "//conditions:default": common_flags + arm64_specific_flags, }) dcsctp_sources = [ "webrtc/net/dcsctp/" + path for path in [ diff --git a/third-party/webrtc/crc32c/BUILD b/third-party/webrtc/crc32c/BUILD index 52d9138c..8a9db1c0 100644 --- a/third-party/webrtc/crc32c/BUILD +++ b/third-party/webrtc/crc32c/BUILD @@ -6,6 +6,9 @@ arch_specific_crc32c_sources = select({ "//build-system:ios_sim_arm64": [ "third_party/crc32c/src/crc32c_arm64.cc", ], + "//conditions:default": [ + "third_party/crc32c/src/crc32c_arm64.cc", + ], }) crc32c_sources = ["third_party/crc32c/src/" + x for x in [ diff --git a/third-party/webrtc/libsrtp/BUILD b/third-party/webrtc/libsrtp/BUILD index bc29d57c..420d0900 100644 --- a/third-party/webrtc/libsrtp/BUILD +++ b/third-party/webrtc/libsrtp/BUILD @@ -37,6 +37,7 @@ arm64_specific_flags = [ arch_specific_cflags = select({ "@build_bazel_rules_apple//apple:ios_arm64": common_flags + arm64_specific_flags, "//build-system:ios_sim_arm64": common_flags + arm64_specific_flags, + "//conditions:default": common_flags + arm64_specific_flags, }) optimization_flags = select({ diff --git a/versions.json b/versions.json index 6e69f7fb..50523d2e 100644 --- a/versions.json +++ b/versions.json @@ -1,6 +1,6 @@ { "app": "12.2.3", - "xcode": "26.0", - "bazel": "8.4.2:45e9388abf21d1107e146ea366ad080eb93cb6a5f3a4a3b048f78de0bc3faffa", + "xcode": "26.2", + "bazel": "8.4.2:ce73346274c379f77880db8bd8b9c8569885fe56f19386173760949da9078df0", "macos": "26" -} +} \ No newline at end of file