Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mikhail Filimonov 2026-01-30 17:41:04 +01:00
commit 4da24b44dc
93 changed files with 2914 additions and 1410 deletions

View file

@ -1,7 +1,7 @@
http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
bazel_dep(name = "bazel_features", version = "1.30.0")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "bazel_features", version = "1.33.0")
bazel_dep(name = "bazel_skylib", version = "1.8.1")
bazel_dep(name = "platforms", version = "0.0.11")
bazel_dep(name = "rules_xcodeproj")

165
MODULE.bazel.lock generated
View file

@ -9,7 +9,10 @@
"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/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20250512.1/source.json": "d725d73707d01bb46ab3ca59ba408b8e9bd336642ca77a2269d4bfb8bbfd413d",
"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",
@ -21,9 +24,9 @@
"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.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.33.0/source.json": "13617db3930328c2cd2807a0f13d52ca870ac05f96db9668655113265147b2a6",
"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",
@ -36,19 +39,23 @@
"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/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a",
"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/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713",
"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/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000",
"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/nlohmann_json/3.12.0.bcr.1/MODULE.bazel": "a1c8bb07b5b91d971727c635f449d05623ac9608f6fe4f5f04254ea12f08e349",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/nlohmann_json/3.12.0.bcr.1/source.json": "93f82a5ae985eb935c539bfee95e04767187818189241ac956f3ccadbdb8fb02",
"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",
@ -64,12 +71,17 @@
"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/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95",
"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/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79",
"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/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680",
"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/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa",
"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",
@ -78,16 +90,18 @@
"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.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a",
"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_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.2.15/MODULE.bazel": "6a0a4a75a57aa6dc888300d848053a58c6b12a29f89d4304e1c41448514ec6e8",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.2.15/source.json": "197965c6dcca5c98a9288f93849e2e1c69d622e71b0be8deb524e22d48c88e32",
"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",
@ -102,13 +116,15 @@
"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_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2",
"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_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4",
"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",
@ -121,19 +137,23 @@
"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.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483",
"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_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e",
"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.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937",
"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_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/1.6.0/source.json": "e980f654cf66ec4928672f41fc66c4102b5ea54286acf4aecd23256c84211be6",
"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",
@ -144,8 +164,8 @@
"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/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b",
"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",
@ -220,8 +240,8 @@
},
"@@rules_python+//python/uv:uv.bzl%uv": {
"general": {
"bzlTransitiveDigest": "Xpqjnjzy6zZ90Es9Wa888ZLHhn7IsNGbph/e6qoxzw8=",
"usagesDigest": "vJ5RHUxAnV24M5swNGiAnkdxMx3Hp/iOLmNANTC5Xc8=",
"bzlTransitiveDigest": "PmZM/pIkZKEDDL68TohlKJrWPYKL5VwUw3MA7kmm6fk=",
"usagesDigest": "p80sy6cYQuWxx5jhV3fOTu+N9EyIUFG9+F7UC/nhXic=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
@ -246,6 +266,11 @@
}
},
"recordedRepoMappingEntries": [
[
"rules_python+",
"bazel_tools",
"bazel_tools"
],
[
"rules_python+",
"platforms",
@ -256,7 +281,7 @@
},
"@@rules_xcodeproj+//xcodeproj:extensions.bzl%internal": {
"general": {
"bzlTransitiveDigest": "hsJI1bzu5CX3ZE+NX8jnNWEOh1pNDT650SbTSBnF5jA=",
"bzlTransitiveDigest": "+qmLBZzimJ0CYyKoQg6/pbdkTnu/s4e5IisoM+TLM+8=",
"usagesDigest": "fvsnMonVwKDYnBfww4bXuYie3WU0d9VSqT2gePSdQco=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@ -268,93 +293,21 @@
}
},
"recordedRepoMappingEntries": [
[
"bazel_features+",
"bazel_features_globals",
"bazel_features++version_extension+bazel_features_globals"
],
[
"bazel_features+",
"bazel_features_version",
"bazel_features++version_extension+bazel_features_version"
],
[
"rules_xcodeproj+",
"bazel_tools",
"bazel_tools"
]
]
}
},
"@@rules_xcodeproj+//xcodeproj:extensions.bzl%non_module_deps": {
"general": {
"bzlTransitiveDigest": "hsJI1bzu5CX3ZE+NX8jnNWEOh1pNDT650SbTSBnF5jA=",
"usagesDigest": "jzxYhnOC9BE0dJ0biFLfxWXi/+R19uAAZkJ0p9CY0JI=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"rules_xcodeproj_legacy_index_import": {
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
"attributes": {
"build_file_content": "load(\"@bazel_skylib//rules:native_binary.bzl\", \"native_binary\")\n\nnative_binary(\n name = \"index_import\",\n src = \"index-import\",\n out = \"index-import\",\n visibility = [\"//visibility:public\"],\n)\n",
"canonical_id": "index-import-5.8.0.1",
"sha256": "28c1ffa39d99e74ed70623899b207b41f79214c498c603915aef55972a851a15",
"url": "https://github.com/MobileNativeFoundation/index-import/releases/download/5.8.0.1/index-import.tar.gz"
}
},
"rules_xcodeproj_index_import": {
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
"attributes": {
"build_file_content": "load(\"@bazel_skylib//rules:native_binary.bzl\", \"native_binary\")\n\nnative_binary(\n name = \"index_import\",\n src = \"index-import\",\n out = \"index-import\",\n visibility = [\"//visibility:public\"],\n)\n",
"canonical_id": "index-import-6.1.0.1",
"sha256": "9a54fc1674af6031125a9884480a1e31e1bcf48b8f558b3e8bcc6b6fcd6e8b61",
"url": "https://github.com/MobileNativeFoundation/index-import/releases/download/6.1.0.1/index-import.tar.gz"
}
},
"com_github_apple_swift_argument_parser": {
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
"attributes": {
"build_file_content": "load(\"@build_bazel_rules_swift//swift:swift.bzl\", \"swift_library\")\n\nswift_library(\n name = \"ArgumentParserToolInfo\",\n srcs = glob([\"Sources/ArgumentParserToolInfo/**/*.swift\"]),\n visibility = [\"//visibility:public\"],\n)\n\nswift_library(\n name = \"ArgumentParser\",\n srcs = glob([\"Sources/ArgumentParser/**/*.swift\"]),\n visibility = [\"//visibility:public\"],\n deps = [\":ArgumentParserToolInfo\"],\n)\n",
"sha256": "4a10bbef290a2167c5cc340b39f1f7ff6a8cf4e1b5433b68548bf5f1e542e908",
"strip_prefix": "swift-argument-parser-1.2.3",
"url": "https://github.com/apple/swift-argument-parser/archive/refs/tags/1.2.3.tar.gz"
}
},
"com_github_michaeleisel_jjliso8601dateformatter": {
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
"attributes": {
"build_file_content": "objc_library(\n name = \"JJLISO8601DateFormatter\",\n srcs = glob([\"Sources/JJLISO8601DateFormatter/**/*\"]),\n copts = [\n \"-Wno-incompatible-pointer-types\",\n \"-Wno-incompatible-pointer-types-discards-qualifiers\",\n \"-Wno-shorten-64-to-32\",\n \"-Wno-unreachable-code\",\n \"-Wno-unused-function\",\n \"-Wno-unused-variable\",\n ],\n includes = [\"Sources/JJLISO8601DateFormatter/include\"],\n hdrs = glob([\"Sources/JJLISO8601DateFormatter/include/*\"]),\n visibility = [\"//visibility:public\"],\n)\n",
"patches": [
"@@rules_xcodeproj+//third_party/com_github_michaeleisel_jjliso8601dateformatter:include_fix.patch"
],
"sha256": "6fe15f251f100f3df057c2802a50765387674fde9c922375683682b5ba37eef0",
"strip_prefix": "JJLISO8601DateFormatter-0.1.6",
"url": "https://github.com/michaeleisel/JJLISO8601DateFormatter/archive/refs/tags/0.1.6.tar.gz"
}
},
"com_github_michaeleisel_zippyjsoncfamily": {
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
"attributes": {
"build_file_content": "objc_library(\n name = \"ZippyJSONCFamily\",\n copts = [\n \"-std=c++17\",\n \"-Wno-unused-function\",\n \"-Wno-reorder-ctor\",\n \"-Wno-return-type-c-linkage\",\n \"-Wno-shorten-64-to-32\",\n \"-Wno-unused-variable\",\n ],\n srcs = glob([\"Sources/ZippyJSONCFamily/**/*\"]),\n includes = [\"Sources/ZippyJSONCFamily/include\"],\n hdrs = glob([\"Sources/ZippyJSONCFamily/include/*\"]),\n visibility = [\"//visibility:public\"],\n)\n",
"patches": [
"@@rules_xcodeproj+//third_party/com_github_michaeleisel_zippyjsoncfamily:include_fix.patch"
],
"sha256": "b215927ada8403e1b056d39450c6a7b59122eca4b0c7fc5beb5f0b5fea2acd72",
"strip_prefix": "ZippyJSONCFamily-1.2.9",
"url": "https://github.com/michaeleisel/ZippyJSONCFamily/archive/refs/tags/1.2.9.tar.gz"
}
},
"com_github_michaeleisel_zippyjson": {
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
"attributes": {
"build_file_content": "load(\"@build_bazel_rules_swift//swift:swift.bzl\", \"swift_library\")\n\nswift_library(\n name = \"ZippyJSON\",\n srcs = glob([\"Sources/ZippyJSON/**/*.swift\"]),\n deps = [\n \"@com_github_michaeleisel_jjliso8601dateformatter//:JJLISO8601DateFormatter\",\n \"@com_github_michaeleisel_zippyjsoncfamily//:ZippyJSONCFamily\",\n ],\n visibility = [\"//visibility:public\"],\n)\n",
"sha256": "4b256843c9c3686c527e76dde54f8d76b6201c1fd903c07dc2211ab1b250bd04",
"strip_prefix": "ZippyJSON-1.2.10",
"url": "https://github.com/michaeleisel/ZippyJSON/archive/refs/tags/1.2.10.tar.gz"
}
},
"com_github_apple_swift_collections": {
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
"attributes": {
"build_file_content": "load(\"@build_bazel_rules_swift//swift:swift.bzl\", \"swift_library\")\n\nswift_library(\n name = \"Collections\",\n srcs = glob([\"Sources/Collections/**/*.swift\"]),\n deps = [\n \"@com_github_apple_swift_collections//:DequeModule\",\n ],\n visibility = [\"//visibility:public\"],\n)\n\nswift_library(\n name = \"DequeModule\",\n srcs = glob([\"Sources/DequeModule/**/*.swift\"]),\n visibility = [\"//visibility:public\"],\n)\n\nswift_library(\n name = \"OrderedCollections\",\n srcs = glob([\"Sources/OrderedCollections/**/*.swift\"]),\n visibility = [\"//visibility:public\"],\n)\n",
"sha256": "1a2ec8cc6c63c383a9dd4eb975bf83ce3bc7a2ac21a0289a50dae98a576327d6",
"strip_prefix": "swift-collections-4cab1c1c417855b90e9cfde40349a43aff99c536",
"url": "https://github.com/apple/swift-collections/archive/4cab1c1c417855b90e9cfde40349a43aff99c536.tar.gz"
}
}
},
"recordedRepoMappingEntries": [
"bazel_features",
"bazel_features+"
],
[
"rules_xcodeproj+",
"bazel_tools",

View file

@ -8,6 +8,13 @@ load("@build_bazel_rules_apple//apple:ios.bzl",
"ios_application",
"ios_extension",
"ios_framework",
"ios_ui_test",
"ios_ui_test_suite",
)
load(
"@build_bazel_rules_apple//apple/testing/default_runner:ios_test_runner.bzl",
"ios_test_runner",
)
load("@build_bazel_rules_apple//apple:resources.bzl",
@ -21,6 +28,7 @@ load("@build_bazel_rules_swift//swift:swift.bzl",
load(
"@rules_xcodeproj//xcodeproj:defs.bzl",
"top_level_targets",
"top_level_target",
"xcodeproj",
"xcode_provisioning_profile",
)
@ -1759,12 +1767,10 @@ xcodeproj(
bazel_path = telegram_bazel_path,
project_name = "Telegram",
tags = ["manual"],
top_level_targets = top_level_targets(
labels = [
":Telegram",
],
target_environments = ["device", "simulator"],
),
top_level_targets = [
top_level_target(":Telegram", target_environments = ["device", "simulator"]),
":iOSAppUITestSuite",
],
xcode_configurations = {
"Debug": {
"//command_line_option:compilation_mode": "dbg",
@ -1776,6 +1782,39 @@ xcodeproj(
default_xcode_configuration = "Debug"
)
filegroup(
name = "TestSources",
srcs = glob(["Tests/Sources/**/*.swift"]),
)
ios_test_runner(
name = "iPhone-17__26.2",
device_type = "iPhone 17",
os_version = "26.2",
)
swift_library(
name = "iOSAppUITestSuite.library",
testonly = True,
srcs = [":TestSources"],
module_name = "iOSAppUITestSuite",
tags = ["manual"],
)
ios_ui_test_suite(
name = "iOSAppUITestSuite",
bundle_id = "org.telegram.Telegram-iOS-uitests",
minimum_os_version = "13.0",
runners = [
":iPhone-17__26.2",
],
tags = ["manual"],
test_host = "//Telegram",
visibility = ["@rules_xcodeproj//xcodeproj:generated"],
deps = [":iOSAppUITestSuite.library"],
)
# Temporary targets used to simplify build tests
ios_application(

View file

@ -15676,9 +15676,9 @@ Error: %8$@";
"Gift.Craft.Title" = "Craft Gift";
"Gift.Craft.Description" = "Add up to **4 gifts** to craft new\n**$ %@**.\n\nIf crafting fails, all used gifts\nwill be lost.";
"Gift.Craft.ViewVariants" = "View all new variants";
"Gift.Craft.BackdropTooltip" = "There's **%1$@%** chance the crafted gift will have **%2$@** backdrop.";
"Gift.Craft.SymbolTooltip" = "There's **%1$@%** chance the crafted gift will have **%2$@** symbol.";
"Gift.Craft.ViewVariants" = "View all craftable models";
"Gift.Craft.BackdropTooltip" = "**%1$@%** chance the crafted gift will have **%2$@** backdrop.";
"Gift.Craft.SymbolTooltip" = "**%1$@%** chance the crafted gift will have **%2$@** symbol.";
"Gift.Craft.SuccessChanceSuffix" = "% Success Chance";
"Gift.Craft.Craft" = "Craft %@";
"Gift.Craft.Crafting.Title" = "Crafting";
@ -15699,6 +15699,7 @@ Error: %8$@";
"Gift.Craft.Info.Understood" = "Understood";
"Gift.Craft.Unavailable.Title" = "Try Later";
"Gift.Craft.Unavailable.Text" = "You will be able to craft this gift on %@.";
"Gift.Craft.Error.NotAvailable" = "One of the gifts is not available for crafting.";
"Gift.Craft.Select.Title" = "Select Gifts";
"Gift.Craft.Select.YourGifts" = "Your Gifts";
@ -15721,3 +15722,36 @@ Error: %8$@";
"Gift.Variants.ViewPrimaryModels" = "View Primary Models";
"Gift.View.OnSale" = "On sale for %@";
"Gift.Store.Balance.MyStars" = "My Stars";
"Gift.Store.Balance.MyTon" = "My TON";
"Resolve.GiftErrorNotFound" = "Sorry, this gift doesn't seem to exist.";
"Resolve.GiftErrorBurned" = "Sorry, this gift has already been burned.";
"Notification.StarGift.Burned" = "burned";
"AuthConfirmation.Title" = "Log in to **%@**";
"AuthConfirmation.Description" = "This site will receive your **name**,\n**username** and **profile photo**.";
"AuthConfirmation.Device" = "Device";
"AuthConfirmation.IpAddress" ="IP Address";
"AuthConfirmation.Info" ="This login attempt came from the device above.";
"AuthConfirmation.AllowMessages" = "Allow Messages";
"AuthConfirmation.AllowMessagesInfo" = "This will allow %@ to message you.";
"AuthConfirmation.Cancel" = "Cancel";
"AuthConfirmation.LogIn" = "Log In";
"AuthConfirmation.PhoneNumberConfirmation.Title" = "Phone Number";
"AuthConfirmation.PhoneNumberConfirmation.Text" = "**%1$@** wants to access your phone number **%2$@**.\n\nAllow access?";
"AuthConfirmation.PhoneNumberConfirmation.Deny" = "Deny";
"AuthConfirmation.PhoneNumberConfirmation.Allow" = "Allow";
"AuthConfirmation.LoginSuccess.Title" = "Login Successful";
"AuthConfirmation.LoginSuccess.Text" = "You are now logged in to [%@]().";
"AuthConfirmation.LoginSuccess.TextNoNumber" = "You are logged in to [%@](), but you didn't grant access to your phone number.";
"AuthConfirmation.LoginFail.Title" = "Login Failed";
"AuthConfirmation.LoginFail.Text" = "Please try logging in to [%@]() again.";
"Notification.GroupCreatorChangePending" = "%2$@ will become the new main admin in 7 days if %1$@ does not return.";
"Notification.GroupCreatorChangeApplied" = "%1$@ has transferred ownership of the group to %2$@.";

View file

@ -0,0 +1,25 @@
import XCTest
class UITests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
}

@ -1 +1 @@
Subproject commit 4c51f10063687af77eabbbdc035124d798ca3a3f
Subproject commit 7967e453d1f54963935a1ae06808e1c64420bfb1

@ -1 +1 @@
Subproject commit 5b448b48fc2fbd5456d55011811d77610dfd5069
Subproject commit 39cc2529241335d20afcb6700c587d064e7a2870

@ -1 +1 @@
Subproject commit 5c45bcec63147fbdba1a164bdd36c2883fc3169f
Subproject commit 2b9261134a123ad83f8b1d2dc7fc69ae83fe7172

@ -1 +1 @@
Subproject commit df2419de2c8d44592dddf213124e6b48bd175768
Subproject commit 79f26ec1315531a8459fe95934440cb3ca3c16df

View file

@ -316,7 +316,6 @@ public enum ResolvedUrl {
case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?)
case premiumGiftCode(slug: String)
case premiumMultiGift(reference: String?)
case collectible(gift: StarGift.UniqueGift?)
case auction(auction: GiftAuctionContext?)
case messageLink(link: TelegramResolvedMessageLink?)
case stars
@ -328,6 +327,13 @@ public enum ResolvedUrl {
case unknownDeepLink(path: String)
case oauth(url: String)
public enum ResolvedCollectible {
case gift(StarGift.UniqueGift)
case invalidSlug
case alreadyBurned
}
case collectible(ResolvedCollectible)
public enum ChatsSection {
case search
case edit
@ -732,6 +738,11 @@ public enum DeviceContactInfoSubject {
}
public enum PeerInfoControllerMode {
public enum PeerInfoMediaKind {
case photoVideo
case file
}
case generic
case calls(messages: [Message])
case nearbyPeer(distance: Int32)
@ -747,6 +758,7 @@ public enum PeerInfoControllerMode {
case monoforum(EnginePeer.Id)
case storyAlbum(id: Int64)
case giftCollection(id: Int64)
case media(kind: PeerInfoMediaKind, messageIndex: EngineMessage.Index)
}
public enum ContactListActionItemInlineIconPosition {

View file

@ -48,6 +48,7 @@ public final class OpenChatMessageParams {
public let playlistLocation: PeerMessagesPlaylistLocation?
public let gallerySource: GalleryControllerItemSource?
public let centralItemUpdated: ((MessageId) -> Void)?
public let navigateToMessageContext: ((EngineMessage) -> Void)?
public let getSourceRect: (() -> CGRect?)?
public let blockInteraction: Promise<Bool>
@ -81,6 +82,7 @@ public final class OpenChatMessageParams {
playlistLocation: PeerMessagesPlaylistLocation? = nil,
gallerySource: GalleryControllerItemSource? = nil,
centralItemUpdated: ((MessageId) -> Void)? = nil,
navigateToMessageContext: ((EngineMessage) -> Void)? = nil,
getSourceRect: (() -> CGRect?)? = nil
) {
self.context = context
@ -112,6 +114,7 @@ public final class OpenChatMessageParams {
self.playlistLocation = playlistLocation
self.gallerySource = gallerySource
self.centralItemUpdated = centralItemUpdated
self.navigateToMessageContext = navigateToMessageContext
self.getSourceRect = getSourceRect
self.blockInteraction = Promise()
}

View file

@ -114,12 +114,14 @@ public extension ComponentTransition {
options.insert(.allowUserInteraction)
}
switch curve {
case .spring, .bounce:
case .spring, .bounce, .custom:
var parameters: CALayerSpringParametersOverrideParameters?
var dampingValue: CGFloat = 500.0
if case let .bounce(stiffness, damping) = curve {
dampingValue = damping
parameters = CALayerSpringParametersOverrideParameters(stiffness: stiffness, damping: damping, duration: duration)
parameters = CALayerSpringParametersOverrideParametersSpring(stiffness: stiffness, damping: damping, duration: duration)
} else if case let .custom(a, b, c, d) = curve {
parameters = CALayerSpringParametersOverrideParametersCustomCurve(cp1: CGPoint(x: CGFloat(a), y: CGFloat(b)), cp2: CGPoint(x: CGFloat(c), y: CGFloat(d)))
}
CALayer.push(CALayerSpringParametersOverride(parameters: parameters))
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: dampingValue, initialSpringVelocity: 0.0, options: options, animations: {

View file

@ -1152,7 +1152,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
self.hasExpandedCaptionPromise.set(scrollView.contentOffset.y > 1.0)
}
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> LayoutInfo {
self.validLayout = (size, metrics, leftInset, rightInset, bottomInset, contentInset)
let width = size.width
@ -1177,6 +1177,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
buttonPanelInsets.right += 18.0
}
var needsShadow = false
if !self.textNode.isHidden {
var textFrame = CGRect()
var visibleTextHeight: CGFloat = 0.0
@ -1191,6 +1192,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
var textOffset: CGFloat = 0.0
var additionalTextHeight: CGFloat = 0.0
if displayCaption {
needsShadow = true
visibleTextHeight = textSize.height
if visibleTextHeight > 100.0 {
visibleTextHeight = 80.0
@ -1272,8 +1275,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
panelHeight += 10.0
if isLandscape, case .compact = metrics.widthClass {
panelHeight += 14.0
if isLandscape {
panelHeight += 34.0
} else {
panelHeight += 34.0
}
@ -1409,6 +1412,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
}
}
if !centerControlItems.isEmpty && rightControlItems.isEmpty {
rightControlItems = centerControlItems
centerControlItems = []
}
let buttonPanelSize = self.buttonPanel.update(
transition: ComponentTransition(transition),
component: AnyComponent(GlassControlPanelComponent(
@ -1482,7 +1490,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
self.contentNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
return panelHeight
return LayoutInfo(height: panelHeight, needsShadow: needsShadow)
}
override func animateIn(transition: ContainedViewLayoutTransition) {
@ -1500,7 +1508,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
scrubberView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
transition.animatePositionAdditive(node: self.scrollWrapperNode, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight))
self.scrollWrapperNode.alpha = 1.0
self.scrollNode.alpha = 0.0
ComponentTransition(transition).setAlpha(view: self.scrollNode.view, alpha: 1.0)
if let buttonPanelView = self.buttonPanel.view {
buttonPanelView.alpha = 1.0
}
@ -1510,7 +1519,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
self.statusNode.alpha = 1.0
self.playbackControlButton.alpha = 1.0
self.buttonNode?.alpha = 1.0
self.scrollWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
override func animateOut(transition: ContainedViewLayoutTransition) {
@ -1527,7 +1535,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
scrubberView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
}
transition.updateFrame(node: self.scrollWrapperNode, frame: self.scrollWrapperNode.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight))
self.scrollWrapperNode.alpha = 0.0
ComponentTransition(transition).setAlpha(view: self.scrollNode.view, alpha: 0.0, completion: { _ in
completion()
})
if let buttonPanelView = self.buttonPanel.view {
buttonPanelView.alpha = 0.0
@ -1538,9 +1548,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
self.statusNode.alpha = 0.0
self.playbackControlButton.alpha = 0.0
self.buttonNode?.alpha = 0.0
self.scrollWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { _ in
completion()
})
}
@objc func fullscreenButtonPressed() {

View file

@ -587,6 +587,8 @@ public class GalleryController: ViewController, StandalonePresentableController,
private let source: GalleryControllerItemSource
private let invertItemOrder: Bool
private let titleView: GalleryTitleView
private let streamVideos: Bool
private let _ready = Promise<Bool>()
@ -617,7 +619,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
private var configuration: GalleryConfiguration?
private let centralItemTitle = Promise<String>()
private let centralItemTitleView = Promise<UIView?>()
private let centralItemTitleContent = Promise<GalleryTitleView.Content?>()
private let centralItemRightBarButtonItem = Promise<UIBarButtonItem?>()
private let centralItemRightBarButtonItems = Promise<[UIBarButtonItem]?>(nil)
private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>()
@ -645,6 +647,8 @@ public class GalleryController: ViewController, StandalonePresentableController,
public var onDidAppear: (() -> Void)?
public var useSimpleAnimation: Bool = false
public var navigateToMessageContext: ((EngineMessage) -> Void)?
private var initialOrientation: UIInterfaceOrientation?
public init(context: AccountContext, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, playbackRate: Double? = nil, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil, generateStoreAfterDownload: ((Message, TelegramMediaFile) -> (() -> Void)?)? = nil) {
@ -680,6 +684,8 @@ public class GalleryController: ViewController, StandalonePresentableController,
openActionOptionsImpl?(action, message)
}
self.titleView = GalleryTitleView(context: context, presentationData: self.presentationData)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
let backItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.donePressed))
@ -915,8 +921,8 @@ public class GalleryController: ViewController, StandalonePresentableController,
self?.navigationItem.title = title
}))
self.centralItemAttributesDisposable.add(self.centralItemTitleView.get().start(next: { [weak self] titleView in
self?.navigationItem.titleView = titleView
self.centralItemAttributesDisposable.add(self.centralItemTitleContent.get().start(next: { [weak self] titleContent in
self?.titleView.setContent(content: titleContent)
}))
self.centralItemAttributesDisposable.add(combineLatest(self.centralItemRightBarButtonItem.get(), self.centralItemRightBarButtonItems.get()).start(next: { [weak self] rightBarButtonItem, rightBarButtonItems in
@ -933,7 +939,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
self.centralItemAttributesDisposable.add(self.centralItemFooterContentNode.get().start(next: { [weak self] footerContentNode, overlayContentNode in
self?.galleryNode.updatePresentationState({
$0.withUpdatedFooterContentNode(footerContentNode).withUpdatedOverlayContentNode(overlayContentNode)
}, transition: .immediate)
}, transition: .animated(duration: 0.4, curve: .spring))
}))
self.centralItemAttributesDisposable.add(self.centralItemNavigationStyle.get().start(next: { [weak self] style in
@ -1297,6 +1303,10 @@ public class GalleryController: ViewController, StandalonePresentableController,
var animatedOutNode = true
var animatedOutInterface = false
if forceAway {
self._hiddenMedia.set(.single(nil))
}
let completion = { [weak self] in
if animatedOutNode && animatedOutInterface {
self?.actionInteraction?.updateCanReadHistory(true)
@ -1375,7 +1385,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
})
let disableTapNavigation = !(self.context.sharedContext.currentMediaDisplaySettings.with { $0 }.showNextMediaOnTap)
self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction, disableTapNavigation: disableTapNavigation)
self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction, titleView: titleView, disableTapNavigation: disableTapNavigation)
self.displayNodeDidLoad()
self.galleryNode.statusBar = self.statusBar
@ -1529,7 +1539,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
if let node = strongSelf.galleryNode.pager.centralItemNode() {
strongSelf.centralItemTitle.set(node.title())
strongSelf.centralItemTitleView.set(node.titleView())
strongSelf.centralItemTitleContent.set(node.titleContent())
strongSelf.centralItemRightBarButtonItem.set(node.rightBarButtonItem())
strongSelf.centralItemRightBarButtonItems.set(node.rightBarButtonItems())
strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
@ -1715,7 +1725,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
let entry = self.entries[centralItemNode.index]
self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemTitleContent.set(centralItemNode.titleContent())
self.centralItemRightBarButtonItem.set(centralItemNode.rightBarButtonItem())
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
@ -1768,7 +1778,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
if let centralItemNode = self.galleryNode.pager.centralItemNode() {
let message = self.entries[centralItemNode.index].entry.message
self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemTitleContent.set(centralItemNode.titleContent())
self.centralItemRightBarButtonItem.set(centralItemNode.rightBarButtonItem())
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
@ -1913,4 +1923,11 @@ public class GalleryController: ViewController, StandalonePresentableController,
return true
}
func dismissAndNavigateToMessageContext(message: Message) {
if let navigateToMessageContext = self.navigateToMessageContext {
navigateToMessageContext(EngineMessage(message))
}
self.dismiss(forceAway: true)
}
}

View file

@ -20,11 +20,8 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
private let context: AccountContext
public var statusBar: StatusBar?
public var navigationBar: NavigationBar? {
didSet {
}
}
public var navigationBar: NavigationBar?
private let titleView: GalleryTitleView?
private let headerEdgeEffectView: EdgeEffectView
@ -63,12 +60,13 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
}
}
public init(context: AccountContext, controllerInteraction: GalleryControllerInteraction, pageGap: CGFloat = 20.0, disableTapNavigation: Bool = false) {
public init(context: AccountContext, controllerInteraction: GalleryControllerInteraction, titleView: GalleryTitleView?, pageGap: CGFloat = 20.0, disableTapNavigation: Bool = false) {
self.context = context
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = UIColor.black
self.scrollView = UIScrollView()
self.scrollView.delaysContentTouches = false
self.titleView = titleView
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
@ -277,15 +275,19 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
}
}
}
self.pager.isInteractingUpdated = { [weak self] isInteracting in
guard let self else {
return
}
self.titleView?.updateIsInteracting(isInteracting: isInteracting)
}
}
override open func didLoad() {
super.didLoad()
if #available(iOSApplicationExtension 11.0, iOS 11.0, *), !self.isLayerBacked {
self.view.accessibilityIgnoresInvertColors = true
}
self.view.accessibilityIgnoresInvertColors = true
self.view.disablesInteractiveTransitionGestureRecognizer = true
}
@ -312,9 +314,24 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
if self.footerNode.supernode == nil {
self.addSubnode(self.footerNode)
}
if self.headerEdgeEffectView.superview == nil {
self.view.insertSubview(self.headerEdgeEffectView, belowSubview: navigationBar.view)
if let titleView = self.titleView {
if titleView.superview == nil {
self.view.addSubview(titleView)
}
let titleHeight: CGFloat = navigationBarHeight - (layout.statusBarHeight ?? 0.0) - 3.0
let titleSize = CGSize(width: layout.size.width - (layout.safeInsets.left + 44.0 + 16.0 + 8.0) * 2.0, height: titleHeight)
var titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) * 0.5), y: (layout.statusBarHeight ?? 0.0) + 4.0), size: titleSize)
if self.areControlsHidden {
titleFrame.origin.y -= navigationBarHeight
}
let _ = titleView.updateLayout(availableSize: titleFrame.size, transition: .immediate)
transition.updateFrame(view: titleView, frame: titleFrame)
}
/*if self.headerEdgeEffectView.superview == nil {
self.view.insertSubview(self.headerEdgeEffectView, belowSubview: navigationBar.view)
}*/
}
var thumbnailPanelHeight: CGFloat = 0.0
@ -370,7 +387,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
self.statusBar?.updateAlpha(alpha, transition: .animated(duration: 0.3, curve: .easeInOut))
})
self.footerNode.setVisibilityAlpha(alpha, animated: animated)
self.updateThumbnailContainerNodeAlpha(.immediate)
self.updateThumbnailContainerNodeAlpha(animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
if let (navigationBarHeight, layout) = self.containerLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
@ -404,6 +421,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
self.navigationBar?.alpha = 0.0
self.footerNode.alpha = 0.0
self.headerEdgeEffectView.alpha = 0.0
self.titleView?.alpha = 0.0
self.currentThumbnailContainerNode?.alpha = 0.0
self.backgroundNode.layer.animate(from: backgroundColor.withAlphaComponent(0.0).cgColor, to: backgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.15)
@ -420,7 +438,10 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
self.footerNode.alpha = 1.0
self.footerNode.animateIn(transition: .animated(duration: 0.15, curve: .linear))
ComponentTransition.easeInOut(duration: 0.15).setAlpha(view: self.headerEdgeEffectView, alpha: 1.0)
ComponentTransition.easeInOut(duration: 0.15).animateView {
self.headerEdgeEffectView.alpha = 1.0
self.titleView?.alpha = 1.0
}
}
if animateContent {
@ -461,7 +482,10 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
interfaceAnimationCompleted = true
intermediateCompletion()
})
ComponentTransition.easeInOut(duration: 0.1).setAlpha(view: self.headerEdgeEffectView, alpha: 0.0)
ComponentTransition.easeInOut(duration: 0.1).animateView {
self.headerEdgeEffectView.alpha = 0.0
self.titleView?.alpha = 0.0
}
self.footerNode.animateOut(transition: .animated(duration: 0.1, curve: .easeInOut))
@ -522,6 +546,8 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
if let overlayNode = self.overlayNode {
overlayNode.alpha = transition
}
self.headerEdgeEffectView.alpha = transition
self.titleView?.alpha = transition
}
open func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

View file

@ -28,6 +28,16 @@ public final class GalleryControllerInteraction {
}
open class GalleryFooterContentNode: ASDisplayNode {
public struct LayoutInfo {
let height: CGFloat
let needsShadow: Bool
public init(height: CGFloat, needsShadow: Bool) {
self.height = height
self.needsShadow = needsShadow
}
}
public var requestLayout: ((ContainedViewLayoutTransition) -> Void)?
public var controllerInteraction: GalleryControllerInteraction?
@ -37,8 +47,8 @@ open class GalleryFooterContentNode: ASDisplayNode {
self.alpha = alpha
}
open func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
return 0.0
open func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> LayoutInfo {
return LayoutInfo(height: 0.0, needsShadow: false)
}
open func animateIn(transition: ContainedViewLayoutTransition) {

View file

@ -29,10 +29,14 @@ public final class GalleryFooterNode: ASDisplayNode {
}
private var visibilityAlpha: CGFloat = 1.0
private var isEdgeEffectVisible: Bool = false
public func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
self.visibilityAlpha = alpha
let transition: ComponentTransition = animated ? .easeInOut(duration: 0.2) : .immediate
transition.setAlpha(view: self.edgeEffectView, alpha: alpha)
if self.isEdgeEffectVisible {
transition.setAlpha(view: self.edgeEffectView, alpha: alpha)
}
self.currentFooterContentNode?.setVisibilityAlpha(alpha, animated: true)
self.currentOverlayContentNode?.setVisibilityAlpha(alpha)
}
@ -107,32 +111,38 @@ public final class GalleryFooterNode: ASDisplayNode {
if layout.size.width > layout.size.height {
effectiveThumbnailPanelHeight = 0.0
}
var backgroundHeight: CGFloat = 0.0
var backgroundLayoutInfo: GalleryFooterContentNode.LayoutInfo?
let verticalOffset: CGFloat = isHidden ? (layout.size.width > layout.size.height ? 44.0 : (effectiveThumbnailPanelHeight > 0.0 ? 106.0 : 54.0)) : 0.0
let backgroundFrame: CGRect
var edgeEffectTransition = ComponentTransition(transition)
if let footerContentNode = self.currentFooterContentNode {
backgroundHeight = footerContentNode.updateLayout(size: layout.size, metrics: layout.metrics, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, contentInset: effectiveThumbnailPanelHeight, transition: transition)
transition.updateFrame(node: footerContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight)))
var footerTransition = transition
if footerContentNode.bounds.isEmpty {
footerTransition = .immediate
}
let backgroundLayoutInfoValue = footerContentNode.updateLayout(size: layout.size, metrics: layout.metrics, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, contentInset: effectiveThumbnailPanelHeight, transition: footerTransition)
backgroundLayoutInfo = backgroundLayoutInfoValue
footerTransition.updateFrame(node: footerContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundLayoutInfoValue.height + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundLayoutInfoValue.height)))
if let dismissedCurrentFooterContentNode = dismissedCurrentFooterContentNode {
let contentTransition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
footerContentNode.animateIn(fromHeight: dismissedCurrentFooterContentNode.bounds.height, previousContentNode: dismissedCurrentFooterContentNode, transition: contentTransition)
dismissedCurrentFooterContentNode.animateOut(toHeight: backgroundHeight, nextContentNode: footerContentNode, transition: contentTransition, completion: { [weak self, weak dismissedCurrentFooterContentNode] in
dismissedCurrentFooterContentNode.animateOut(toHeight: backgroundLayoutInfoValue.height, nextContentNode: footerContentNode, transition: contentTransition, completion: { [weak self, weak dismissedCurrentFooterContentNode] in
if let strongSelf = self, let dismissedCurrentFooterContentNode = dismissedCurrentFooterContentNode, dismissedCurrentFooterContentNode !== strongSelf.currentFooterContentNode {
dismissedCurrentFooterContentNode.removeFromSupernode()
}
})
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight))
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundLayoutInfoValue.height + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundLayoutInfoValue.height))
edgeEffectTransition = ComponentTransition(contentTransition)
} else {
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight))
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundLayoutInfoValue.height + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundLayoutInfoValue.height))
}
} else {
if let dismissedCurrentFooterContentNode = dismissedCurrentFooterContentNode {
dismissedCurrentFooterContentNode.removeFromSupernode()
}
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight))
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height + verticalOffset), size: CGSize(width: layout.size.width, height: 0.0))
}
self.contentsFrame = backgroundFrame
@ -144,9 +154,20 @@ public final class GalleryFooterNode: ASDisplayNode {
edgeEffectFrame.size.height += edgeEffectOffset
edgeEffectTransition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: .black, alpha: 0.65, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, edgeEffectFrame.height), transition: edgeEffectTransition)
if let backgroundLayoutInfo, backgroundLayoutInfo.needsShadow {
self.isEdgeEffectVisible = true
ComponentTransition(transition).setAlpha(view: self.edgeEffectView, alpha: self.visibilityAlpha)
} else {
self.isEdgeEffectVisible = false
ComponentTransition(transition).setAlpha(view: self.edgeEffectView, alpha: 0.0)
}
let contentTransition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
if let overlayContentNode = self.currentOverlayContentNode {
var backgroundHeight: CGFloat = 0.0
if let backgroundLayoutInfo {
backgroundHeight = backgroundLayoutInfo.height
}
let insets = UIEdgeInsets(top: navigationBarHeight, left: layout.safeInsets.left, bottom: isHidden ? layout.intrinsicInsets.bottom : backgroundHeight, right: layout.safeInsets.right)
overlayContentNode.updateLayout(size: layout.size, metrics: layout.metrics, insets: insets, isHidden: isHidden, transition: transition)
transition.updateFrame(node: overlayContentNode, frame: CGRect(origin: CGPoint(), size: layout.size))

View file

@ -52,7 +52,7 @@ open class GalleryItemNode: ASDisplayNode {
return .single("")
}
open func titleView() -> Signal<UIView?, NoError> {
open func titleContent() -> Signal<GalleryTitleView.Content?, NoError> {
return .single(nil)
}

View file

@ -121,6 +121,7 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
public var completeCustomDismiss: (Bool) -> Void = { _ in }
public var baseNavigationController: () -> NavigationController? = { return nil }
public var galleryController: () -> ViewController? = { return nil }
public var isInteractingUpdated: ((Bool) -> Void)?
private var pagingEnabled = true
public var pagingEnabledPromise = Promise<Bool>(true)
@ -771,15 +772,18 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
self.ensureItemsLoaded(force: false)
self.isInteractingUpdated?(false)
}
}
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.ensureItemsLoaded(force: true)
self.isInteractingUpdated?(true)
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.ensureItemsLoaded(force: true)
self.isInteractingUpdated?(false)
}
private func shouldLoadItems(force: Bool) -> Bool {

View file

@ -4,6 +4,7 @@ import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import AccountContext
import TelegramPresentationData
import TelegramStringFormatting
import GlassBackgroundComponent
@ -14,7 +15,37 @@ import MultilineTextComponent
private let titleFont = Font.semibold(17.0)
private let dateFont = Font.regular(12.0)
final class GalleryTitleView: UIView, NavigationBarTitleView {
public final class GalleryTitleView: UIView, NavigationBarTitleView {
public final class Content: Equatable {
let message: EngineMessage
let title: String?
let action: (() -> Void)?
public init(message: EngineMessage, title: String?, action: (() -> Void)?) {
self.message = message
self.title = title
self.action = action
}
public static func ==(lhs: Content, rhs: Content) -> Bool {
if lhs.message != rhs.message {
return false
}
if lhs.title != rhs.title {
return false
}
if (lhs.action == nil) != (rhs.action == nil) {
return false
}
return true
}
}
private let context: AccountContext
private let presentationData: PresentationData
private let backgroundContainer: GlassBackgroundContainerView
private let backgroundView: GlassBackgroundView
private let authorNameNode: ASTextNode
private let dateNode: ASTextNode
@ -22,16 +53,28 @@ final class GalleryTitleView: UIView, NavigationBarTitleView {
private let titleBackground: GlassBackgroundView
private let title = ComponentView<Empty>()
private var content: Content?
private var titleString: String?
var requestUpdate: ((ContainedViewLayoutTransition) -> Void)?
public var requestUpdate: ((ContainedViewLayoutTransition) -> Void)?
override init(frame: CGRect) {
private var interactionTimer: Foundation.Timer?
public init(context: AccountContext, presentationData: PresentationData) {
self.context = context
self.presentationData = presentationData
self.backgroundContainer = GlassBackgroundContainerView()
self.backgroundView = GlassBackgroundView()
self.backgroundContainer.contentView.addSubview(self.backgroundView)
self.authorNameNode = ASTextNode()
self.authorNameNode.isUserInteractionEnabled = false
self.authorNameNode.displaysAsynchronously = false
self.authorNameNode.maximumNumberOfLines = 1
self.dateNode = ASTextNode()
self.dateNode.isUserInteractionEnabled = false
self.dateNode.displaysAsynchronously = false
self.dateNode.maximumNumberOfLines = 1
@ -40,45 +83,90 @@ final class GalleryTitleView: UIView, NavigationBarTitleView {
self.titleBackgroundContainer.contentView.addSubview(self.titleBackground)
super.init(frame: frame)
super.init(frame: CGRect())
self.addSubnode(self.authorNameNode)
self.addSubnode(self.dateNode)
self.addSubview(self.backgroundContainer)
self.backgroundView.contentView.addSubview(self.authorNameNode.view)
self.backgroundView.contentView.addSubview(self.dateNode.view)
self.backgroundContainer.isHidden = true
self.addSubview(self.titleBackgroundContainer)
self.titleBackgroundContainer.alpha = 0.0
self.backgroundView.contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
}
required init?(coder: NSCoder) {
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setMessage(_ message: Message, presentationData: PresentationData, accountPeerId: PeerId, title: String?) {
let authorNameText = stringForFullAuthorName(message: EngineMessage(message), strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: accountPeerId).first ?? ""
let dateText = humanReadableStringForTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, timestamp: message.timestamp).string
self.authorNameNode.attributedText = NSAttributedString(string: authorNameText, font: titleFont, textColor: .white)
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: UIColor(white: 1.0, alpha: 0.5))
self.titleString = title
deinit {
self.interactionTimer?.invalidate()
}
func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.content?.action?()
}
}
public func setContent(content: Content?) {
self.content = content
self.backgroundContainer.isHidden = self.content == nil
if let content {
let authorNameText = stringForFullAuthorName(message: content.message, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, accountPeerId: self.context.account.peerId).first ?? ""
let dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: content.message.timestamp).string
self.authorNameNode.attributedText = NSAttributedString(string: authorNameText, font: titleFont, textColor: .white)
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: UIColor(white: 1.0, alpha: 0.5))
}
self.titleString = content?.title
if !self.bounds.isEmpty {
let _ = self.updateLayout(availableSize: self.bounds.size, transition: .immediate)
}
}
public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
let size = availableSize
let leftInset: CGFloat = 0.0
let rightInset: CGFloat = 0.0
let leftInset: CGFloat = 8.0 + 14.0
let rightInset: CGFloat = 8.0 + 14.0
let authorNameSize = self.authorNameNode.measure(CGSize(width: max(1.0, size.width - 8.0 * 2.0 - leftInset - rightInset), height: CGFloat.greatestFiniteMagnitude))
let dateSize = self.dateNode.measure(CGSize(width: max(1.0, size.width - 8.0 * 2.0), height: CGFloat.greatestFiniteMagnitude))
var backgroundSize = CGSize(width: 0.0, height: 44.0)
if authorNameSize.height.isZero {
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((size.width - dateSize.width) / 2.0), y: floor((size.height - dateSize.height) / 2.0)), size: dateSize)
backgroundSize.width = dateSize.width
} else {
backgroundSize.width = max(authorNameSize.width, dateSize.width)
}
backgroundSize.width = max(150.0, backgroundSize.width)
backgroundSize.width += 14.0 * 2.0
if authorNameSize.height.isZero {
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((backgroundSize.width - dateSize.width) / 2.0), y: floor((backgroundSize.height - dateSize.height) / 2.0)), size: dateSize)
} else {
let labelsSpacing: CGFloat = 2.0
self.authorNameNode.frame = CGRect(origin: CGPoint(x: floor((size.width - authorNameSize.width) / 2.0), y: floor((size.height - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0)), size: authorNameSize)
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)
self.authorNameNode.frame = CGRect(origin: CGPoint(x: floor((backgroundSize.width - authorNameSize.width) / 2.0), y: floor((backgroundSize.height - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0)), size: authorNameSize)
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((backgroundSize.width - dateSize.width) / 2.0), y: floor((backgroundSize.height - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize)
}
let backgroundFrame = CGRect(origin: CGPoint(x: floor((size.width - backgroundSize.width) * 0.5), y: floor((size.height - backgroundSize.height) * 0.5)), size: backgroundSize)
self.backgroundContainer.update(size: backgroundFrame.size, isDark: true, transition: ComponentTransition(transition))
ComponentTransition(transition).setFrame(view: self.backgroundContainer, frame: backgroundFrame)
ComponentTransition(transition).setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
self.backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: true, tintColor: .init(kind: .panel), isInteractive: self.content?.action != nil, transition: ComponentTransition(transition))
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
@ -112,7 +200,27 @@ final class GalleryTitleView: UIView, NavigationBarTitleView {
return availableSize
}
func animateLayoutTransition() {
public func animateLayoutTransition() {
}
public func updateIsInteracting(isInteracting: Bool) {
if isInteracting {
self.interactionTimer?.invalidate()
self.interactionTimer = nil
ComponentTransition.easeInOut(duration: 0.2).animateView {
self.titleBackgroundContainer.alpha = 1.0
}
} else {
self.interactionTimer?.invalidate()
self.interactionTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false, block: { [weak self] _ in
guard let self else {
return
}
ComponentTransition.easeInOut(duration: 0.2).animateView {
self.titleBackgroundContainer.alpha = 0.0
}
})
}
}
}

View file

@ -149,7 +149,7 @@ class ChatImageGalleryItem: GalleryItem {
func node(synchronous: Bool) -> GalleryItemNode {
let node = ChatImageGalleryItemNode(context: self.context, presentationData: self.presentationData, performAction: self.performAction, openActionOptions: self.openActionOptions, present: self.present)
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected, isSecret: self.isSecret)
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected, isSecret: self.isSecret, location: self.location)
for media in self.message.media {
if let paidContent = media as? TelegramMediaPaidContent {
let mediaIndex = self.mediaIndex ?? 0
@ -175,26 +175,12 @@ class ChatImageGalleryItem: GalleryItem {
}
}
var title: String?
if let _ = message.adAttribute {
title = self.presentationData.strings.Gallery_Ad
} else if let location = self.location {
title = self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").string
}
node.titleContentView?.setMessage(self.message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId, title: title)
return node
}
func updateNode(node: GalleryItemNode, synchronous: Bool) {
if let node = node as? ChatImageGalleryItemNode, let location = self.location {
let title = self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").string
if self.displayInfoOnTop {
node.titleContentView?.setMessage(self.message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId, title: title)
}
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected, isSecret: self.isSecret)
node.setMessage(self.message, displayInfo: !self.displayInfoOnTop, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected, isSecret: self.isSecret, location: location)
}
}
@ -251,12 +237,11 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
private var tilingNode: TilingNode?
fileprivate let _ready = Promise<Void>()
fileprivate let _title = Promise<String>()
fileprivate let _titleView = Promise<UIView?>()
fileprivate let _titleContent = Promise<GalleryTitleView.Content?>(nil)
fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>(nil)
private let statusNodeContainer: HighlightableButtonNode
private let statusNode: RadialStatusNode
private let footerContentNode: ChatItemGalleryFooterContentNode
fileprivate var titleContentView: GalleryTitleView?
private var contextAndMedia: (AccountContext, AnyMediaReference)?
@ -320,9 +305,6 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.statusNodeContainer.isUserInteractionEnabled = false
self.titleContentView = GalleryTitleView(frame: CGRect())
self._titleView.set(.single(self.titleContentView))
self.recognitionOverlayContentNode.action = { [weak self] active in
if let strongSelf = self {
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
@ -459,7 +441,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
}))
}
fileprivate func setMessage(_ message: Message, displayInfo: Bool, translateToLanguage: String?, peerIsCopyProtected: Bool, isSecret: Bool) {
fileprivate func setMessage(_ message: Message, displayInfo: Bool, translateToLanguage: String?, peerIsCopyProtected: Bool, isSecret: Bool, location: MessageHistoryEntryLocation?) {
self.message = message
self.displayInfo = displayInfo
self.translateToLanguage = translateToLanguage
@ -467,6 +449,23 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.isSecret = isSecret
self.imageNode.captureProtected = message.id.peerId.namespace == Namespaces.Peer.SecretChat || message.isCopyProtected() || peerIsCopyProtected || isSecret || message.paidContent != nil
self.updateFooter(animated: false)
var title: String?
if let _ = message.adAttribute {
title = self.presentationData.strings.Gallery_Ad
} else if let location {
title = self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").string
}
self._titleContent.set(.single(GalleryTitleView.Content(message: EngineMessage(message), title: title, action: message.adAttribute == nil ? { [weak self] in
guard let self else {
return
}
guard let controller = self.galleryController() as? GalleryController else {
return
}
controller.dismissAndNavigateToMessageContext(message: message)
} : nil)))
}
private func updateFooter(animated: Bool) {
@ -1133,8 +1132,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
return self._title.get()
}
override func titleView() -> Signal<UIView?, NoError> {
return self._titleView.get()
override func titleContent() -> Signal<GalleryTitleView.Content?, NoError> {
return self._titleContent.get()
}
override func rightBarButtonItems() -> Signal<[UIBarButtonItem]?, NoError> {

View file

@ -101,34 +101,14 @@ public class UniversalVideoGalleryItem: GalleryItem {
public func node(synchronous: Bool) -> GalleryItemNode {
let node = UniversalVideoGalleryItemNode(context: self.context, presentationData: self.presentationData, performAction: self.performAction, openActionOptions: self.openActionOptions, present: self.present)
var title: String?
if let indexData = self.indexData {
title = self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string
} else if case let .message(message, _) = self.contentInfo, let _ = message.adAttribute {
title = self.presentationData.strings.Gallery_Ad
}
node.setupItem(self)
if case let .message(message, _) = self.contentInfo {
node.titleContentView?.setMessage(message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId, title: title)
}
return node
}
public func updateNode(node: GalleryItemNode, synchronous: Bool) {
if let node = node as? UniversalVideoGalleryItemNode {
var title: String?
if let indexData = self.indexData {
title = self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string
}
node.setupItem(self)
if self.displayInfoOnTop, case let .message(message, _) = self.contentInfo {
node.titleContentView?.setMessage(message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId, title: title)
}
}
}
@ -881,7 +861,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
fileprivate let _ready = Promise<Void>()
fileprivate let _title = Promise<String>()
fileprivate let _titleView = Promise<UIView?>()
fileprivate let _titleContent = Promise<GalleryTitleView.Content?>(nil)
fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>()
fileprivate var titleContentView: GalleryTitleView?
@ -1136,9 +1116,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.openMoreMenu(sourceView: self.moreBarButton.referenceNode.view, gesture: gesture, adMessage: adMessage, isSettings: false)
}
self.titleContentView = GalleryTitleView(frame: CGRect())
self._titleView.set(.single(self.titleContentView))
let shouldHideControlsSignal: Signal<Void, NoError> = combineLatest(self.isPlayingPromise.get(), self.isInteractingPromise.get(), self.controlsVisiblePromise.get(), self.isShowingContextMenuPromise.get(), self.isShowingSettingsMenuPromise.get(), self.isShowingAdMenuPromise.get(), self.hasExpandedCaptionPromise.get())
|> mapToSignal { isPlaying, isInteracting, controlsVisible, isShowingContextMenu, isShowingSettingsMenu, isShowingAdMenu, hasExpandedCaptionPromise -> Signal<Void, NoError> in
if isShowingContextMenu || isShowingSettingsMenu || isShowingAdMenu || hasExpandedCaptionPromise {
@ -1962,6 +1939,25 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
self.overlayContentNode.setMessage(context: item.context, message: message)
}
var title: String?
if let indexData = item.indexData {
title = self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string
} else if case let .message(message, _) = item.contentInfo, let _ = message.adAttribute {
title = self.presentationData.strings.Gallery_Ad
}
if case let .message(message, _) = item.contentInfo {
self._titleContent.set(.single(GalleryTitleView.Content(message: EngineMessage(message), title: title, action: message.adAttribute == nil ? { [weak self] in
guard let self else {
return
}
guard let controller = self.galleryController() as? GalleryController else {
return
}
controller.dismissAndNavigateToMessageContext(message: message)
} : nil)))
}
}
override func controlsVisibilityUpdated(isVisible: Bool) {
@ -2759,8 +2755,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
return self._title.get()
}
override func titleView() -> Signal<UIView?, NoError> {
return self._titleView.get()
override func titleContent() -> Signal<GalleryTitleView.Content?, NoError> {
return self._titleContent.get()
}
override func rightBarButtonItems() -> Signal<[UIBarButtonItem]?, NoError> {

View file

@ -256,7 +256,7 @@ public final class SecretMediaPreviewController: ViewController {
}, currentItemNode: { [weak self] in
return self?.controllerNode.pager.centralItemNode()
})
self.displayNode = SecretMediaPreviewControllerNode(context: self.context, controllerInteraction: controllerInteraction)
self.displayNode = SecretMediaPreviewControllerNode(context: self.context, controllerInteraction: controllerInteraction, titleView: nil)
self.displayNodeDidLoad()
self.controllerNode.statusPressed = { [weak self] _ in

View file

@ -32,7 +32,7 @@ final class SecretMediaPreviewFooterContentNode: GalleryFooterContentNode {
}
}
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> LayoutInfo {
let width = size.width
let panelHeight: CGFloat = 44.0 + bottomInset
@ -40,7 +40,7 @@ final class SecretMediaPreviewFooterContentNode: GalleryFooterContentNode {
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: floor((44.0 - textSize.height) / 2.0)), size: textSize))
return panelHeight
return LayoutInfo(height: panelHeight, needsShadow: false)
}
}

View file

@ -379,7 +379,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
}, currentItemNode: { [weak self] in
return self?.galleryNode.pager.centralItemNode()
})
self.displayNode = GalleryControllerNode(context: self.context,controllerInteraction: controllerInteraction)
self.displayNode = GalleryControllerNode(context: self.context,controllerInteraction: controllerInteraction, titleView: nil)
self.displayNodeDidLoad()
self.galleryNode.statusBar = self.statusBar
@ -417,7 +417,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
if let node = strongSelf.galleryNode.pager.centralItemNode() {
strongSelf.centralItemTitle.set(node.title())
strongSelf.centralItemTitleView.set(node.titleView())
//strongSelf.centralItemTitleView.set(node.titleView())
strongSelf.centralItemRightBarButtonItem.set(node.rightBarButtonItem())
strongSelf.centralItemRightBarButtonItems.set(node.rightBarButtonItems())
strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
@ -448,7 +448,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? InstantPageGalleryControllerPresentationArguments {
self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView())
//self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemRightBarButtonItem.set(centralItemNode.rightBarButtonItem())
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())

View file

@ -106,7 +106,7 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode {
self.actionButton.isHidden = shareMedia == nil
}
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> LayoutInfo {
let width = size.width
var panelHeight: CGFloat = 44.0 + bottomInset + contentInset
if !self.textNode.isHidden {
@ -126,7 +126,7 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode {
self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
return panelHeight
return LayoutInfo(height: panelHeight, needsShadow: false)
}
override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {

View file

@ -477,6 +477,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
let header: ListViewItemHeader?
let shimmering: ItemListPeerItemShimmering?
let displayDecorations: Bool
let displayBackground: Bool
let disableInteractiveTransitionIfNecessary: Bool
let storyStats: PeerStoryStats?
let openStories: ((UIView) -> Void)?
@ -520,6 +521,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
header: ListViewItemHeader? = nil,
shimmering: ItemListPeerItemShimmering? = nil,
displayDecorations: Bool = true,
displayBackground: Bool = true,
disableInteractiveTransitionIfNecessary: Bool = false,
storyStats: PeerStoryStats? = nil,
openStories: ((UIView) -> Void)? = nil
@ -562,6 +564,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
self.header = header
self.shimmering = shimmering
self.displayDecorations = displayDecorations
self.displayBackground = displayBackground
self.disableInteractiveTransitionIfNecessary = disableInteractiveTransitionIfNecessary
self.storyStats = storyStats
self.openStories = openStories
@ -606,6 +609,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
header: ListViewItemHeader? = nil,
shimmering: ItemListPeerItemShimmering? = nil,
displayDecorations: Bool = true,
displayBackground: Bool = true,
disableInteractiveTransitionIfNecessary: Bool = false,
storyStats: PeerStoryStats? = nil,
openStories: ((UIView) -> Void)? = nil
@ -648,6 +652,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
self.header = header
self.shimmering = shimmering
self.displayDecorations = displayDecorations
self.displayBackground = displayBackground
self.disableInteractiveTransitionIfNecessary = disableInteractiveTransitionIfNecessary
self.storyStats = storyStats
self.openStories = openStories
@ -1337,6 +1342,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
strongSelf.backgroundNode.isHidden = !item.displayBackground
let revealOffset = strongSelf.revealOffset
let transition: ContainedViewLayoutTransition

View file

@ -189,7 +189,7 @@ class SecureIdDocumentGalleryController: ViewController, StandalonePresentableCo
}, currentItemNode: { [weak self] in
return self?.galleryNode.pager.centralItemNode()
})
self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction)
self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction, titleView: nil)
self.displayNodeDidLoad()
self.galleryNode.statusBar = self.statusBar
@ -218,7 +218,7 @@ class SecureIdDocumentGalleryController: ViewController, StandalonePresentableCo
if let node = strongSelf.galleryNode.pager.centralItemNode() {
strongSelf.centralItemTitle.set(node.title())
strongSelf.centralItemTitleView.set(node.titleView())
//strongSelf.centralItemTitleView.set(node.titleView())
strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
strongSelf.centralItemFooterContentNode.set(node.footerContent())
}
@ -237,7 +237,7 @@ class SecureIdDocumentGalleryController: ViewController, StandalonePresentableCo
if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? SecureIdDocumentGalleryControllerPresentationArguments {
self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView())
//self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
self.centralItemFooterContentNode.set(centralItemNode.footerContent())

View file

@ -91,7 +91,7 @@ final class SecureIdDocumentGalleryFooterContentNode: GalleryFooterContentNode {
}
}
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> LayoutInfo {
let width = size.width
var panelHeight: CGFloat = 44.0 + bottomInset
panelHeight += contentInset
@ -117,7 +117,7 @@ final class SecureIdDocumentGalleryFooterContentNode: GalleryFooterContentNode {
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize)
}
return panelHeight
return LayoutInfo(height: panelHeight, needsShadow: false)
}
override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {

View file

@ -412,7 +412,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
private var centralEntryIndex: Int?
private let centralItemTitle = Promise<String>()
private let centralItemTitleView = Promise<UIView?>()
private let centralItemTitleContent = Promise<GalleryTitleView.Content?>()
private let centralItemRightBarButtonItems = Promise<[UIBarButtonItem]?>(nil)
private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>()
private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>()
@ -427,6 +427,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
return self._hiddenMedia.get()
}
private let titleView: GalleryTitleView
private let replaceRootController: (ViewController, Promise<Bool>?) -> Void
private let editDisposable = MetaDisposable ()
@ -441,11 +443,15 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self.centralEntryIndex = centralEntryIndex
self.titleView = GalleryTitleView(context: context, presentationData: self.presentationData)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
let backItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.donePressed))
self.navigationItem.leftBarButtonItem = backItem
self.navigationItem.titleView = self.titleView
self.statusBar.statusBarStyle = .White
let remoteEntriesSignal: Signal<[AvatarGalleryEntry], NoError>
@ -546,8 +552,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
}
}))
self.centralItemAttributesDisposable.add(self.centralItemTitleView.get().start(next: { [weak self] titleView in
self?.navigationItem.titleView = titleView
self.centralItemAttributesDisposable.add(self.centralItemTitleContent.get().start(next: { [weak self] titleContent in
self?.titleView.setContent(content: titleContent)
}))
self.centralItemAttributesDisposable.add(self.centralItemRightBarButtonItems.get().start(next: { [weak self] rightBarButtonItems in
@ -633,7 +639,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
}, currentItemNode: { [weak self] in
return self?.galleryNode.pager.centralItemNode()
})
self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction)
self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction, titleView: self.titleView)
self.displayNodeDidLoad()
self.galleryNode.pager.updateOnReplacement = true
@ -680,7 +686,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
if let node = strongSelf.galleryNode.pager.centralItemNode() {
strongSelf.centralItemTitle.set(node.title())
strongSelf.centralItemTitleView.set(node.titleView())
strongSelf.centralItemTitleContent.set(node.titleContent())
strongSelf.centralItemRightBarButtonItems.set(node.rightBarButtonItems())
strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
strongSelf.centralItemFooterContentNode.set(node.footerContent())
@ -709,7 +715,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? AvatarGalleryControllerPresentationArguments {
self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemTitleContent.set(centralItemNode.titleContent())
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
self.centralItemFooterContentNode.set(centralItemNode.footerContent())

View file

@ -175,7 +175,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
}
}
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> LayoutInfo {
self.validLayout = (size, metrics, leftInset, rightInset, bottomInset, contentInset)
let width = size.width
@ -203,7 +203,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
let mainButtonSize = self.setMainButton.measure(constrainedSize)
self.setMainButton.frame = CGRect(origin: CGPoint(x: floor((width - mainButtonSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - mainButtonSize.height) / 2.0)), size: mainButtonSize)
return panelHeight
return LayoutInfo(height: panelHeight, needsShadow: false)
}
override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {

View file

@ -89,7 +89,7 @@ private enum ArchiveSettingsControllerEntry: ItemListNodeEntry {
case .unmutedHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_UnmutedChatsHeader, sectionId: self.section)
case let .unmutedValue(value):
return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateUnmuted(value)
})
case .unmutedFooter:
@ -97,7 +97,7 @@ private enum ArchiveSettingsControllerEntry: ItemListNodeEntry {
case .foldersHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_FolderChatsHeader, sectionId: self.section)
case let .foldersValue(value):
return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.ArchiveSettings_KeepArchived, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateFolders(value)
})
case .foldersFooter:
@ -105,7 +105,7 @@ private enum ArchiveSettingsControllerEntry: ItemListNodeEntry {
case .unknownHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ArchiveSettings_UnknownChatsHeader, sectionId: self.section)
case let .unknownValue(isOn, isLocked):
return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ArchiveSettings_AutomaticallyArchive, value: isOn, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { value in
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.ArchiveSettings_AutomaticallyArchive, value: isOn, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateUnknown(value)
}, activatedWhileDisabled: {
arguments.updateUnknown(nil)

View file

@ -911,15 +911,15 @@ public final class SparseItemGrid: ASDisplayNode {
return self.scrollView.convert(layer.frame, to: self.view)
}
func scrollToItem(at index: Int) {
func scrollToItem(at index: Int, force: Bool) {
guard let layout = self.layout, let _ = self.items else {
return
}
if layout.containerLayout.lockScrollingAtTop {
if !force && layout.containerLayout.lockScrollingAtTop {
return
}
let itemFrame = layout.frame(at: index)
var contentOffset = itemFrame.minY
var contentOffset = floor(itemFrame.midY - self.scrollView.bounds.height / 2.0)
if contentOffset > self.scrollView.contentSize.height - self.scrollView.bounds.height {
contentOffset = self.scrollView.contentSize.height - self.scrollView.bounds.height
}
@ -2234,11 +2234,11 @@ public final class SparseItemGrid: ASDisplayNode {
return currentViewport.visualItem(at: index)
}
public func scrollToItem(at index: Int) {
public func scrollToItem(at index: Int, force: Bool = false) {
guard let currentViewport = self.currentViewport else {
return
}
currentViewport.scrollToItem(at: index)
currentViewport.scrollToItem(at: index, force: force)
}
public func ensureItemVisible(index: Int, anyAmount: Bool = true) {

View file

@ -538,25 +538,26 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[736157604] = { return Api.JSONValue.parse_jsonNumber($0) }
dict[-1715350371] = { return Api.JSONValue.parse_jsonObject($0) }
dict[-1222740358] = { return Api.JSONValue.parse_jsonString($0) }
dict[-916050683] = { return Api.KeyboardButton.parse_inputKeyboardButtonRequestPeer($0) }
dict[-802258988] = { return Api.KeyboardButton.parse_inputKeyboardButtonUrlAuth($0) }
dict[-376962181] = { return Api.KeyboardButton.parse_inputKeyboardButtonUserProfile($0) }
dict[-1560655744] = { return Api.KeyboardButton.parse_keyboardButton($0) }
dict[-1344716869] = { return Api.KeyboardButton.parse_keyboardButtonBuy($0) }
dict[901503851] = { return Api.KeyboardButton.parse_keyboardButtonCallback($0) }
dict[1976723854] = { return Api.KeyboardButton.parse_keyboardButtonCopy($0) }
dict[1358175439] = { return Api.KeyboardButton.parse_keyboardButtonGame($0) }
dict[-59151553] = { return Api.KeyboardButton.parse_keyboardButtonRequestGeoLocation($0) }
dict[1406648280] = { return Api.KeyboardButton.parse_keyboardButtonRequestPeer($0) }
dict[-1318425559] = { return Api.KeyboardButton.parse_keyboardButtonRequestPhone($0) }
dict[-1144565411] = { return Api.KeyboardButton.parse_keyboardButtonRequestPoll($0) }
dict[-1598009252] = { return Api.KeyboardButton.parse_keyboardButtonSimpleWebView($0) }
dict[-1816527947] = { return Api.KeyboardButton.parse_keyboardButtonSwitchInline($0) }
dict[629866245] = { return Api.KeyboardButton.parse_keyboardButtonUrl($0) }
dict[280464681] = { return Api.KeyboardButton.parse_keyboardButtonUrlAuth($0) }
dict[814112961] = { return Api.KeyboardButton.parse_keyboardButtonUserProfile($0) }
dict[326529584] = { return Api.KeyboardButton.parse_keyboardButtonWebView($0) }
dict[45580630] = { return Api.KeyboardButton.parse_inputKeyboardButtonRequestPeer($0) }
dict[1744911986] = { return Api.KeyboardButton.parse_inputKeyboardButtonUrlAuth($0) }
dict[2103314375] = { return Api.KeyboardButton.parse_inputKeyboardButtonUserProfile($0) }
dict[2098662655] = { return Api.KeyboardButton.parse_keyboardButton($0) }
dict[1067792645] = { return Api.KeyboardButton.parse_keyboardButtonBuy($0) }
dict[-433338016] = { return Api.KeyboardButton.parse_keyboardButtonCallback($0) }
dict[-1127960816] = { return Api.KeyboardButton.parse_keyboardButtonCopy($0) }
dict[-1983540999] = { return Api.KeyboardButton.parse_keyboardButtonGame($0) }
dict[-1438582451] = { return Api.KeyboardButton.parse_keyboardButtonRequestGeoLocation($0) }
dict[1527715317] = { return Api.KeyboardButton.parse_keyboardButtonRequestPeer($0) }
dict[1098841487] = { return Api.KeyboardButton.parse_keyboardButtonRequestPhone($0) }
dict[2047989634] = { return Api.KeyboardButton.parse_keyboardButtonRequestPoll($0) }
dict[-514047120] = { return Api.KeyboardButton.parse_keyboardButtonSimpleWebView($0) }
dict[-1726768644] = { return Api.KeyboardButton.parse_keyboardButtonSwitchInline($0) }
dict[-670292500] = { return Api.KeyboardButton.parse_keyboardButtonUrl($0) }
dict[-183499015] = { return Api.KeyboardButton.parse_keyboardButtonUrlAuth($0) }
dict[-1057137399] = { return Api.KeyboardButton.parse_keyboardButtonUserProfile($0) }
dict[-398020192] = { return Api.KeyboardButton.parse_keyboardButtonWebView($0) }
dict[2002815875] = { return Api.KeyboardButtonRow.parse_keyboardButtonRow($0) }
dict[1339896880] = { return Api.KeyboardButtonStyle.parse_keyboardButtonStyle($0) }
dict[-886477832] = { return Api.LabeledPrice.parse_labeledPrice($0) }
dict[-209337866] = { return Api.LangPackDifference.parse_langPackDifference($0) }
dict[-288727837] = { return Api.LangPackLanguage.parse_langPackLanguage($0) }
@ -579,6 +580,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[2055212554] = { return Api.Message.parse_messageService($0) }
dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) }
dict[-988359047] = { return Api.MessageAction.parse_messageActionBotAllowed($0) }
dict[-511160261] = { return Api.MessageAction.parse_messageActionChangeCreator($0) }
dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) }
dict[-365344535] = { return Api.MessageAction.parse_messageActionChannelMigrateFrom($0) }
dict[365886720] = { return Api.MessageAction.parse_messageActionChatAddUser($0) }
@ -606,6 +608,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1281329567] = { return Api.MessageAction.parse_messageActionGroupCallScheduled($0) }
dict[-1615153660] = { return Api.MessageAction.parse_messageActionHistoryClear($0) }
dict[1345295095] = { return Api.MessageAction.parse_messageActionInviteToGroupCall($0) }
dict[-1333866363] = { return Api.MessageAction.parse_messageActionNewCreatorPending($0) }
dict[-2068281992] = { return Api.MessageAction.parse_messageActionPaidMessagesPrice($0) }
dict[-1407246387] = { return Api.MessageAction.parse_messageActionPaidMessagesRefunded($0) }
dict[1102307842] = { return Api.MessageAction.parse_messageActionPaymentRefunded($0) }
@ -2016,6 +2019,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.KeyboardButtonRow:
_1.serialize(buffer, boxed)
case let _1 as Api.KeyboardButtonStyle:
_1.serialize(buffer, boxed)
case let _1 as Api.LabeledPrice:
_1.serialize(buffer, boxed)
case let _1 as Api.LangPackDifference:

File diff suppressed because it is too large Load diff

View file

@ -45,6 +45,57 @@ public extension Api {
}
}
}
public extension Api {
enum KeyboardButtonStyle: TypeConstructorDescription {
public class Cons_keyboardButtonStyle {
public var flags: Int32
public var icon: Int64?
public init(flags: Int32, icon: Int64?) {
self.flags = flags
self.icon = icon
}
}
case keyboardButtonStyle(Cons_keyboardButtonStyle)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .keyboardButtonStyle(let _data):
if boxed {
buffer.appendInt32(1339896880)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
if Int(_data.flags) & Int(1 << 3) != 0 {
serializeInt64(_data.icon!, buffer: buffer, boxed: false)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .keyboardButtonStyle(let _data):
return ("keyboardButtonStyle", [("flags", _data.flags as Any), ("icon", _data.icon as Any)])
}
}
public static func parse_keyboardButtonStyle(_ reader: BufferReader) -> KeyboardButtonStyle? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
if Int(_1!) & Int(1 << 3) != 0 {
_2 = reader.readInt64()
}
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil
if _c1 && _c2 {
return Api.KeyboardButtonStyle.keyboardButtonStyle(Cons_keyboardButtonStyle(flags: _1!, icon: _2))
}
else {
return nil
}
}
}
}
public extension Api {
enum LabeledPrice: TypeConstructorDescription {
public class Cons_labeledPrice {
@ -1513,6 +1564,12 @@ public extension Api {
self.app = app
}
}
public class Cons_messageActionChangeCreator {
public var newCreatorId: Int64
public init(newCreatorId: Int64) {
self.newCreatorId = newCreatorId
}
}
public class Cons_messageActionChannelCreate {
public var title: String
public init(title: String) {
@ -1725,6 +1782,12 @@ public extension Api {
self.users = users
}
}
public class Cons_messageActionNewCreatorPending {
public var newCreatorId: Int64
public init(newCreatorId: Int64) {
self.newCreatorId = newCreatorId
}
}
public class Cons_messageActionPaidMessagesPrice {
public var flags: Int32
public var stars: Int64
@ -2043,6 +2106,7 @@ public extension Api {
}
case messageActionBoostApply(Cons_messageActionBoostApply)
case messageActionBotAllowed(Cons_messageActionBotAllowed)
case messageActionChangeCreator(Cons_messageActionChangeCreator)
case messageActionChannelCreate(Cons_messageActionChannelCreate)
case messageActionChannelMigrateFrom(Cons_messageActionChannelMigrateFrom)
case messageActionChatAddUser(Cons_messageActionChatAddUser)
@ -2070,6 +2134,7 @@ public extension Api {
case messageActionGroupCallScheduled(Cons_messageActionGroupCallScheduled)
case messageActionHistoryClear
case messageActionInviteToGroupCall(Cons_messageActionInviteToGroupCall)
case messageActionNewCreatorPending(Cons_messageActionNewCreatorPending)
case messageActionPaidMessagesPrice(Cons_messageActionPaidMessagesPrice)
case messageActionPaidMessagesRefunded(Cons_messageActionPaidMessagesRefunded)
case messageActionPaymentRefunded(Cons_messageActionPaymentRefunded)
@ -2122,6 +2187,12 @@ public extension Api {
_data.app!.serialize(buffer, true)
}
break
case .messageActionChangeCreator(let _data):
if boxed {
buffer.appendInt32(-511160261)
}
serializeInt64(_data.newCreatorId, buffer: buffer, boxed: false)
break
case .messageActionChannelCreate(let _data):
if boxed {
buffer.appendInt32(-1781355374)
@ -2369,6 +2440,12 @@ public extension Api {
serializeInt64(item, buffer: buffer, boxed: false)
}
break
case .messageActionNewCreatorPending(let _data):
if boxed {
buffer.appendInt32(-1333866363)
}
serializeInt64(_data.newCreatorId, buffer: buffer, boxed: false)
break
case .messageActionPaidMessagesPrice(let _data):
if boxed {
buffer.appendInt32(-2068281992)
@ -2737,6 +2814,8 @@ public extension Api {
return ("messageActionBoostApply", [("boosts", _data.boosts as Any)])
case .messageActionBotAllowed(let _data):
return ("messageActionBotAllowed", [("flags", _data.flags as Any), ("domain", _data.domain as Any), ("app", _data.app as Any)])
case .messageActionChangeCreator(let _data):
return ("messageActionChangeCreator", [("newCreatorId", _data.newCreatorId as Any)])
case .messageActionChannelCreate(let _data):
return ("messageActionChannelCreate", [("title", _data.title as Any)])
case .messageActionChannelMigrateFrom(let _data):
@ -2791,6 +2870,8 @@ public extension Api {
return ("messageActionHistoryClear", [])
case .messageActionInviteToGroupCall(let _data):
return ("messageActionInviteToGroupCall", [("call", _data.call as Any), ("users", _data.users as Any)])
case .messageActionNewCreatorPending(let _data):
return ("messageActionNewCreatorPending", [("newCreatorId", _data.newCreatorId as Any)])
case .messageActionPaidMessagesPrice(let _data):
return ("messageActionPaidMessagesPrice", [("flags", _data.flags as Any), ("stars", _data.stars as Any)])
case .messageActionPaidMessagesRefunded(let _data):
@ -2890,6 +2971,17 @@ public extension Api {
return nil
}
}
public static func parse_messageActionChangeCreator(_ reader: BufferReader) -> MessageAction? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.MessageAction.messageActionChangeCreator(Cons_messageActionChangeCreator(newCreatorId: _1!))
}
else {
return nil
}
}
public static func parse_messageActionChannelCreate(_ reader: BufferReader) -> MessageAction? {
var _1: String?
_1 = parseString(reader)
@ -3327,6 +3419,17 @@ public extension Api {
return nil
}
}
public static func parse_messageActionNewCreatorPending(_ reader: BufferReader) -> MessageAction? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.MessageAction.messageActionNewCreatorPending(Cons_messageActionNewCreatorPending(newCreatorId: _1!))
}
else {
return nil
}
}
public static func parse_messageActionPaidMessagesPrice(_ reader: BufferReader) -> MessageAction? {
var _1: Int32?
_1 = reader.readInt32()

View file

@ -26,141 +26,141 @@ extension ReplyMarkupButtonAction.PeerTypes {
extension ReplyMarkupButton {
init(apiButton: Api.KeyboardButton) {
switch apiButton {
case let .keyboardButton(keyboardButtonData):
let text = keyboardButtonData.text
self.init(title: text, titleWhenForwarded: nil, action: .text)
case let .keyboardButtonCallback(keyboardButtonCallbackData):
let (flags, text, data) = (keyboardButtonCallbackData.flags, keyboardButtonCallbackData.text, keyboardButtonCallbackData.data)
let memory = malloc(data.size)!
memcpy(memory, data.data, data.size)
let dataBuffer = MemoryBuffer(memory: memory, capacity: data.size, length: data.size, freeWhenDone: true)
self.init(title: text, titleWhenForwarded: nil, action: .callback(requiresPassword: (flags & (1 << 0)) != 0, data: dataBuffer))
case let .keyboardButtonRequestGeoLocation(keyboardButtonRequestGeoLocationData):
let text = keyboardButtonRequestGeoLocationData.text
self.init(title: text, titleWhenForwarded: nil, action: .requestMap)
case let .keyboardButtonRequestPhone(keyboardButtonRequestPhoneData):
let text = keyboardButtonRequestPhoneData.text
self.init(title: text, titleWhenForwarded: nil, action: .requestPhone)
case let .keyboardButtonSwitchInline(keyboardButtonSwitchInlineData):
let (flags, text, query, types) = (keyboardButtonSwitchInlineData.flags, keyboardButtonSwitchInlineData.text, keyboardButtonSwitchInlineData.query, keyboardButtonSwitchInlineData.peerTypes)
var peerTypes = ReplyMarkupButtonAction.PeerTypes()
if let types = types {
for type in types {
switch type {
case .inlineQueryPeerTypePM:
peerTypes.insert(.users)
case .inlineQueryPeerTypeBotPM:
peerTypes.insert(.bots)
case .inlineQueryPeerTypeBroadcast:
peerTypes.insert(.channels)
case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup:
peerTypes.insert(.groups)
case .inlineQueryPeerTypeSameBotPM:
break
}
case let .keyboardButton(keyboardButtonData):
let text = keyboardButtonData.text
self.init(title: text, titleWhenForwarded: nil, action: .text, style: keyboardButtonData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonCallback(keyboardButtonCallbackData):
let (flags, text, data) = (keyboardButtonCallbackData.flags, keyboardButtonCallbackData.text, keyboardButtonCallbackData.data)
let memory = malloc(data.size)!
memcpy(memory, data.data, data.size)
let dataBuffer = MemoryBuffer(memory: memory, capacity: data.size, length: data.size, freeWhenDone: true)
self.init(title: text, titleWhenForwarded: nil, action: .callback(requiresPassword: (flags & (1 << 0)) != 0, data: dataBuffer), style: keyboardButtonCallbackData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonRequestGeoLocation(keyboardButtonRequestGeoLocationData):
let text = keyboardButtonRequestGeoLocationData.text
self.init(title: text, titleWhenForwarded: nil, action: .requestMap, style: keyboardButtonRequestGeoLocationData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonRequestPhone(keyboardButtonRequestPhoneData):
let text = keyboardButtonRequestPhoneData.text
self.init(title: text, titleWhenForwarded: nil, action: .requestPhone, style: keyboardButtonRequestPhoneData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonSwitchInline(keyboardButtonSwitchInlineData):
let (flags, text, query, types) = (keyboardButtonSwitchInlineData.flags, keyboardButtonSwitchInlineData.text, keyboardButtonSwitchInlineData.query, keyboardButtonSwitchInlineData.peerTypes)
var peerTypes = ReplyMarkupButtonAction.PeerTypes()
if let types = types {
for type in types {
switch type {
case .inlineQueryPeerTypePM:
peerTypes.insert(.users)
case .inlineQueryPeerTypeBotPM:
peerTypes.insert(.bots)
case .inlineQueryPeerTypeBroadcast:
peerTypes.insert(.channels)
case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup:
peerTypes.insert(.groups)
case .inlineQueryPeerTypeSameBotPM:
break
}
}
self.init(title: text, titleWhenForwarded: nil, action: .switchInline(samePeer: (flags & (1 << 0)) != 0, query: query, peerTypes: peerTypes))
case let .keyboardButtonUrl(keyboardButtonUrlData):
let (text, url) = (keyboardButtonUrlData.text, keyboardButtonUrlData.url)
self.init(title: text, titleWhenForwarded: nil, action: .url(url))
case let .keyboardButtonGame(keyboardButtonGameData):
let text = keyboardButtonGameData.text
self.init(title: text, titleWhenForwarded: nil, action: .openWebApp)
case let .keyboardButtonBuy(keyboardButtonBuyData):
let text = keyboardButtonBuyData.text
self.init(title: text, titleWhenForwarded: nil, action: .payment)
case let .keyboardButtonUrlAuth(keyboardButtonUrlAuthData):
let (text, fwdText, url, buttonId) = (keyboardButtonUrlAuthData.text, keyboardButtonUrlAuthData.fwdText, keyboardButtonUrlAuthData.url, keyboardButtonUrlAuthData.buttonId)
self.init(title: text, titleWhenForwarded: fwdText, action: .urlAuth(url: url, buttonId: buttonId))
case let .inputKeyboardButtonUrlAuth(inputKeyboardButtonUrlAuthData):
let (text, fwdText, url) = (inputKeyboardButtonUrlAuthData.text, inputKeyboardButtonUrlAuthData.fwdText, inputKeyboardButtonUrlAuthData.url)
self.init(title: text, titleWhenForwarded: fwdText, action: .urlAuth(url: url, buttonId: 0))
case let .keyboardButtonRequestPoll(keyboardButtonRequestPollData):
let (quiz, text) = (keyboardButtonRequestPollData.quiz, keyboardButtonRequestPollData.text)
let isQuiz: Bool? = quiz.flatMap { quiz in
if case .boolTrue = quiz {
return true
} else {
return false
}
}
self.init(title: text, titleWhenForwarded: nil, action: .switchInline(samePeer: (flags & (1 << 0)) != 0, query: query, peerTypes: peerTypes), style: keyboardButtonSwitchInlineData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonUrl(keyboardButtonUrlData):
let (text, url) = (keyboardButtonUrlData.text, keyboardButtonUrlData.url)
self.init(title: text, titleWhenForwarded: nil, action: .url(url), style: keyboardButtonUrlData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonGame(keyboardButtonGameData):
let text = keyboardButtonGameData.text
self.init(title: text, titleWhenForwarded: nil, action: .openWebApp, style: keyboardButtonGameData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonBuy(keyboardButtonBuyData):
let text = keyboardButtonBuyData.text
self.init(title: text, titleWhenForwarded: nil, action: .payment, style: keyboardButtonBuyData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonUrlAuth(keyboardButtonUrlAuthData):
let (text, fwdText, url, buttonId) = (keyboardButtonUrlAuthData.text, keyboardButtonUrlAuthData.fwdText, keyboardButtonUrlAuthData.url, keyboardButtonUrlAuthData.buttonId)
self.init(title: text, titleWhenForwarded: fwdText, action: .urlAuth(url: url, buttonId: buttonId), style: keyboardButtonUrlAuthData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .inputKeyboardButtonUrlAuth(inputKeyboardButtonUrlAuthData):
let (text, fwdText, url) = (inputKeyboardButtonUrlAuthData.text, inputKeyboardButtonUrlAuthData.fwdText, inputKeyboardButtonUrlAuthData.url)
self.init(title: text, titleWhenForwarded: fwdText, action: .urlAuth(url: url, buttonId: 0), style: inputKeyboardButtonUrlAuthData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonRequestPoll(keyboardButtonRequestPollData):
let (quiz, text) = (keyboardButtonRequestPollData.quiz, keyboardButtonRequestPollData.text)
let isQuiz: Bool? = quiz.flatMap { quiz in
if case .boolTrue = quiz {
return true
} else {
return false
}
self.init(title: text, titleWhenForwarded: nil, action: .setupPoll(isQuiz: isQuiz))
case let .keyboardButtonUserProfile(keyboardButtonUserProfileData):
let (text, userId) = (keyboardButtonUserProfileData.text, keyboardButtonUserProfileData.userId)
self.init(title: text, titleWhenForwarded: nil, action: .openUserProfile(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))))
case let .inputKeyboardButtonUserProfile(inputKeyboardButtonUserProfileData):
let text = inputKeyboardButtonUserProfileData.text
self.init(title: text, titleWhenForwarded: nil, action: .openUserProfile(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0))))
case let .keyboardButtonWebView(keyboardButtonWebViewData):
let (text, url) = (keyboardButtonWebViewData.text, keyboardButtonWebViewData.url)
self.init(title: text, titleWhenForwarded: nil, action: .openWebView(url: url, simple: false))
case let .keyboardButtonSimpleWebView(keyboardButtonSimpleWebViewData):
let (text, url) = (keyboardButtonSimpleWebViewData.text, keyboardButtonSimpleWebViewData.url)
self.init(title: text, titleWhenForwarded: nil, action: .openWebView(url: url, simple: true))
case let .keyboardButtonRequestPeer(keyboardButtonRequestPeerData):
let (text, buttonId, peerType, maxQuantity) = (keyboardButtonRequestPeerData.text, keyboardButtonRequestPeerData.buttonId, keyboardButtonRequestPeerData.peerType, keyboardButtonRequestPeerData.maxQuantity)
let mappedPeerType: ReplyMarkupButtonRequestPeerType
switch peerType {
case let .requestPeerTypeUser(requestPeerTypeUserData):
let (bot, premium) = (requestPeerTypeUserData.bot, requestPeerTypeUserData.premium)
mappedPeerType = .user(ReplyMarkupButtonRequestPeerType.User(
isBot: bot.flatMap({ $0 == .boolTrue }),
isPremium: premium.flatMap({ $0 == .boolTrue })
))
case let .requestPeerTypeChat(requestPeerTypeChatData):
let (flags, hasUsername, forum, userAdminRights, botAdminRights) = (requestPeerTypeChatData.flags, requestPeerTypeChatData.hasUsername, requestPeerTypeChatData.forum, requestPeerTypeChatData.userAdminRights, requestPeerTypeChatData.botAdminRights)
mappedPeerType = .group(ReplyMarkupButtonRequestPeerType.Group(
isCreator: (flags & (1 << 0)) != 0,
hasUsername: hasUsername.flatMap({ $0 == .boolTrue }),
isForum: forum.flatMap({ $0 == .boolTrue }),
botParticipant: (flags & (1 << 5)) != 0,
userAdminRights: userAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:)),
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
case let .requestPeerTypeBroadcast(requestPeerTypeBroadcastData):
let (flags, hasUsername, userAdminRights, botAdminRights) = (requestPeerTypeBroadcastData.flags, requestPeerTypeBroadcastData.hasUsername, requestPeerTypeBroadcastData.userAdminRights, requestPeerTypeBroadcastData.botAdminRights)
mappedPeerType = .channel(ReplyMarkupButtonRequestPeerType.Channel(
isCreator: (flags & (1 << 0)) != 0,
hasUsername: hasUsername.flatMap({ $0 == .boolTrue }),
userAdminRights: userAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:)),
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
}
self.init(title: text, titleWhenForwarded: nil, action: .requestPeer(peerType: mappedPeerType, buttonId: buttonId, maxQuantity: maxQuantity))
case let .inputKeyboardButtonRequestPeer(inputKeyboardButtonRequestPeerData):
let (text, buttonId, peerType, maxQuantity) = (inputKeyboardButtonRequestPeerData.text, inputKeyboardButtonRequestPeerData.buttonId, inputKeyboardButtonRequestPeerData.peerType, inputKeyboardButtonRequestPeerData.maxQuantity)
let mappedPeerType: ReplyMarkupButtonRequestPeerType
switch peerType {
case let .requestPeerTypeUser(requestPeerTypeUserData):
let (bot, premium) = (requestPeerTypeUserData.bot, requestPeerTypeUserData.premium)
mappedPeerType = .user(ReplyMarkupButtonRequestPeerType.User(
isBot: bot.flatMap({ $0 == .boolTrue }),
isPremium: premium.flatMap({ $0 == .boolTrue })
))
case let .requestPeerTypeChat(requestPeerTypeChatData):
let (flags, hasUsername, forum, userAdminRights, botAdminRights) = (requestPeerTypeChatData.flags, requestPeerTypeChatData.hasUsername, requestPeerTypeChatData.forum, requestPeerTypeChatData.userAdminRights, requestPeerTypeChatData.botAdminRights)
mappedPeerType = .group(ReplyMarkupButtonRequestPeerType.Group(
isCreator: (flags & (1 << 0)) != 0,
hasUsername: hasUsername.flatMap({ $0 == .boolTrue }),
isForum: forum.flatMap({ $0 == .boolTrue }),
botParticipant: (flags & (1 << 5)) != 0,
userAdminRights: userAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:)),
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
case let .requestPeerTypeBroadcast(requestPeerTypeBroadcastData):
let (flags, hasUsername, userAdminRights, botAdminRights) = (requestPeerTypeBroadcastData.flags, requestPeerTypeBroadcastData.hasUsername, requestPeerTypeBroadcastData.userAdminRights, requestPeerTypeBroadcastData.botAdminRights)
mappedPeerType = .channel(ReplyMarkupButtonRequestPeerType.Channel(
isCreator: (flags & (1 << 0)) != 0,
hasUsername: hasUsername.flatMap({ $0 == .boolTrue }),
userAdminRights: userAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:)),
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
}
self.init(title: text, titleWhenForwarded: nil, action: .requestPeer(peerType: mappedPeerType, buttonId: buttonId, maxQuantity: maxQuantity))
case let .keyboardButtonCopy(keyboardButtonCopyData):
let (text, payload) = (keyboardButtonCopyData.text, keyboardButtonCopyData.copyText)
self.init(title: text, titleWhenForwarded: nil, action: .copyText(payload: payload))
}
self.init(title: text, titleWhenForwarded: nil, action: .setupPoll(isQuiz: isQuiz), style: keyboardButtonRequestPollData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonUserProfile(keyboardButtonUserProfileData):
let (text, userId) = (keyboardButtonUserProfileData.text, keyboardButtonUserProfileData.userId)
self.init(title: text, titleWhenForwarded: nil, action: .openUserProfile(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))), style: keyboardButtonUserProfileData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .inputKeyboardButtonUserProfile(inputKeyboardButtonUserProfileData):
let text = inputKeyboardButtonUserProfileData.text
self.init(title: text, titleWhenForwarded: nil, action: .openUserProfile(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0))), style: inputKeyboardButtonUserProfileData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonWebView(keyboardButtonWebViewData):
let (text, url) = (keyboardButtonWebViewData.text, keyboardButtonWebViewData.url)
self.init(title: text, titleWhenForwarded: nil, action: .openWebView(url: url, simple: false), style: keyboardButtonWebViewData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonSimpleWebView(keyboardButtonSimpleWebViewData):
let (text, url) = (keyboardButtonSimpleWebViewData.text, keyboardButtonSimpleWebViewData.url)
self.init(title: text, titleWhenForwarded: nil, action: .openWebView(url: url, simple: true), style: keyboardButtonSimpleWebViewData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonRequestPeer(keyboardButtonRequestPeerData):
let (text, buttonId, peerType, maxQuantity) = (keyboardButtonRequestPeerData.text, keyboardButtonRequestPeerData.buttonId, keyboardButtonRequestPeerData.peerType, keyboardButtonRequestPeerData.maxQuantity)
let mappedPeerType: ReplyMarkupButtonRequestPeerType
switch peerType {
case let .requestPeerTypeUser(requestPeerTypeUserData):
let (bot, premium) = (requestPeerTypeUserData.bot, requestPeerTypeUserData.premium)
mappedPeerType = .user(ReplyMarkupButtonRequestPeerType.User(
isBot: bot.flatMap({ $0 == .boolTrue }),
isPremium: premium.flatMap({ $0 == .boolTrue })
))
case let .requestPeerTypeChat(requestPeerTypeChatData):
let (flags, hasUsername, forum, userAdminRights, botAdminRights) = (requestPeerTypeChatData.flags, requestPeerTypeChatData.hasUsername, requestPeerTypeChatData.forum, requestPeerTypeChatData.userAdminRights, requestPeerTypeChatData.botAdminRights)
mappedPeerType = .group(ReplyMarkupButtonRequestPeerType.Group(
isCreator: (flags & (1 << 0)) != 0,
hasUsername: hasUsername.flatMap({ $0 == .boolTrue }),
isForum: forum.flatMap({ $0 == .boolTrue }),
botParticipant: (flags & (1 << 5)) != 0,
userAdminRights: userAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:)),
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
case let .requestPeerTypeBroadcast(requestPeerTypeBroadcastData):
let (flags, hasUsername, userAdminRights, botAdminRights) = (requestPeerTypeBroadcastData.flags, requestPeerTypeBroadcastData.hasUsername, requestPeerTypeBroadcastData.userAdminRights, requestPeerTypeBroadcastData.botAdminRights)
mappedPeerType = .channel(ReplyMarkupButtonRequestPeerType.Channel(
isCreator: (flags & (1 << 0)) != 0,
hasUsername: hasUsername.flatMap({ $0 == .boolTrue }),
userAdminRights: userAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:)),
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
}
self.init(title: text, titleWhenForwarded: nil, action: .requestPeer(peerType: mappedPeerType, buttonId: buttonId, maxQuantity: maxQuantity), style: keyboardButtonRequestPeerData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .inputKeyboardButtonRequestPeer(inputKeyboardButtonRequestPeerData):
let (text, buttonId, peerType, maxQuantity) = (inputKeyboardButtonRequestPeerData.text, inputKeyboardButtonRequestPeerData.buttonId, inputKeyboardButtonRequestPeerData.peerType, inputKeyboardButtonRequestPeerData.maxQuantity)
let mappedPeerType: ReplyMarkupButtonRequestPeerType
switch peerType {
case let .requestPeerTypeUser(requestPeerTypeUserData):
let (bot, premium) = (requestPeerTypeUserData.bot, requestPeerTypeUserData.premium)
mappedPeerType = .user(ReplyMarkupButtonRequestPeerType.User(
isBot: bot.flatMap({ $0 == .boolTrue }),
isPremium: premium.flatMap({ $0 == .boolTrue })
))
case let .requestPeerTypeChat(requestPeerTypeChatData):
let (flags, hasUsername, forum, userAdminRights, botAdminRights) = (requestPeerTypeChatData.flags, requestPeerTypeChatData.hasUsername, requestPeerTypeChatData.forum, requestPeerTypeChatData.userAdminRights, requestPeerTypeChatData.botAdminRights)
mappedPeerType = .group(ReplyMarkupButtonRequestPeerType.Group(
isCreator: (flags & (1 << 0)) != 0,
hasUsername: hasUsername.flatMap({ $0 == .boolTrue }),
isForum: forum.flatMap({ $0 == .boolTrue }),
botParticipant: (flags & (1 << 5)) != 0,
userAdminRights: userAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:)),
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
case let .requestPeerTypeBroadcast(requestPeerTypeBroadcastData):
let (flags, hasUsername, userAdminRights, botAdminRights) = (requestPeerTypeBroadcastData.flags, requestPeerTypeBroadcastData.hasUsername, requestPeerTypeBroadcastData.userAdminRights, requestPeerTypeBroadcastData.botAdminRights)
mappedPeerType = .channel(ReplyMarkupButtonRequestPeerType.Channel(
isCreator: (flags & (1 << 0)) != 0,
hasUsername: hasUsername.flatMap({ $0 == .boolTrue }),
userAdminRights: userAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:)),
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
}
self.init(title: text, titleWhenForwarded: nil, action: .requestPeer(peerType: mappedPeerType, buttonId: buttonId, maxQuantity: maxQuantity), style: inputKeyboardButtonRequestPeerData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
case let .keyboardButtonCopy(keyboardButtonCopyData):
let (text, payload) = (keyboardButtonCopyData.text, keyboardButtonCopyData.copyText)
self.init(title: text, titleWhenForwarded: nil, action: .copyText(payload: payload), style: keyboardButtonCopyData.style.flatMap(ReplyMarkupButton.Style.init(apiStyle:)))
}
}
}

View file

@ -292,6 +292,10 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
if let otherParticipants {
result.append(contentsOf: otherParticipants.map(\.peerId))
}
case let .messageActionNewCreatorPending(messageActionNewCreatorPending):
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(messageActionNewCreatorPending.newCreatorId)))
case let .messageActionChangeCreator(messageActionChangeCreator):
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(messageActionChangeCreator.newCreatorId)))
}
return result

View file

@ -349,6 +349,16 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return nil
}
return TelegramMediaAction(action: .starGiftPurchaseOfferDeclined(gift: gift, amount: CurrencyAmount(apiAmount: price), hasExpired: (flags & (1 << 0)) != 0))
case let .messageActionNewCreatorPending(messageActionNewCreatorPending):
return TelegramMediaAction(action: .groupCreatorChange(TelegramMediaActionType.GroupCreatorChange(
kind: .pending,
targetPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(messageActionNewCreatorPending.newCreatorId))
)))
case let .messageActionChangeCreator(messageActionChangeCreator):
return TelegramMediaAction(action: .groupCreatorChange(TelegramMediaActionType.GroupCreatorChange(
kind: .applied,
targetPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(messageActionChangeCreator.newCreatorId))
)))
}
}

View file

@ -932,7 +932,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
buttons.append(button)
} else if case let .switchInline(samePeer, query, peerTypes) = button.action, sourceSentViaBot {
let samePeer = samePeer && peerId == sourceMessage.id.peerId
let updatedButton = ReplyMarkupButton(title: button.titleWhenForwarded ?? button.title, titleWhenForwarded: button.titleWhenForwarded, action: .switchInline(samePeer: samePeer, query: query, peerTypes: peerTypes))
let updatedButton = ReplyMarkupButton(title: button.titleWhenForwarded ?? button.title, titleWhenForwarded: button.titleWhenForwarded, action: .switchInline(samePeer: samePeer, query: query, peerTypes: peerTypes), style: button.style)
buttons.append(updatedButton)
} else {
rows.removeAll()

View file

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 223
return 224
}
public func parseMessage(_ data: Data!) -> Any! {

View file

@ -1,4 +1,5 @@
import Postbox
import TelegramApi
public enum ReplyMarkupButtonRequestPeerType: Codable, Equatable {
enum CodingKeys: String, CodingKey {
@ -323,21 +324,79 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
}
}
extension ReplyMarkupButton.Style {
init(apiStyle: Api.KeyboardButtonStyle) {
switch apiStyle {
case let .keyboardButtonStyle(keyboardButtonStyle):
var color: Color?
if keyboardButtonStyle.flags & (1 << 0) != 0 {
color = .primary
} else if keyboardButtonStyle.flags & (1 << 1) != 0 {
color = .danger
} else if keyboardButtonStyle.flags & (1 << 2) != 0 {
color = .success
}
self.init(color: color, iconFileId: keyboardButtonStyle.icon)
}
}
}
public struct ReplyMarkupButton: PostboxCoding, Equatable {
public struct Style: PostboxCoding, Equatable {
public enum Color: Int32 {
case primary = 0
case danger = 1
case success = 2
}
public let color: Color?
public let iconFileId: Int64?
public init(color: Color?, iconFileId: Int64?) {
self.color = color
self.iconFileId = iconFileId
}
public init(decoder: PostboxDecoder) {
let rawColor = decoder.decodeInt32ForKey("c", orElse: -1)
if rawColor != -1 {
self.color = Color(rawValue: rawColor)
} else {
self.color = nil
}
self.iconFileId = decoder.decodeOptionalInt64ForKey("i")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.color?.rawValue ?? -1, forKey: "c")
if let iconFileId = self.iconFileId {
encoder.encodeInt64(iconFileId, forKey: "i")
} else {
encoder.encodeNil(forKey: "i")
}
}
}
public let title: String
public let titleWhenForwarded: String?
public let action: ReplyMarkupButtonAction
public let style: Style?
public init(title: String, titleWhenForwarded: String?, action: ReplyMarkupButtonAction) {
public init(title: String, titleWhenForwarded: String?, action: ReplyMarkupButtonAction, style: Style?) {
self.title = title
self.titleWhenForwarded = titleWhenForwarded
self.action = action
self.style = style
}
public init(decoder: PostboxDecoder) {
self.title = decoder.decodeStringForKey(".t", orElse: "")
self.titleWhenForwarded = decoder.decodeOptionalStringForKey(".tf")
self.action = ReplyMarkupButtonAction(decoder: decoder)
self.style = (decoder.decodeObjectForKey(".stl", decoder: { Style(decoder: $0) }) as? Style)
}
public func encode(_ encoder: PostboxEncoder) {
@ -348,10 +407,15 @@ public struct ReplyMarkupButton: PostboxCoding, Equatable {
encoder.encodeNil(forKey: ".tf")
}
self.action.encode(encoder)
if let style = self.style {
encoder.encodeObject(style, forKey: ".stl")
} else {
encoder.encodeNil(forKey: ".stl")
}
}
public static func ==(lhs: ReplyMarkupButton, rhs: ReplyMarkupButton) -> Bool {
return lhs.title == rhs.title && lhs.action == rhs.action
return lhs.title == rhs.title && lhs.action == rhs.action && lhs.style == rhs.style
}
}
@ -399,6 +463,21 @@ public class ReplyMarkupMessageAttribute: MessageAttribute, Equatable {
public let flags: ReplyMarkupMessageFlags
public let placeholder: String?
public var associatedMediaIds: [MediaId] {
var result: [MediaId] = []
for row in rows {
for button in row.buttons {
if let iconFileId = button.style?.iconFileId {
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: iconFileId)
if !result.contains(mediaId) {
result.append(mediaId)
}
}
}
}
return result
}
public init(rows: [ReplyMarkupRow], flags: ReplyMarkupMessageFlags, placeholder: String?) {
self.rows = rows
self.flags = flags

View file

@ -211,6 +211,38 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
public static let isFinal = StarGiftAuctionBidFlags(rawValue: 1 << 3)
}
public struct GroupCreatorChange: Codable, Equatable {
private enum CodingKeys: String, CodingKey {
case kind = "k"
case targetPeerId = "t"
}
public enum Kind: Int32 {
case pending = 0
case applied = 1
}
public let kind: Kind
public let targetPeerId: PeerId
public init(kind: Kind, targetPeerId: PeerId) {
self.kind = kind
self.targetPeerId = targetPeerId
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.kind = Kind(rawValue: try container.decode(Int32.self, forKey: .kind)) ?? .pending
self.targetPeerId = PeerId(try container.decode(Int64.self, forKey: .targetPeerId))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.kind.rawValue, forKey: .kind)
try container.encode(self.targetPeerId.toInt64(), forKey: .kind)
}
}
case unknown
case groupCreated(title: String)
case addedMembers(peerIds: [PeerId])
@ -269,7 +301,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case suggestedBirthday(TelegramBirthday)
case starGiftPurchaseOffer(gift: StarGift, amount: CurrencyAmount, expireDate: Int32, isAccepted: Bool, isDeclined: Bool)
case starGiftPurchaseOfferDeclined(gift: StarGift, amount: CurrencyAmount, hasExpired: Bool)
case starGiftCraftFail
case groupCreatorChange(GroupCreatorChange)
public init(decoder: PostboxDecoder) {
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
@ -440,9 +472,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case 56:
self = .starGiftPurchaseOffer(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amount") ?? CurrencyAmount(amount: .zero, currency: .stars), expireDate: decoder.decodeInt32ForKey("expireDate", orElse: 0), isAccepted: decoder.decodeBoolForKey("isAccepted", orElse: false), isDeclined: decoder.decodeBoolForKey("isDeclined", orElse: false))
case 57:
self = .starGiftPurchaseOfferDeclined(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amount") ?? CurrencyAmount(amount: .zero, currency: .stars), hasExpired: decoder.decodeBoolForKey("hasExpired", orElse: false))
case 58:
self = .starGiftCraftFail
self = .starGiftPurchaseOfferDeclined(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amount")!, hasExpired: decoder.decodeBoolForKey("hasExpired", orElse: false))
case 59:
self = .groupCreatorChange(decoder.decodeCodable(GroupCreatorChange.self, forKey: "d") ?? GroupCreatorChange(kind: .pending, targetPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0))))
default:
self = .unknown
}
@ -934,8 +966,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
encoder.encodeObject(gift, forKey: "gift")
encoder.encodeCodable(amount, forKey: "amount")
encoder.encodeBool(hasExpired, forKey: "hasExpired")
case .starGiftCraftFail:
encoder.encodeInt32(58, forKey: "_rawValue")
case let .groupCreatorChange(groupCreatorChange):
encoder.encodeInt32(59, forKey: "_rawValue")
encoder.encodeCodable(groupCreatorChange, forKey: "d")
}
}
@ -992,6 +1025,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
return peerIds
case let .conferenceCall(conferenceCall):
return conferenceCall.otherParticipants
case let .groupCreatorChange(groupCreatorChange):
return [groupCreatorChange.targetPeerId]
default:
return []
}

View file

@ -199,6 +199,11 @@ public enum MessageActionUrlAuthResult {
case request(domain: String, bot: Peer, clientData: ClientData?, flags: Flags)
}
public enum MessageActionUrlAuthError {
case generic
case urlExpired
}
public enum MessageActionUrlSubject {
case message(id: MessageId, buttonId: Int32)
case url(String)
@ -259,7 +264,7 @@ func _internal_requestMessageActionUrlAuth(account: Account, subject: MessageAct
}
}
func _internal_acceptMessageActionUrlAuth(account: Account, subject: MessageActionUrlSubject, allowWriteAccess: Bool, sharePhoneNumber: Bool) -> Signal<MessageActionUrlAuthResult, NoError> {
func _internal_acceptMessageActionUrlAuth(account: Account, subject: MessageActionUrlSubject, allowWriteAccess: Bool, sharePhoneNumber: Bool) -> Signal<MessageActionUrlAuthResult, MessageActionUrlAuthError> {
var flags: Int32 = 0
if allowWriteAccess {
flags |= Int32(1 << 0)
@ -292,13 +297,10 @@ func _internal_acceptMessageActionUrlAuth(account: Account, subject: MessageActi
return request
|> `catch` { _ -> Signal<Api.UrlAuthResult?, NoError> in
return .single(nil)
|> mapError { _ -> MessageActionUrlAuthError in
return .generic
}
|> map { result -> MessageActionUrlAuthResult in
guard let result = result else {
return .default
}
switch result {
case let .urlAuthResultAccepted(urlAuthResultAcceptedData):
let url = urlAuthResultAcceptedData.url

View file

@ -86,13 +86,14 @@ public final class SparseMessageList {
}
private let loadHoleDisposable = MetaDisposable()
private var loadingHole: LoadingHole?
private var isLoadingInitial: Bool = false
private var loadingPlaceholders: [MessageId: Disposable] = [:]
private var loadedPlaceholders: [MessageId: Message] = [:]
let statePromise = Promise<SparseMessageList.State>()
init(queue: Queue, account: Account, peerId: PeerId, threadId: Int64?, messageTag: MessageTags) {
init(queue: Queue, account: Account, peerId: PeerId, threadId: Int64?, messageTag: MessageTags, initialMessageIndex: MessageIndex?) {
self.queue = queue
self.account = account
self.peerId = peerId
@ -102,6 +103,11 @@ public final class SparseMessageList {
self.resetTopSection()
if self.threadId == nil {
if initialMessageIndex != nil {
self.isLoadingInitial = true
self.updateState()
}
self.sparseItemsDisposable = (self.account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
@ -112,7 +118,7 @@ public final class SparseMessageList {
guard let messageFilter = messageFilterForTagMask(messageTag) else {
return .single(SparseItems(items: []))
}
//TODO:api
return account.network.request(Api.functions.messages.getSearchResultsPositions(flags: 0, peer: inputPeer, savedPeerId: nil, filter: messageFilter, offsetId: 0, limit: 1000))
|> map { result -> SparseItems in
switch result {
@ -136,7 +142,11 @@ public final class SparseMessageList {
var result = SparseItems(items: [])
for i in 0 ..< positions.count {
if i != 0 {
if i == 0 {
if initialMessageIndex != nil && positions[i].offset != 0 {
result.items.append(.range(count: positions[i].offset))
}
} else {
let deltaCount = positions[i].offset - 1 - positions[i - 1].offset
if deltaCount > 0 {
result.items.append(.range(count: deltaCount))
@ -162,9 +172,34 @@ public final class SparseMessageList {
guard let strongSelf = self else {
return
}
strongSelf.isLoadingInitial = false
strongSelf.sparseItems = sparseItems
if strongSelf.topSection != nil {
strongSelf.updateState()
if let initialMessageIndex {
var loadHoleAnchor: MessageId?
loop: for item in sparseItems.items {
switch item {
case let .anchor(id, timestamp, _):
let anchorIndex = MessageIndex(id: id, timestamp: timestamp)
if anchorIndex <= initialMessageIndex {
loadHoleAnchor = id
break loop
}
case .range:
break
}
}
if let loadHoleAnchor {
strongSelf.loadHole(anchor: loadHoleAnchor, direction: .around, completion: {
})
} else {
if strongSelf.topSection != nil {
strongSelf.updateState()
}
}
} else {
if strongSelf.topSection != nil {
strongSelf.updateState()
}
}
})
}
@ -593,10 +628,22 @@ public final class SparseMessageList {
if self.topSection != topSection {
self.topSection = topSection
}
self.updateState()
if self.loadingHole == nil && !self.isLoadingInitial {
self.updateState()
}
}
private func updateState() {
if self.isLoadingInitial {
self.statePromise.set(.single(SparseMessageList.State(
items: [],
totalCount: 0,
isLoading: true
)))
return
}
var items: [SparseMessageList.State.Item] = []
var minMessageId: MessageId?
if let topSection = self.topSection {
@ -694,11 +741,11 @@ public final class SparseMessageList {
}
}
init(account: Account, peerId: PeerId, threadId: Int64?, messageTag: MessageTags) {
init(account: Account, peerId: PeerId, threadId: Int64?, messageTag: MessageTags, initialMessageIndex: MessageIndex?) {
self.queue = Queue()
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return Impl(queue: queue, account: account, peerId: peerId, threadId: threadId, messageTag: messageTag)
return Impl(queue: queue, account: account, peerId: peerId, threadId: threadId, messageTag: messageTag, initialMessageIndex: initialMessageIndex)
})
}

View file

@ -99,7 +99,7 @@ public extension TelegramEngine {
return _internal_requestMessageActionUrlAuth(account: self.account, subject: subject)
}
public func acceptMessageActionUrlAuth(subject: MessageActionUrlSubject, allowWriteAccess: Bool, sharePhoneNumber: Bool) -> Signal<MessageActionUrlAuthResult, NoError> {
public func acceptMessageActionUrlAuth(subject: MessageActionUrlSubject, allowWriteAccess: Bool, sharePhoneNumber: Bool) -> Signal<MessageActionUrlAuthResult, MessageActionUrlAuthError> {
return _internal_acceptMessageActionUrlAuth(account: self.account, subject: subject, allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber)
}
@ -495,8 +495,8 @@ public extension TelegramEngine {
}
}
public func sparseMessageList(peerId: EnginePeer.Id, threadId: Int64?, tag: EngineMessage.Tags) -> SparseMessageList {
return SparseMessageList(account: self.account, peerId: peerId, threadId: threadId, messageTag: tag)
public func sparseMessageList(peerId: EnginePeer.Id, threadId: Int64?, tag: EngineMessage.Tags, initialMessageIndex: MessageIndex? = nil) -> SparseMessageList {
return SparseMessageList(account: self.account, peerId: peerId, threadId: threadId, messageTag: tag, initialMessageIndex: initialMessageIndex)
}
public func sparseMessageCalendar(peerId: EnginePeer.Id, threadId: Int64?, tag: EngineMessage.Tags, displayMedia: Bool) -> SparseMessageCalendar {

View file

@ -3595,33 +3595,36 @@ extension StarGift.UniqueGift.Attribute {
}
}
public enum GetUniqueStarGiftError {
case generic
case burned
case invalidSlug
case alreadyBurned
}
func _internal_getUniqueStarGift(account: Account, slug: String) -> Signal<StarGift.UniqueGift?, GetUniqueStarGiftError> {
func _internal_getUniqueStarGift(account: Account, slug: String) -> Signal<StarGift.UniqueGift, GetUniqueStarGiftError> {
return account.network.request(Api.functions.payments.getUniqueStarGift(slug: slug))
|> mapError { error -> GetUniqueStarGiftError in
if error.errorDescription == "STARGIFT_ALREADY_BURNED" {
return .burned
return .alreadyBurned
} else if error.errorDescription == "STARGIFT_SLUG_INVALID" {
return .invalidSlug
}
return .generic
}
|> mapToSignal { result -> Signal<StarGift.UniqueGift?, GetUniqueStarGiftError> in
|> mapToSignal { result -> Signal<StarGift.UniqueGift, GetUniqueStarGiftError> in
switch result {
case let .uniqueStarGift(uniqueStarGiftData):
let (gift, chats, users) = (uniqueStarGiftData.gift, uniqueStarGiftData.chats, uniqueStarGiftData.users)
return account.postbox.transaction { transaction in
return account.postbox.transaction { transaction -> Signal<StarGift.UniqueGift, GetUniqueStarGiftError> in
let parsedPeers = AccumulatedPeers(chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
guard case let .unique(uniqueGift) = StarGift(apiStarGift: gift) else {
return nil
return .fail(.invalidSlug)
}
return uniqueGift
return .single(uniqueGift)
}
|> castError(GetUniqueStarGiftError.self)
|> switchToLatest
}
}
}

View file

@ -149,7 +149,7 @@ public extension TelegramEngine {
return _internal_checkCanSendStarGift(account: self.account, giftId: giftId)
}
public func getUniqueStarGift(slug: String) -> Signal<StarGift.UniqueGift?, GetUniqueStarGiftError> {
public func getUniqueStarGift(slug: String) -> Signal<StarGift.UniqueGift, GetUniqueStarGiftError> {
return _internal_getUniqueStarGift(account: self.account, slug: slug)
}

View file

@ -1743,6 +1743,17 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGiftOffer_Rejected(peerName, giftTitle, priceString)._tuple, body: bodyAttributes, argumentAttributes: attributes)
}
}
case let .groupCreatorChange(groupCreatorChange):
var targetName = ""
if let peer = message.peers[groupCreatorChange.targetPeerId] {
targetName = EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
}
switch groupCreatorChange.kind {
case .pending:
attributedString = addAttributesToStringWithRanges(strings.Notification_GroupCreatorChangePending(authorName, targetName)._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id), (1, groupCreatorChange.targetPeerId)]))
case .applied:
attributedString = addAttributesToStringWithRanges(strings.Notification_GroupCreatorChangeApplied(authorName, targetName)._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id), (1, groupCreatorChange.targetPeerId)]))
}
case .unknown:
attributedString = nil
}

View file

@ -31,6 +31,9 @@ swift_library(
"//submodules/TelegramUI/Components/AvatarComponent",
"//submodules/Markdown",
"//submodules/PhoneNumberFormat",
"//submodules/ContextUI",
"//submodules/AccountUtils",
"//submodules/TelegramUI/Components/PeerInfo/AccountPeerContextItem",
],
visibility = [
"//visibility:public",

View file

@ -0,0 +1,134 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import TelegramCore
import TelegramPresentationData
import GlassBackgroundComponent
import AvatarComponent
import BundleIconComponent
import AccountContext
final class AccountSwitchComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let peer: EnginePeer
let canSwitch: Bool
let action: ((GlassContextExtractableContainer) -> Void)
init(
context: AccountContext,
theme: PresentationTheme,
peer: EnginePeer,
canSwitch: Bool,
action: @escaping ((GlassContextExtractableContainer) -> Void)
) {
self.context = context
self.theme = theme
self.peer = peer
self.canSwitch = canSwitch
self.action = action
}
static func ==(lhs: AccountSwitchComponent, rhs: AccountSwitchComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.canSwitch != rhs.canSwitch {
return false
}
return true
}
final class View: UIView {
private let backgroundView = GlassContextExtractableContainer()
private let avatar = ComponentView<Empty>()
private let arrow = ComponentView<Empty>()
private let button = HighlightTrackingButton()
private var component: AccountSwitchComponent?
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.backgroundView)
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func buttonPressed() {
if let component = self.component {
component.action(self.backgroundView)
}
}
func update(component: AccountSwitchComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
let size = CGSize(width: component.canSwitch ? 76.0 : 44.0, height: 44.0)
let avatarSize = self.avatar.update(
transition: .immediate,
component: AnyComponent(
AvatarComponent(
context: component.context,
theme: component.theme,
peer: component.peer,
)
),
environment: {},
containerSize: CGSize(width: 36.0, height: 36.0)
)
if let avatarView = self.avatar.view {
if avatarView.superview == nil {
avatarView.isUserInteractionEnabled = false
self.backgroundView.contentView.addSubview(avatarView)
}
avatarView.frame = CGRect(origin: CGPoint(x: 4.0, y: 4.0), size: avatarSize)
}
let arrowSize = self.arrow.update(
transition: .immediate,
component: AnyComponent(
BundleIconComponent(name: "Navigation/Disclosure", tintColor: component.theme.rootController.navigationBar.secondaryTextColor)
),
environment: {},
containerSize: availableSize
)
if let arrowView = self.arrow.view {
if arrowView.superview == nil {
arrowView.isUserInteractionEnabled = false
self.backgroundView.contentView.addSubview(arrowView)
self.backgroundView.contentView.addSubview(self.button)
}
arrowView.frame = CGRect(origin: CGPoint(x: size.width - arrowSize.width - 12.0, y: floorToScreenPixels((size.height - arrowSize.height) / 2.0)), size: arrowSize)
transition.setAlpha(view: arrowView, alpha: component.canSwitch ? 1.0 : 0.0)
}
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: component.canSwitch, transition: transition)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size))
transition.setFrame(view: self.button, frame: CGRect(origin: .zero, size: size))
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -19,6 +19,10 @@ import ListActionItemComponent
import AvatarComponent
import Markdown
import PhoneNumberFormat
import ContextUI
import AccountUtils
import GlassBackgroundComponent
import AccountPeerContextItem
private final class AuthConfirmationSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -52,6 +56,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
private let subject: MessageActionUrlAuthResult
var peer: EnginePeer?
var forcedAccount: (AccountContext, EnginePeer)?
fileprivate var inProgress = false
var allowWrite = true
@ -77,24 +82,24 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
guard case let .request(domain, _, _, _) = self.subject else {
return
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
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, case let .user(user) = peer, let phone = user.phone else {
return
}
let phoneNumber = formatPhoneNumber(context: self.context, number: phone)
let phoneNumber = formatPhoneNumber(context: self.context, number: phone).replacingOccurrences(of: " ", with: "\u{00A0}")
//TODO:localize
let alertController = textAlertController(
context: self.context,
title: "Phone Number",
text: "**\(domain)** wants to access your phone number **\(phoneNumber)**.\n\nAllow access?",
title: presentationData.strings.AuthConfirmation_PhoneNumberConfirmation_Title,
text: presentationData.strings.AuthConfirmation_PhoneNumberConfirmation_Text(domain, phoneNumber).string,
actions: [
TextAlertAction(type: .genericAction, title: "Deny", action: {
TextAlertAction(type: .genericAction, title: presentationData.strings.AuthConfirmation_PhoneNumberConfirmation_Deny, action: {
commit(false)
}),
TextAlertAction(type: .defaultAction, title: "Allow", action: {
TextAlertAction(type: .defaultAction, title: presentationData.strings.AuthConfirmation_PhoneNumberConfirmation_Allow, action: {
commit(true)
})
]
@ -102,6 +107,51 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
self.controller?.present(alertController, in: .window(.root))
})
}
func presentAccountSwitchMenu(sourceView: GlassContextExtractableContainer) {
guard let controller = self.controller else {
return
}
let context = self.context
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let items: Signal<[ContextMenuItem], NoError> = activeAccountsAndPeers(context: self.context, includePrimary: true)
|> take(1)
|> map { primary, other -> [ContextMenuItem] in
var items: [ContextMenuItem] = []
var existingIds = Set<EnginePeer.Id>()
if let (_, peer) = primary {
items.append(.custom(AccountPeerContextItem(context: context, account: context.account, peer: peer, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
self.forcedAccount = nil
self.updated()
}), true))
existingIds.insert(peer.id)
}
for (accountContext, peer, _) in other {
guard !existingIds.contains(peer.id) else {
continue
}
items.append(.custom(AccountPeerContextItem(context: accountContext, account: accountContext.account, peer: peer, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
self.forcedAccount = (accountContext, peer)
self.updated()
}), true))
}
return items
}
let contextController = makeContextController(presentationData: presentationData, source: .reference(AuthConfirmationReferenceContentSource(controller: controller, sourceView: sourceView)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: nil)
controller.presentInGlobalOverlay(contextController)
}
}
func makeState() -> State {
@ -110,7 +160,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
static var body: Body {
let closeButton = Child(GlassBarButtonComponent.self)
let peerButton = Child(AvatarComponent.self)
let accountButton = Child(AccountSwitchComponent.self)
let avatar = Child(AvatarComponent.self)
let title = Child(MultilineTextComponent.self)
let description = Child(MultilineTextComponent.self)
@ -162,17 +212,21 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
)
if let peer = state.peer {
let peerButton = peerButton.update(
component: AvatarComponent(
context: component.context,
let accountButton = accountButton.update(
component: AccountSwitchComponent(
context: state.forcedAccount?.0 ?? component.context,
theme: environment.theme,
peer: peer
peer: state.forcedAccount?.1 ?? peer,
canSwitch: true,
action: { [weak state] sourceView in
state?.presentAccountSwitchMenu(sourceView: sourceView)
}
),
availableSize: CGSize(width: 44.0, height: 44.0),
availableSize: context.availableSize,
transition: .immediate
)
context.add(peerButton
.position(CGPoint(x: context.availableSize.width - 16.0 - peerButton.size.width / 2.0, y: 16.0 + peerButton.size.height / 2.0))
context.add(accountButton
.position(CGPoint(x: context.availableSize.width - 16.0 - accountButton.size.width / 2.0, y: 16.0 + accountButton.size.height / 2.0))
)
}
@ -192,11 +246,11 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
)
contentHeight += avatar.size.height
contentHeight += 18.0
let titleFont = Font.bold(24.0)
let title = title.update(
component: MultilineTextComponent(
text: .markdown(text: "Log in to **\(domain)**", attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.controlAccentColor), link: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })),
text: .markdown(text: strings.AuthConfirmation_Title(domain).string, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.controlAccentColor), link: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })),
horizontalAlignment: .center,
maximumNumberOfLines: 2
),
@ -213,7 +267,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
let boldTextFont = Font.semibold(15.0)
let description = description.update(
component: MultilineTextComponent(
text: .markdown(text: "This site will receive your **name**,\n**username** and **profile photo**.", attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: theme.actionSheet.primaryTextColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })),
text: .markdown(text: strings.AuthConfirmation_Description, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: theme.actionSheet.primaryTextColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil })),
horizontalAlignment: .center,
maximumNumberOfLines: 3,
lineSpacing: 0.2
@ -235,7 +289,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
style: .glass,
title: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Device",
string: strings.AuthConfirmation_Device,
font: Font.regular(17.0),
textColor: theme.list.itemPrimaryTextColor
)),
@ -283,7 +337,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
style: .glass,
title: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "IP Address",
string: strings.AuthConfirmation_IpAddress,
font: Font.regular(17.0),
textColor: theme.list.itemPrimaryTextColor
)),
@ -331,7 +385,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
header: nil,
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "This login attempt came from the device above.",
string: strings.AuthConfirmation_Info,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
@ -357,7 +411,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Allow Messages",
string: strings.AuthConfirmation_AllowMessages,
font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize),
textColor: theme.list.itemPrimaryTextColor
)),
@ -380,7 +434,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
header: nil,
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "This will allow \(EnginePeer(bot).compactDisplayTitle) to message you.",
string: strings.AuthConfirmation_AllowMessagesInfo(EnginePeer(bot).compactDisplayTitle).string,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
@ -412,7 +466,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: "Cancel", font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center))))
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.AuthConfirmation_Cancel, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center))))
),
action: {
component.cancel(true)
@ -436,7 +490,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: "Log In", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.AuthConfirmation_LogIn, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
),
displaysProgress: state.inProgress,
action: { [weak state] in
@ -610,3 +664,19 @@ public class AuthConfirmationScreen: ViewControllerComponentContainer {
}
}
}
private final class AuthConfirmationReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController
private let sourceView: UIView
let forceDisplayBelowKeyboard = true
init(controller: ViewController, sourceView: UIView) {
self.controller = controller
self.sourceView = sourceView
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View file

@ -21,6 +21,10 @@ swift_library(
"//submodules/WallpaperBackgroundNode",
"//submodules/TelegramUI/Components/ChatControllerInteraction",
"//submodules/TelegramUI/Components/ChatInputNode",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
],
visibility = [
"//visibility:public",

View file

@ -11,20 +11,19 @@ import ChatPresentationInterfaceState
import WallpaperBackgroundNode
import ChatControllerInteraction
import ChatInputNode
import ComponentFlow
import ComponentDisplayAdapters
import GlassBackgroundComponent
import EmojiStatusComponent
private final class ChatButtonKeyboardInputButtonNode: HighlightTrackingButtonNode {
var button: ReplyMarkupButton? {
didSet {
self.updateIcon()
}
}
private(set) var button: ReplyMarkupButton?
private let backgroundContainerNode: ASDisplayNode
private var backgroundNode: WallpaperBubbleBackgroundNode?
private let backgroundColorNode: ASDisplayNode
private let backgroundAdditionalColorNode: ASDisplayNode
private let backgroundView: UIImageView
private var icon: ComponentView<Empty>?
private let highlightNode: ASImageNode
let tintMaskView: UIImageView
private let textNode: ImmediateTextNode
private var iconNode: ASImageNode?
@ -33,22 +32,11 @@ private final class ChatButtonKeyboardInputButtonNode: HighlightTrackingButtonNo
init() {
self.backgroundContainerNode = ASDisplayNode()
self.backgroundContainerNode.clipsToBounds = true
self.backgroundContainerNode.allowsGroupOpacity = true
self.backgroundContainerNode.isUserInteractionEnabled = false
self.backgroundContainerNode.cornerRadius = 10.0
self.backgroundContainerNode.layer.cornerCurve = .continuous
self.backgroundColorNode = ASDisplayNode()
self.backgroundColorNode.cornerRadius = 10.0
self.backgroundColorNode.layer.cornerCurve = .continuous
self.backgroundAdditionalColorNode = ASDisplayNode()
self.backgroundAdditionalColorNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.1)
self.backgroundAdditionalColorNode.isHidden = true
self.highlightNode = ASImageNode()
self.highlightNode.isUserInteractionEnabled = false
self.backgroundView = UIImageView()
self.tintMaskView = UIImageView()
self.textNode = ImmediateTextNode()
self.textNode.isUserInteractionEnabled = false
@ -61,58 +49,68 @@ private final class ChatButtonKeyboardInputButtonNode: HighlightTrackingButtonNo
self.addSubnode(self.backgroundContainerNode)
self.backgroundContainerNode.addSubnode(self.backgroundColorNode)
self.backgroundContainerNode.addSubnode(self.backgroundAdditionalColorNode)
self.addSubnode(self.textNode)
self.backgroundView.isUserInteractionEnabled = false
self.backgroundContainerNode.view.addSubview(self.backgroundView)
self.backgroundContainerNode.addSubnode(self.highlightNode)
self.textNode.isUserInteractionEnabled = false
self.addSubnode(self.textNode)
self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted, !strongSelf.bounds.width.isZero {
let scale = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width
strongSelf.layer.animateScale(from: 1.0, to: scale, duration: 0.15, removeOnCompletion: false)
strongSelf.backgroundContainerNode.layer.removeAnimation(forKey: "opacity")
strongSelf.backgroundContainerNode.alpha = 0.6
} else if let presentationLayer = strongSelf.layer.presentation() {
strongSelf.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
strongSelf.backgroundContainerNode.alpha = 0.9
} else {
strongSelf.backgroundContainerNode.alpha = 1.0
strongSelf.backgroundContainerNode.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
strongSelf.backgroundContainerNode.layer.animateAlpha(from: 0.9, to: 1.0, duration: 0.2)
}
}
}
}
override func setAttributedTitle(_ title: NSAttributedString, for state: UIControl.State) {
func update(context: AccountContext, size: CGSize, theme: PresentationTheme, wallpaperBackgroundNode: WallpaperBackgroundNode?, button: ReplyMarkupButton, message: EngineMessage) {
self.button = button
if theme !== self.theme {
self.theme = theme
}
if self.backgroundView.image == nil {
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: 20.0, color: .white)?.withRenderingMode(.alwaysTemplate)
self.tintMaskView.image = self.backgroundView.image
}
self.tintMaskView.tintColor = .black
var titleColor = theme.chat.inputButtonPanel.buttonTextColor
if let color = button.style?.color {
switch color {
case .primary:
self.backgroundView.tintColor = theme.list.itemCheckColors.fillColor
titleColor = theme.list.itemCheckColors.foregroundColor
case .danger:
self.backgroundView.tintColor = UIColor(rgb: 0xFF3B30)
titleColor = .white
case .success:
self.backgroundView.tintColor = UIColor(rgb: 0x21B246)
titleColor = .white
}
} else {
self.backgroundView.tintColor = theme.overallDarkAppearance ? UIColor(white: 1.0, alpha: 0.25) : UIColor(white: 1.0, alpha: 0.85)
}
let title = NSAttributedString(string: button.title, font: Font.regular(16.0), textColor: titleColor, paragraphAlignment: .center)
self.textNode.attributedText = title
self.accessibilityLabel = title.string
}
private var absoluteRect: (CGRect, CGSize)?
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
self.absoluteRect = (rect, containerSize)
if let backgroundNode = self.backgroundNode {
var backgroundFrame = backgroundNode.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundNode.update(rect: backgroundFrame, within: containerSize, transition: transition)
}
}
private func updateIcon() {
guard let theme = self.theme else {
return
}
var iconImage: UIImage?
if let button = self.button {
switch button.action {
case .openWebView:
iconImage = PresentationResourcesChat.chatKeyboardActionButtonWebAppIconImage(theme)
default:
iconImage = nil
case .openWebView:
iconImage = PresentationResourcesChat.chatKeyboardActionButtonWebAppIconImage(theme)
default:
iconImage = nil
}
}
@ -121,6 +119,7 @@ private final class ChatButtonKeyboardInputButtonNode: HighlightTrackingButtonNo
let iconNode = ASImageNode()
iconNode.contentMode = .center
self.iconNode = iconNode
iconNode.isUserInteractionEnabled = false
self.addSubnode(iconNode)
}
self.iconNode?.image = iconImage
@ -129,71 +128,84 @@ private final class ChatButtonKeyboardInputButtonNode: HighlightTrackingButtonNo
self.iconNode = nil
}
self.setNeedsLayout()
}
func updateTheme(theme: PresentationTheme, wallpaperBackgroundNode: WallpaperBackgroundNode?) {
if theme !== self.theme {
self.theme = theme
self.highlightNode.image = PresentationResourcesChat.chatInputButtonPanelButtonHighlightImage(theme)
self.updateIcon()
}
self.backgroundColorNode.backgroundColor = theme.chat.inputButtonPanel.buttonFillColor
if let alpha = self.backgroundColorNode.backgroundColor?.alpha, alpha < 1.0 {
self.backgroundColorNode.layer.compositingFilter = "softLightBlendMode"
self.backgroundAdditionalColorNode.isHidden = false
} else {
self.backgroundColorNode.layer.compositingFilter = nil
self.backgroundAdditionalColorNode.isHidden = true
}
if wallpaperBackgroundNode?.hasExtraBubbleBackground() == true {
if self.backgroundNode == nil, let backgroundContent = wallpaperBackgroundNode?.makeBubbleBackground(for: .free) {
self.backgroundNode = backgroundContent
self.backgroundContainerNode.insertSubnode(backgroundContent, at: 0)
self.setNeedsLayout()
}
} else {
self.backgroundNode?.removeFromSupernode()
self.backgroundNode = nil
}
}
override func layout() {
super.layout()
self.backgroundContainerNode.frame = self.bounds
self.backgroundColorNode.frame = CGRect(origin: .zero, size: CGSize(width: self.bounds.width, height: self.bounds.height - 1.0))
self.backgroundAdditionalColorNode.frame = self.backgroundColorNode.frame
self.backgroundNode?.frame = self.backgroundColorNode.frame
self.highlightNode.frame = self.bounds
if let (rect, containerSize) = self.absoluteRect {
self.update(rect: rect, within: containerSize, transition: .immediate)
}
self.backgroundView.frame = CGRect(origin: .zero, size: CGSize(width: self.bounds.width, height: self.bounds.height))
if let iconNode = self.iconNode {
iconNode.frame = CGRect(x: self.frame.width - 16.0, y: 4.0, width: 12.0, height: 12.0)
}
let textSize = self.textNode.updateLayout(CGSize(width: self.bounds.width - 16.0, height: self.bounds.height))
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.bounds.width - textSize.width) / 2.0), y: floorToScreenPixels((self.bounds.height - textSize.height) / 2.0)), size: textSize)
var maxTextWidth = size.width - 16.0
let iconSize = CGSize(width: 24.0, height: 24.0)
let iconSpacing: CGFloat = 6.0
if let iconFileId = button.style?.iconFileId {
let icon: ComponentView<Empty>
if let current = self.icon {
icon = current
} else {
icon = ComponentView()
self.icon = icon
}
maxTextWidth -= iconSize.width + iconSpacing
var animationContent: EmojiStatusComponent.AnimationContent = .customEmoji(fileId: iconFileId)
if let file = message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: iconFileId)] as? TelegramMediaFile {
animationContent = .file(file: file)
}
let _ = icon.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
content: .animation(
content: animationContent,
size: iconSize,
placeholderColor: theme.overallDarkAppearance ? UIColor(white: 1.0, alpha: 0.1) : UIColor(white: 0.0, alpha: 0.1),
themeColor: theme.list.itemPrimaryTextColor,
loopMode: .count(0)
),
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: iconSize
)
} else if let icon = self.icon {
self.icon = nil
icon.view?.removeFromSuperview()
}
let textSize = self.textNode.updateLayout(CGSize(width: maxTextWidth, height: self.bounds.height))
var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.bounds.width - textSize.width) / 2.0), y: floorToScreenPixels((self.bounds.height - textSize.height) / 2.0)), size: textSize)
if let iconView = self.icon?.view {
let contentX = floor((size.width - textSize.width - iconSize.width - iconSpacing) * 0.5)
textFrame.origin.x = contentX + iconSize.width + iconSpacing
let iconFrame = CGRect(origin: CGPoint(x: contentX, y: floor((size.height - iconSize.height) * 0.5)), size: iconSize)
if iconView.superview == nil {
iconView.isUserInteractionEnabled = false
self.view.addSubview(iconView)
}
iconView.frame = iconFrame
}
self.textNode.frame = textFrame
}
}
public final class ChatButtonKeyboardInputNode: ChatInputNode {
public final class ChatButtonKeyboardInputNode: ChatInputNode, UIScrollViewDelegate {
private let context: AccountContext
private let controllerInteraction: ChatControllerInteraction
private let backgroundView: BlurredBackgroundView
private let backgroundTintView: UIImageView
private let backgroundTintMaskView: UIView
private let backgroundChromeView: UIImageView
private let scrollNode: ASScrollNode
private var backgroundNode: WallpaperBubbleBackgroundNode?
private let backgroundColorNode: ASDisplayNode
private var buttonNodes: [ChatButtonKeyboardInputButtonNode] = []
private var message: Message?
@ -204,19 +216,42 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode {
self.context = context
self.controllerInteraction = controllerInteraction
self.scrollNode = ASScrollNode()
self.backgroundView = BlurredBackgroundView(color: .black, enableBlur: true)
self.backgroundTintView = UIImageView()
self.backgroundColorNode = ASDisplayNode()
self.backgroundTintMaskView = UIView()
self.backgroundTintMaskView.backgroundColor = .white
self.backgroundChromeView = UIImageView()
self.scrollNode = ASScrollNode()
super.init()
self.addSubnode(self.backgroundColorNode)
self.view.addSubview(self.backgroundView)
self.view.addSubview(self.backgroundTintView)
if let filter = CALayer.luminanceToAlpha() {
self.backgroundTintMaskView.layer.filters = [filter]
self.backgroundTintView.mask = self.backgroundTintMaskView
}
self.view.addSubview(self.backgroundChromeView)
self.addSubnode(self.scrollNode)
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.canCancelContentTouches = true
self.scrollNode.view.alwaysBounceHorizontal = false
self.scrollNode.view.alwaysBounceVertical = false
self.scrollNode.view.delegate = self
self.scrollNode.view.clipsToBounds = true
self.scrollNode.cornerRadius = 30.0
self.scrollNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
self.backgroundTintMaskView.clipsToBounds = true
self.backgroundTintMaskView.layer.cornerRadius = 30.0
self.backgroundTintMaskView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
}
override public func didLoad() {
@ -227,38 +262,16 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode {
}
}
private var absoluteRect: (CGRect, CGSize)?
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
self.absoluteRect = (rect, containerSize)
if let backgroundNode = self.backgroundNode {
var backgroundFrame = backgroundNode.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundNode.update(rect: backgroundFrame, within: containerSize, transition: transition)
}
for buttonNode in self.buttonNodes {
var buttonFrame = buttonNode.frame
buttonFrame.origin.x += rect.minX
buttonFrame.origin.y += rect.minY
buttonNode.update(rect: buttonFrame, within: containerSize, transition: transition)
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
var bounds = self.backgroundTintMaskView.bounds
bounds.origin.y = scrollView.contentOffset.y
self.backgroundTintMaskView.bounds = bounds
}
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, layoutMetrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool) -> (CGFloat, CGFloat) {
if self.backgroundNode == nil {
if let backgroundNode = self.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
self.backgroundNode = backgroundNode
self.insertSubnode(backgroundNode, at: 0)
}
}
let updatedTheme = self.theme !== interfaceState.theme
if updatedTheme {
self.theme = interfaceState.theme
self.backgroundColorNode.backgroundColor = interfaceState.theme.chat.inputButtonPanel.panelBackgroundColor
}
var validatedMarkup: ReplyMarkupMessageAttribute?
@ -275,9 +288,9 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode {
self.message = interfaceState.keyboardButtonsMessage
if let markup = validatedMarkup {
let verticalInset: CGFloat = 16.0
let sideInset: CGFloat = 16.0 + leftInset
if let markup = validatedMarkup, let message = self.message {
let verticalInset: CGFloat = 18.0
let sideInset: CGFloat = 18.0 + leftInset
var buttonHeight: CGFloat = 43.0
let columnSpacing: CGFloat = 6.0
let rowSpacing: CGFloat = 5.0
@ -305,15 +318,14 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode {
buttonNode.titleNode.maximumNumberOfLines = 2
buttonNode.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: [.touchUpInside])
self.scrollNode.addSubnode(buttonNode)
self.backgroundTintMaskView.addSubview(buttonNode.tintMaskView)
self.buttonNodes.append(buttonNode)
}
buttonNode.updateTheme(theme: interfaceState.theme, wallpaperBackgroundNode: self.controllerInteraction.presentationContext.backgroundNode)
buttonIndex += 1
if buttonNode.button != button || updatedTheme {
buttonNode.button = button
buttonNode.setAttributedTitle(NSAttributedString(string: button.title, font: Font.regular(16.0), textColor: interfaceState.theme.chat.inputButtonPanel.buttonTextColor, paragraphAlignment: .center), for: [])
}
buttonNode.frame = CGRect(origin: CGPoint(x: sideInset + CGFloat(columnIndex) * (buttonWidth + columnSpacing), y: verticalOffset), size: CGSize(width: buttonWidth, height: buttonHeight))
let buttonFrame = CGRect(origin: CGPoint(x: sideInset + CGFloat(columnIndex) * (buttonWidth + columnSpacing), y: verticalOffset), size: CGSize(width: buttonWidth, height: buttonHeight))
buttonNode.frame = buttonFrame
buttonNode.tintMaskView.frame = buttonFrame
buttonNode.update(context: self.context, size: buttonFrame.size, theme: interfaceState.theme, wallpaperBackgroundNode: self.controllerInteraction.presentationContext.backgroundNode, button: button, message: EngineMessage(message))
columnIndex += 1
}
verticalOffset += buttonHeight + rowSpacing
@ -321,6 +333,7 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode {
for i in (buttonIndex ..< self.buttonNodes.count).reversed() {
self.buttonNodes[i].removeFromSupernode()
self.buttonNodes[i].tintMaskView.removeFromSuperview()
self.buttonNodes.remove(at: i)
}
@ -335,15 +348,28 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode {
self.scrollNode.view.setContentOffset(CGPoint(), animated: false)
}
if let backgroundNode = self.backgroundNode {
backgroundNode.frame = CGRect(origin: .zero, size: CGSize(width: width, height: panelHeight))
}
self.backgroundColorNode.frame = CGRect(origin: .zero, size: CGSize(width: width, height: panelHeight))
var backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
backgroundFrame.size.height += 32.0
let keyboardCornerRadius: CGFloat = 30.0
if let (rect, containerSize) = self.absoluteRect {
self.updateAbsoluteRect(rect, within: containerSize, transition: transition)
if self.backgroundChromeView.image == nil || updatedTheme {
self.backgroundChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: keyboardCornerRadius * 2.0, height: keyboardCornerRadius * 2.0), isDark: interfaceState.theme.overallDarkAppearance, fillColor: .clear)
}
if self.backgroundTintView.image == nil {
self.backgroundTintView.image = generateStretchableFilledCircleImage(diameter: keyboardCornerRadius * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
}
self.backgroundTintView.tintColor = interfaceState.theme.chat.inputButtonPanel.panelBackgroundColor
transition.updateFrame(view: self.backgroundView, frame: backgroundFrame)
transition.updateFrame(view: self.backgroundTintView, frame: backgroundFrame)
transition.updateFrame(view: self.backgroundTintMaskView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
self.backgroundView.updateColor(color: .clear, forceKeepBlur: true, transition: .immediate)
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: keyboardCornerRadius, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition)
transition.updateFrame(view: self.backgroundChromeView, frame: backgroundFrame.insetBy(dx: -1.0, dy: 0.0))
return (panelHeight, 0.0)
} else {
return (0.0, 0.0)

View file

@ -477,6 +477,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
transition: ComponentTransition(transition),
component: AnyComponent(GlassControlPanelComponent(
theme: interfaceState.theme,
preferClearGlass: interfaceState.preferredGlassType == .clear,
leftItem: leftPanelItems.isEmpty ? nil : GlassControlPanelComponent.Item(
items: leftPanelItems,
background: .panel
@ -498,118 +499,6 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
transition.updateFrame(view: panelView, frame: CGRect(origin: CGPoint(), size: panelFrame.size))
}
/*if self.presentationInterfaceState != interfaceState || force {
let previousState = self.presentationInterfaceState
self.presentationInterfaceState = interfaceState
if previousState?.theme !== interfaceState.theme {
self.helpButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.helpButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.panelControlColor
self.suggestedPostButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/SuggestPost"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.suggestedPostButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.panelControlColor
self.giftButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.giftButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.panelControlColor
}
if let context = self.context, let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted || previousState?.pinnedMessage != interfaceState.pinnedMessage || force {
if let action = actionForPeer(context: context, peer: peer, interfaceState: interfaceState, isJoining: self.isJoining, isMuted: interfaceState.peerIsMuted) {
let previousAction = self.action
self.action = action
let (title, _) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings)
let _ = previousAction
let titleColor: UIColor
if case .join = self.action {
titleColor = interfaceState.theme.chat.inputPanel.actionControlForegroundColor
} else {
titleColor = interfaceState.theme.chat.inputPanel.panelControlColor
}
self.buttonTitle.attributedText = NSAttributedString(string: title, font: Font.semibold(15.0), textColor: titleColor)
self.buttonTintTitle.attributedText = NSAttributedString(string: title, font: Font.semibold(15.0), textColor: .black)
self.button.accessibilityLabel = title
} else {
self.action = nil
}
}
}
let panelHeight = defaultHeight(metrics: metrics)
if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel {
if case let .broadcast(broadcastInfo) = peer.info, interfaceState.starGiftsAvailable {
if self.giftButton.isHidden && !isFirstTime {
self.giftButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.giftButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
}
self.giftButtonBackgroundView.isHidden = false
self.helpButtonBackgroundView.isHidden = true
self.suggestedPostButtonBackgroundView.isHidden = !broadcastInfo.flags.contains(.hasMonoforum)
self.presentGiftOrSuggestTooltip()
} else if case let .broadcast(broadcastInfo) = peer.info, broadcastInfo.flags.contains(.hasMonoforum) {
self.giftButtonBackgroundView.isHidden = true
self.helpButtonBackgroundView.isHidden = true
self.suggestedPostButtonBackgroundView.isHidden = false
self.presentGiftOrSuggestTooltip()
} else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications {
self.giftButtonBackgroundView.isHidden = true
self.helpButtonBackgroundView.isHidden = false
self.suggestedPostButtonBackgroundView.isHidden = true
} else {
self.giftButtonBackgroundView.isHidden = true
self.helpButtonBackgroundView.isHidden = true
self.suggestedPostButtonBackgroundView.isHidden = true
}
} else {
self.giftButtonBackgroundView.isHidden = true
self.helpButtonBackgroundView.isHidden = true
self.suggestedPostButtonBackgroundView.isHidden = true
}
let buttonTitleSize = self.buttonTitle.updateLayout(CGSize(width: width, height: panelHeight))
let _ = self.buttonTintTitle.updateLayout(CGSize(width: width, height: panelHeight))
let buttonSize = CGSize(width: buttonTitleSize.width + 16.0 * 2.0, height: 40.0)
let buttonFrame = CGRect(origin: CGPoint(x: floor((width - buttonSize.width) / 2.0), y: floor((panelHeight - buttonSize.height) * 0.5)), size: buttonSize)
transition.updateFrame(view: self.buttonBackgroundView, frame: buttonFrame)
transition.updateFrame(view: self.button, frame: CGRect(origin: CGPoint(), size: buttonFrame.size))
let buttonTintColor: GlassBackgroundView.TintColor
if case .join = self.action {
buttonTintColor = .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7), innerColor: interfaceState.theme.chat.inputPanel.actionControlFillColor)
} else {
buttonTintColor = .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
}
self.buttonBackgroundView.update(size: buttonFrame.size, cornerRadius: buttonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: buttonTintColor, isInteractive: true, transition: ComponentTransition(transition))
self.buttonTitle.frame = CGRect(origin: CGPoint(x: floor((buttonFrame.width - buttonTitleSize.width) * 0.5), y: floor((buttonFrame.height - buttonTitleSize.height) * 0.5)), size: buttonTitleSize)
self.buttonTintTitle.frame = self.buttonTitle.frame
let giftButtonFrame = CGRect(x: width - rightInset - 40.0 - 8.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0)
transition.updateFrame(view: self.giftButtonBackgroundView, frame: giftButtonFrame)
if let image = self.giftButtonIconView.image {
transition.updateFrame(view: self.giftButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: giftButtonFrame.size)))
}
transition.updateFrame(view: self.giftButton, frame: CGRect(origin: CGPoint(), size: giftButtonFrame.size))
self.giftButtonBackgroundView.update(size: giftButtonFrame.size, cornerRadius: giftButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: ComponentTransition(transition))
let helpButtonFrame = CGRect(x: width - rightInset - 8.0 - 40.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0)
transition.updateFrame(view: self.helpButtonBackgroundView, frame: helpButtonFrame)
if let image = self.helpButtonIconView.image {
transition.updateFrame(view: self.helpButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: helpButtonFrame.size)))
}
transition.updateFrame(view: self.helpButton, frame: CGRect(origin: CGPoint(), size: helpButtonFrame.size))
self.helpButtonBackgroundView.update(size: helpButtonFrame.size, cornerRadius: helpButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: ComponentTransition(transition))
let suggestedPostButtonFrame = CGRect(x: leftInset + 8.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0)
transition.updateFrame(view: self.suggestedPostButtonBackgroundView, frame: suggestedPostButtonFrame)
if let image = self.suggestedPostButtonIconView.image {
transition.updateFrame(view: self.suggestedPostButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size)))
}
transition.updateFrame(view: self.suggestedPostButton, frame: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size))
self.suggestedPostButtonBackgroundView.update(size: suggestedPostButtonFrame.size, cornerRadius: suggestedPostButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: ComponentTransition(transition))*/
return panelHeight
}

View file

@ -20,6 +20,9 @@ swift_library(
"//submodules/WallpaperBackgroundNode",
"//submodules/UrlHandling",
"//submodules/TelegramUI/Components/TextLoadingEffect",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
],
visibility = [
"//visibility:public",

View file

@ -10,6 +10,9 @@ import WallpaperBackgroundNode
import UrlHandling
import SwiftSignalKit
import TextLoadingEffect
import ComponentFlow
import ComponentDisplayAdapters
import EmojiStatusComponent
private let titleFont = Font.medium(16.0)
@ -70,9 +73,12 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
private var iconNode: ASImageNode?
private var buttonView: HighlightTrackingButton?
private var icon: ComponentView<Empty>?
private var wallpaperBackgroundNode: WallpaperBackgroundNode?
private var backgroundContent: WallpaperBubbleBackgroundNode?
private var backgroundColorNode: ASDisplayNode?
private var backgroundColorView: UIImageView?
private var maskPath: CGPath?
private var loadingEffectView: TextLoadingEffectView?
@ -175,6 +181,8 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
self.view.insertSubview(loadingEffectView, belowSubview: iconNode.view)
} else if let titleNode = self.titleNode, titleNode.view.superview != nil {
self.view.insertSubview(loadingEffectView, belowSubview: titleNode.view)
} else if let iconView = self.icon?.view, iconView.superview != nil {
self.view.insertSubview(loadingEffectView, belowSubview: iconView)
} else {
self.view.addSubview(loadingEffectView)
}
@ -320,6 +328,12 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
customIconSpaceWidth = 3.0 + iconImage.size.width
}
let emojiIconSize = CGSize(width: 24.0, height: 24.0)
let emojiIconSpacing: CGFloat = 6.0
if button.style?.iconFileId != nil {
customIconSpaceWidth = emojiIconSpacing + emojiIconSize.width
}
let (titleSize, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(44.0, constrainedWidth - minimumSideInset - minimumSideInset - customIconSpaceWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
let contentWidth = titleSize.size.width + sideInset + sideInset + customIconSpaceWidth
@ -345,8 +359,6 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
node.longTapRecognizer?.isEnabled = false
}
//animation.animator.updateFrame(layer: node.backgroundBlurNode.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, width), height: 42.0)), completion: nil)
if node.backgroundBlurView == nil {
if let backgroundBlurView = backgroundNode?.makeFreeBackground() {
node.backgroundBlurView = backgroundBlurView
@ -357,9 +369,6 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
animation.animator.updateFrame(layer: backgroundBlurView.view.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, width), height: 42.0)), completion: nil)
}
/*node.backgroundBlurNode.update(size: node.backgroundBlurNode.bounds.size, cornerRadius: 0.0, animator: animation.animator)
node.backgroundBlurNode.updateColor(color: selectDateFillStaticColor(theme: theme.theme, wallpaper: theme.wallpaper), enableBlur: context.sharedContext.energyUsageSettings.fullTranslucency && dateFillNeedsBlur(theme: theme.theme, wallpaper: theme.wallpaper), transition: .immediate)*/
if backgroundNode?.hasExtraBubbleBackground() == true {
if node.backgroundContent == nil, let backgroundContent = backgroundNode?.makeBubbleBackground(for: .free) {
backgroundContent.clipsToBounds = true
@ -401,7 +410,6 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
node.backgroundBlurView?.view.isHidden = false
}
let rect = CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, width), height: 42.0))
let maskPath: CGPath?
var needsMask = true
@ -435,6 +443,40 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
node.layer.mask = nil
}
}
if let color = button.style?.color {
let backgroundColorView: UIImageView
if let current = node.backgroundColorView {
backgroundColorView = current
} else {
backgroundColorView = UIImageView()
node.backgroundColorView = backgroundColorView
if let iconNode = node.iconNode, iconNode.view.superview != nil {
node.view.insertSubview(backgroundColorView, belowSubview: iconNode.view)
} else if let titleNode = node.titleNode, titleNode.view.superview != nil {
node.view.insertSubview(backgroundColorView, belowSubview: titleNode.view)
} else if let iconView = node.icon?.view, iconView.superview != nil {
node.view.insertSubview(backgroundColorView, belowSubview: iconView)
} else {
node.view.addSubview(backgroundColorView)
}
}
animation.animator.updateFrame(layer: backgroundColorView.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, width), height: 42.0)), completion: nil)
switch color {
case .primary:
backgroundColorView.backgroundColor = UIColor(rgb: 0x0067D7, alpha: 0.55)
case .danger:
backgroundColorView.backgroundColor = UIColor(rgb: 0xD5000C, alpha: 0.55)
case .success:
backgroundColorView.backgroundColor = UIColor(rgb: 0x127E2D, alpha: 0.55)
}
} else if let backgroundColorView = node.backgroundColorView {
node.backgroundColorView = nil
backgroundColorView.removeFromSuperview()
}
if iconImage != nil {
if node.iconNode == nil {
@ -459,7 +501,10 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
}
var titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.size.width) / 2.0), y: floor((42.0 - titleSize.size.height) / 2.0) + 1.0), size: titleSize.size)
if let image = node.iconNode?.image, customInfo?.icon != nil {
if button.style?.iconFileId != nil {
titleFrame.origin.x = floorToScreenPixels((width - titleSize.size.width - emojiIconSize.width - emojiIconSpacing) * 0.5) + emojiIconSize.width + emojiIconSpacing
} else if let image = node.iconNode?.image, customInfo?.icon != nil {
if customInfo?.icon == .actionArrow {
titleFrame.origin.x = floorToScreenPixels((width - titleSize.size.width - image.size.width + 1.0) * 0.5) - 0.0
} else {
@ -486,6 +531,56 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
animation.animator.updateFrame(layer: iconNode.layer, frame: iconFrame, completion: nil)
}
if let iconFileId = button.style?.iconFileId {
let icon: ComponentView<Empty>
var iconTransition = animation.animator
if let current = node.icon {
icon = current
} else {
iconTransition = ListViewItemUpdateAnimation.None.animator
icon = ComponentView()
node.icon = icon
}
var animationContent: EmojiStatusComponent.AnimationContent = .customEmoji(fileId: iconFileId)
if let file = message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: iconFileId)] as? TelegramMediaFile {
animationContent = .file(file: file)
}
let _ = icon.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
content: .animation(
content: animationContent,
size: emojiIconSize,
placeholderColor: theme.theme.overallDarkAppearance ? UIColor(white: 1.0, alpha: 0.1) : UIColor(white: 0.0, alpha: 0.1),
themeColor: titleColor,
loopMode: .count(0)
),
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: emojiIconSize
)
let contentX = titleFrame.origin.x - emojiIconSize.width - emojiIconSpacing
let iconFrame = CGRect(origin: CGPoint(x: contentX, y: floor((42.0 - emojiIconSize.height) * 0.5) - 1.0), size: emojiIconSize)
if let iconView = icon.view {
if iconView.superview == nil {
node.view.addSubview(iconView)
}
iconTransition.updateFrame(layer: iconView.layer, frame: iconFrame, completion: nil)
}
} else if let icon = node.icon {
node.icon = nil
icon.view?.removeFromSuperview()
}
if let (rect, size) = node.absolutePosition {
node.updateAbsoluteRect(rect, within: size)
}

View file

@ -1410,11 +1410,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
ReplyMarkupMessageAttribute(
rows: [
ReplyMarkupRow(buttons: [
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)),
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove))
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline), style: ReplyMarkupButton.Style(color: .danger, iconFileId: nil)),
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove), style: ReplyMarkupButton.Style(color: .success, iconFileId: nil))
]),
ReplyMarkupRow(buttons: [
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges))
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges), style: nil)
])
],
flags: [],

View file

@ -2848,7 +2848,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
ReplyMarkupMessageAttribute(
rows: [
ReplyMarkupRow(
buttons: [ReplyMarkupButton(title: item.presentationData.strings.Channel_AdminLog_ShowMoreMessages(Int32(messages.count - 1)), titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: MemoryBuffer(data: Data())))]
buttons: [ReplyMarkupButton(title: item.presentationData.strings.Channel_AdminLog_ShowMoreMessages(Int32(messages.count - 1)), titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: MemoryBuffer(data: Data())), style: nil)]
)
],
flags: [],
@ -2887,8 +2887,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
ReplyMarkupMessageAttribute(
rows: [
ReplyMarkupRow(buttons: [
ReplyMarkupButton(title: item.presentationData.strings.Chat_GiftPurchaseOffer_Reject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)),
ReplyMarkupButton(title: item.presentationData.strings.Chat_GiftPurchaseOffer_Accept, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove))
ReplyMarkupButton(title: item.presentationData.strings.Chat_GiftPurchaseOffer_Reject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline), style: ReplyMarkupButton.Style(color: .danger, iconFileId: nil)),
ReplyMarkupButton(title: item.presentationData.strings.Chat_GiftPurchaseOffer_Accept, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove), style: ReplyMarkupButton.Style(color: .success, iconFileId: nil))
])
],
flags: [],
@ -2936,11 +2936,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
ReplyMarkupMessageAttribute(
rows: [
ReplyMarkupRow(buttons: [
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)),
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove))
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline), style: ReplyMarkupButton.Style(color: .danger, iconFileId: nil)),
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove), style: ReplyMarkupButton.Style(color: .success, iconFileId: nil))
]),
ReplyMarkupRow(buttons: [
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges))
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges), style: nil)
])
],
flags: [],
@ -2974,7 +2974,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
ReplyMarkupMessageAttribute(
rows: [
ReplyMarkupRow(buttons: [
ReplyMarkupButton(title: item.presentationData.strings.Chat_MessageContinueLastThread, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: button))
ReplyMarkupButton(title: item.presentationData.strings.Chat_MessageContinueLastThread, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: button), style: nil)
])
],
flags: [],

View file

@ -725,11 +725,18 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} else {
ribbonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_Gift
}
buttonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_View
modelTitle = item.presentationData.strings.Notification_StarGift_Model
backdropTitle = item.presentationData.strings.Notification_StarGift_Backdrop
symbolTitle = item.presentationData.strings.Notification_StarGift_Symbol
if uniqueGift.flags.contains(.isBurned) {
ribbonTitle = item.presentationData.strings.Notification_StarGift_Burned
customRibbonColors = [UIColor(rgb: 0xd9433a), UIColor(rgb: 0xff645b)]
buttonTitle = ""
}
for attribute in uniqueGift.attributes {
switch attribute {
case let .model(name, file, _, _):

View file

@ -897,11 +897,11 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
ReplyMarkupMessageAttribute(
rows: [
ReplyMarkupRow(buttons: [
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)),
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove))
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline), style: ReplyMarkupButton.Style(color: .danger, iconFileId: nil)),
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove), style: ReplyMarkupButton.Style(color: .success, iconFileId: nil))
]),
ReplyMarkupRow(buttons: [
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges))
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges), style: nil)
])
],
flags: [],

View file

@ -1422,10 +1422,13 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded)
let defaultGlassTintColor: GlassBackgroundView.TintColor
let defaultGlassTintWithInnerColor: GlassBackgroundView.TintColor
if case .clear = interfaceState.preferredGlassType {
defaultGlassTintColor = .init(kind: .clear)
defaultGlassTintWithInnerColor = .init(kind: .clear, innerColor: interfaceState.theme.list.itemCheckColors.fillColor)
} else {
defaultGlassTintColor = .init(kind: .panel)
defaultGlassTintWithInnerColor = .init(kind: .panel, innerColor: interfaceState.theme.list.itemCheckColors.fillColor)
}
var leftInset = leftInset
@ -2372,7 +2375,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
let menuButtonFrame = CGRect(x: leftInset + 8.0, y: menuButtonOriginY, width: menuButtonExpanded ? menuButtonWidth : menuCollapsedButtonWidth, height: menuButtonHeight)
transition.updateFrameAsPositionAndBounds(node: self.menuButton, frame: menuButtonFrame)
transition.updateFrame(view: self.menuButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size))
self.menuButtonBackgroundView.update(size: menuButtonFrame.size, cornerRadius: menuButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: defaultGlassTintColor, transition: ComponentTransition(transition))
self.menuButtonBackgroundView.update(size: menuButtonFrame.size, cornerRadius: menuButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: defaultGlassTintWithInnerColor, transition: ComponentTransition(transition))
transition.updateFrame(node: self.menuButtonClippingNode, frame: CGRect(origin: CGPoint(x: 19.0, y: 0.0), size: CGSize(width: menuButtonWidth - 19.0, height: menuButtonFrame.height)))
var menuButtonTitleTransition = transition
if buttonTitleUpdated {

View file

@ -45,7 +45,9 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView {
private var activities: ChatTitleComponent.Activities?
private var networkState: AccountNetworkState?
private var ignoreParentTransitionRequests: Bool = false
public var requestUpdate: ((ContainedViewLayoutTransition) -> Void)?
public var tapAction: (() -> Void)?
public var longTapAction: (() -> Void)?
@ -82,8 +84,10 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView {
dateTimeFormat: PresentationDateTimeFormat,
nameDisplayOrder: PresentationPersonNameOrder,
content: ChatTitleContent,
transition: ComponentTransition
transition: ComponentTransition,
ignoreParentTransitionRequests: Bool = false
) {
self.ignoreParentTransitionRequests = ignoreParentTransitionRequests
self.contentData = ContentData(
context: context,
theme: theme,
@ -95,6 +99,7 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView {
content: content
)
self.update(transition: transition)
self.ignoreParentTransitionRequests = false
}
public func updateActivities(activities: ChatTitleComponent.Activities?, transition: ComponentTransition) {
@ -112,7 +117,9 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView {
}
private func update(transition: ComponentTransition) {
self.requestUpdate?(transition.containedViewLayoutTransition)
if !self.ignoreParentTransitionRequests {
self.requestUpdate?(transition.containedViewLayoutTransition)
}
}
public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {

View file

@ -299,7 +299,7 @@ final class CraftTableComponent: Component {
diameter: 84.0,
lineWidth: 5.0,
fontSize: 18.0,
progress: CGFloat(permilleValue / 10 / 100),
progress: CGFloat(permilleValue) / 10.0 / 100.0,
value: permilleValue / 10,
suffix: "%",
isVisible: !component.isCrafting

View file

@ -460,7 +460,7 @@ private final class CraftGiftPageContent: Component {
},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
)
craftContentHeight += 276.0
craftContentHeight += 291.0
craftContentHeight += descriptionTextSize.height
craftContentHeight += 25.0
@ -566,7 +566,7 @@ private final class CraftGiftPageContent: Component {
diameter: 48.0,
lineWidth: 4.0,
fontSize: 10.0,
progress: CGFloat(backdropPermille / 10 / 100),
progress: CGFloat(backdropPermille) / 10.0 / 100.0,
value: backdropPermille / 10,
suffix: "%"
)
@ -615,7 +615,7 @@ private final class CraftGiftPageContent: Component {
contentSize: CGSize(width: 28.0, height: 28.0),
lineWidth: 4.0,
fontSize: 10.0,
progress: CGFloat(symbolPermille / 10 / 100),
progress: CGFloat(symbolPermille) / 10.0 / 100.0,
value: symbolPermille / 10,
suffix: "%"
)
@ -1763,8 +1763,15 @@ private final class SheetContainerComponent: CombinedComponent {
}
default:
if let navigationController = controller()?.navigationController {
var text: String = strings.Login_UnknownError
switch error {
case .unavailable:
text = strings.Gift_Craft_Error_NotAvailable
default:
break
}
dismiss(true)
let alertController = textAlertController(context: component.context, title: nil, text: strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
let alertController = textAlertController(context: component.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])
(navigationController.topViewController as? ViewController)?.present(alertController, in: .window(.root))
}
}

View file

@ -1220,7 +1220,7 @@ final class GiftStoreScreenComponent: Component {
}
let tonBalance = tonContext.currentState?.balance.value ?? 0
if tonBalance == 0 {
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: tonContext)
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: starsContext)
controller.push(controller)
return
}
@ -1242,7 +1242,7 @@ final class GiftStoreScreenComponent: Component {
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(
text: "My Stars",
text: presentationData.strings.Gift_Store_Balance_MyStars,
textLayout: .secondLineWithValue(formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat)),
icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stars"), color: theme.contextMenu.primaryColor) },
action: { [weak self] _, f in
@ -1256,7 +1256,7 @@ final class GiftStoreScreenComponent: Component {
)))
items.append(.action(ContextMenuActionItem(
text: "My TON",
text: presentationData.strings.Gift_Store_Balance_MyTon,
textLayout: .secondLineWithValue(formatTonAmountText(tonBalance, dateTimeFormat: presentationData.dateTimeFormat)),
icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Ton"), color: theme.contextMenu.primaryColor) },
action: { [weak self] _, f in

View file

@ -1013,6 +1013,10 @@ private final class GiftViewSheetContent: CombinedComponent {
.single(gift.themePeerId)
|> then(
context.engine.payments.getUniqueStarGift(slug: gift.slug)
|> map(Optional.init)
|> `catch` { _ -> Signal<StarGift.UniqueGift?, NoError> in
return .single(nil)
}
|> map { gift in
return gift?.themePeerId
}

View file

@ -69,17 +69,20 @@ public final class GlassControlGroupComponent: Component {
}
public let theme: PresentationTheme
public let preferClearGlass: Bool
public let background: Background
public let items: [Item]
public let minWidth: CGFloat
public init(
theme: PresentationTheme,
preferClearGlass: Bool,
background: Background,
items: [Item],
minWidth: CGFloat
) {
self.theme = theme
self.preferClearGlass = preferClearGlass
self.background = background
self.items = items
self.minWidth = minWidth
@ -89,6 +92,9 @@ public final class GlassControlGroupComponent: Component {
if lhs.theme !== rhs.theme {
return false
}
if lhs.preferClearGlass != rhs.preferClearGlass {
return false
}
if lhs.background != rhs.background {
return false
}
@ -151,13 +157,13 @@ public final class GlassControlGroupComponent: Component {
switch component.background {
case .panel:
foregroundColor = component.theme.chat.inputPanel.panelControlColor
tintColor = .init(kind: .panel)
tintColor = .init(kind: component.preferClearGlass ? .clear : .panel)
case .activeTint:
foregroundColor = component.theme.list.itemCheckColors.foregroundColor
tintColor = .init(kind: .panel, innerColor: component.theme.list.itemCheckColors.fillColor)
tintColor = .init(kind: component.preferClearGlass ? .clear : .panel, innerColor: component.theme.list.itemCheckColors.fillColor)
case let .color(color):
foregroundColor = .white
tintColor = .init(kind: .custom(style: .default, color: color))
tintColor = .init(kind: .custom(style: component.preferClearGlass ? .clear : .default, color: color))
}
var contentsWidth: CGFloat = 0.0

View file

@ -32,6 +32,7 @@ public final class GlassControlPanelComponent: Component {
}
public let theme: PresentationTheme
public let preferClearGlass: Bool
public let leftItem: Item?
public let rightItem: Item?
public let centralItem: Item?
@ -41,6 +42,7 @@ public final class GlassControlPanelComponent: Component {
public init(
theme: PresentationTheme,
preferClearGlass: Bool = false,
leftItem: Item?,
centralItem: Item?,
rightItem: Item?,
@ -49,6 +51,7 @@ public final class GlassControlPanelComponent: Component {
tag: AnyObject? = nil
) {
self.theme = theme
self.preferClearGlass = preferClearGlass
self.leftItem = leftItem
self.centralItem = centralItem
self.rightItem = rightItem
@ -61,6 +64,9 @@ public final class GlassControlPanelComponent: Component {
if lhs.theme !== rhs.theme {
return false
}
if lhs.preferClearGlass != rhs.preferClearGlass {
return false
}
if lhs.leftItem != rhs.leftItem {
return false
}
@ -149,6 +155,7 @@ public final class GlassControlPanelComponent: Component {
transition: leftItemTransition,
component: AnyComponent(GlassControlGroupComponent(
theme: component.theme,
preferClearGlass: component.preferClearGlass,
background: leftItem.background,
items: leftItem.items,
minWidth: availableSize.height
@ -198,6 +205,7 @@ public final class GlassControlPanelComponent: Component {
transition: rightItemTransition,
component: AnyComponent(GlassControlGroupComponent(
theme: component.theme,
preferClearGlass: component.preferClearGlass,
background: rightItem.background,
items: rightItem.items,
minWidth: availableSize.height
@ -264,6 +272,7 @@ public final class GlassControlPanelComponent: Component {
transition: centralItemTransition,
component: AnyComponent(GlassControlGroupComponent(
theme: component.theme,
preferClearGlass: component.preferClearGlass,
background: centralItem.background,
items: centralItem.items,
minWidth: centralItem.keepWide ? 165.0 : availableSize.height

View file

@ -1008,6 +1008,14 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
}
}
if (leftTitleInset == leftInset) != (rightTitleInset == rightInset) {
if rightTitleInset == rightInset {
rightTitleInset = leftTitleInset
} else if leftTitleInset == leftInset {
leftTitleInset = rightTitleInset
}
}
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))

View file

@ -0,0 +1,29 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AccountPeerContextItem",
module_name = "AccountPeerContextItem",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/PresentationDataUtils",
"//submodules/TelegramPresentationData",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/AvatarNode",
"//submodules/ContextUI",
"//submodules/AccountContext",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,174 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import ComponentFlow
import TelegramCore
import TelegramPresentationData
import PresentationDataUtils
import ContextUI
import AvatarNode
import EmojiStatusComponent
import AccountContext
public final class AccountPeerContextItem: ContextMenuCustomItem {
let context: AccountContext
let account: Account
let peer: EnginePeer
let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
public 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 buttonNode: HighlightTrackingButtonNode
private let textNode: ImmediateTextNode
private let avatarNode: AvatarNode
private let emojiStatusView: ComponentView<Empty>
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.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<Empty>()
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.isAccessibilityElement = true
self.buttonNode.accessibilityLabel = peerTitle
super.init()
self.addSubnode(self.textNode)
self.addSubnode(self.avatarNode)
self.addSubnode(self.buttonNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 18.0
let iconSideInset: CGFloat = 20.0
let verticalInset: CGFloat = 11.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: iconSideInset + 40.0, 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: iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
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) {
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) {
}
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)
})
}
}

View file

@ -173,6 +173,7 @@ swift_library(
"//submodules/TelegramUI/Components/AvatarComponent",
"//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent",
"//submodules/TelegramUI/Components/HorizontalTabsComponent",
"//submodules/TelegramUI/Components/PeerInfo/AccountPeerContextItem",
],
visibility = [
"//visibility:public",

View file

@ -417,6 +417,7 @@ private final class PeerInfoPendingPane {
sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?,
initialStoryFolderId: Int64?,
initialGiftCollectionId: Int64?,
switchToMediaTarget: PeerInfoSwitchToMediaTarget?,
key: PeerInfoPaneKey,
hasBecomeReady: @escaping (PeerInfoPaneKey) -> Void,
parentController: ViewController?,
@ -520,7 +521,7 @@ private final class PeerInfoPendingPane {
openAddStory()
}
case .media:
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: chatLocationPeerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .photoOrVideo, captureProtected: captureProtected)
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: chatLocationPeerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .photoOrVideo, captureProtected: captureProtected, initialFocusMessageIndex: switchToMediaTarget?.kind == .photoVideo ? switchToMediaTarget?.messageIndex : nil)
paneNode = visualPaneNode
visualPaneNode.openCurrentDate = {
openMediaCalendar()
@ -529,15 +530,15 @@ private final class PeerInfoPendingPane {
paneDidScroll()
}
case .files:
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: chatLocationPeerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .files, captureProtected: captureProtected)
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: chatLocationPeerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .files, captureProtected: captureProtected, initialFocusMessageIndex: switchToMediaTarget?.kind == .photoVideo ? switchToMediaTarget?.messageIndex : nil)
paneNode = visualPaneNode
case .links:
paneNode = PeerInfoListPaneNode(context: context, updatedPresentationData: updatedPresentationData, chatControllerInteraction: chatControllerInteraction, peerId: chatLocationPeerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: .webPage)
case .voice:
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: chatLocationPeerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .voiceAndVideoMessages, captureProtected: captureProtected)
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: chatLocationPeerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .voiceAndVideoMessages, captureProtected: captureProtected, initialFocusMessageIndex: nil)
paneNode = visualPaneNode
case .music:
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: chatLocationPeerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .music, captureProtected: captureProtected)
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: chatLocationPeerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .music, captureProtected: captureProtected, initialFocusMessageIndex: nil)
paneNode = visualPaneNode
case .gifs:
let visualPaneNode = PeerInfoGifPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: chatLocationPeerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .gifs)
@ -626,6 +627,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
private var shouldFadeIn = false
private var initialStoryFolderId: Int64?
private var initialGiftCollectionId: Int64?
private var switchToMediaTarget: PeerInfoSwitchToMediaTarget?
private var isDraggingTabs: Bool = false
private var transitionFraction: CGFloat = 0.0
@ -653,7 +655,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
private let initialPaneKey: PeerInfoPaneKey?
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, chatLocation: ChatLocation, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, isMediaOnly: Bool, initialPaneKey: PeerInfoPaneKey?, initialStoryFolderId: Int64?, initialGiftCollectionId: Int64?) {
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, chatLocation: ChatLocation, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, isMediaOnly: Bool, initialPaneKey: PeerInfoPaneKey?, initialStoryFolderId: Int64?, initialGiftCollectionId: Int64?, switchToMediaTarget: PeerInfoSwitchToMediaTarget?) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.peerId = peerId
@ -664,6 +666,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
self.initialPaneKey = initialPaneKey
self.initialStoryFolderId = initialStoryFolderId
self.initialGiftCollectionId = initialGiftCollectionId
self.switchToMediaTarget = switchToMediaTarget
self.tabsBackgroundContainer = GlassBackgroundContainerView()
self.tabsBackgroundView = GlassBackgroundView()
@ -1002,6 +1005,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
var leftScope = false
var initialStoryFolderId: Int64?
var initialGiftCollectionId: Int64?
var switchToMediaTarget: PeerInfoSwitchToMediaTarget?
if case .stories = key {
if let initialStoryFolderIdValue = self.initialStoryFolderId {
self.initialStoryFolderId = nil
@ -1014,6 +1018,18 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
initialGiftCollectionId = initialGiftCollectionIdValue
}
}
if case .media = key {
if let switchToMediaTargetValue = self.switchToMediaTarget, case .photoVideo = switchToMediaTargetValue.kind {
self.switchToMediaTarget = nil
switchToMediaTarget = switchToMediaTargetValue
}
}
if case .files = key {
if let switchToMediaTargetValue = self.switchToMediaTarget, case .file = switchToMediaTargetValue.kind {
self.switchToMediaTarget = nil
switchToMediaTarget = switchToMediaTargetValue
}
}
let pane = PeerInfoPendingPane(
context: self.context,
updatedPresentationData: self.updatedPresentationData,
@ -1034,6 +1050,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
sharedMediaFromForumTopic: self.sharedMediaFromForumTopic,
initialStoryFolderId: initialStoryFolderId,
initialGiftCollectionId: initialGiftCollectionId,
switchToMediaTarget: switchToMediaTarget,
key: key,
hasBecomeReady: { [weak self] key in
let apply: () -> Void = {

View file

@ -114,6 +114,7 @@ import GiftViewScreen
import PeerMessagesMediaPlaylist
import EdgeEffect
import Pasteboard
import AccountPeerContextItem
public enum PeerInfoAvatarEditingMode {
case generic
@ -212,13 +213,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let chatLocation: ChatLocation
let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
let switchToStoryFolder: Int64?
let switchToMediaTarget: PeerInfoSwitchToMediaTarget?
let switchToGiftsTarget: PeerInfoSwitchToGiftsTarget?
let sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?
let isSettings: Bool
let isMyProfile: Bool
let isMediaOnly: Bool
let initialExpandPanes: Bool
var initialExpandPanes: Bool
private(set) var presentationData: PresentationData
@ -345,7 +347,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
private var didSetReady = false
init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, profileGiftsContext: ProfileGiftsContext?, starsContext: StarsContext?, tonContext: StarsContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, switchToGiftsTarget: PeerInfoSwitchToGiftsTarget?, switchToStoryFolder: Int64?, initialPaneKey: PeerInfoPaneKey?, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?) {
init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, profileGiftsContext: ProfileGiftsContext?, starsContext: StarsContext?, tonContext: StarsContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, switchToGiftsTarget: PeerInfoSwitchToGiftsTarget?, switchToStoryFolder: Int64?, switchToMediaTarget: PeerInfoSwitchToMediaTarget?, initialPaneKey: PeerInfoPaneKey?, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?) {
self.controller = controller
self.context = context
self.peerId = peerId
@ -362,6 +364,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.isMediaOnly = context.account.peerId == peerId && !isSettings && !isMyProfile
self.initialExpandPanes = initialPaneKey != nil
self.switchToStoryFolder = switchToStoryFolder
self.switchToMediaTarget = switchToMediaTarget
self.switchToGiftsTarget = switchToGiftsTarget
self.sharedMediaFromForumTopic = sharedMediaFromForumTopic
@ -385,7 +388,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
break
}
self.paneContainerNode = PeerInfoPaneContainerNode(context: context, updatedPresentationData: controller.updatedPresentationData, peerId: peerId, chatLocation: chatLocation, sharedMediaFromForumTopic: sharedMediaFromForumTopic, chatLocationContextHolder: chatLocationContextHolder, isMediaOnly: self.isMediaOnly, initialPaneKey: initialPaneKey, initialStoryFolderId: switchToStoryFolder, initialGiftCollectionId: switchToGiftCollection)
self.paneContainerNode = PeerInfoPaneContainerNode(context: context, updatedPresentationData: controller.updatedPresentationData, peerId: peerId, chatLocation: chatLocation, sharedMediaFromForumTopic: sharedMediaFromForumTopic, chatLocationContextHolder: chatLocationContextHolder, isMediaOnly: self.isMediaOnly, initialPaneKey: initialPaneKey, initialStoryFolderId: switchToStoryFolder, initialGiftCollectionId: switchToGiftCollection, switchToMediaTarget: switchToMediaTarget)
super.init()
@ -2826,6 +2829,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.ignoreScrolling = true
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: paneAreaExpansionFinalPoint), size: self.scrollNode.bounds.size))
self.ignoreScrolling = false
self.headerNode.headerEdgeEffectContainer.center = CGPoint(x: 0.0, y: self.scrollNode.view.contentOffset.y)
self.updateNavigation(transition: transition, additive: false, animateHeader: true)
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: transition, additive: true)
@ -5747,7 +5751,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
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)
self.paneContainerNode.update(size: self.paneContainerNode.bounds.size, sideInset: layout.safeInsets.left, topInset: paneContainerTopInset, bottomInset: bottomInset, deviceMetrics: layout.deviceMetrics, visibleHeight: visibleHeight, expansionFraction: self.initialExpandPanes ? 1.0 : 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
@ -6217,6 +6221,21 @@ public enum PeerInfoSwitchToGiftsTarget {
case collection(Int64)
}
public struct PeerInfoSwitchToMediaTarget {
public enum Kind {
case photoVideo
case file
}
public let kind: Kind
public let messageIndex: EngineMessage.Index
public init(kind: Kind, messageIndex: EngineMessage.Index) {
self.kind = kind
self.messageIndex = messageIndex
}
}
public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortcutResponder {
let context: AccountContext
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
@ -6237,6 +6256,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
private let switchToGiftsTarget: PeerInfoSwitchToGiftsTarget?
private let switchToGroupsInCommon: Bool
private let switchToStoryFolder: Int64?
private let switchToMediaTarget: PeerInfoSwitchToMediaTarget?
private let sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?
let chatLocation: ChatLocation
private let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
@ -6320,6 +6340,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
switchToGiftsTarget: PeerInfoSwitchToGiftsTarget? = nil,
switchToGroupsInCommon: Bool = false,
switchToStoryFolder: Int64? = nil,
switchToMediaTarget: PeerInfoSwitchToMediaTarget? = nil,
) {
self.context = context
self.updatedPresentationData = updatedPresentationData
@ -6338,6 +6359,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
self.switchToGiftsTarget = switchToGiftsTarget
self.switchToGroupsInCommon = switchToGroupsInCommon
self.switchToStoryFolder = switchToStoryFolder
self.switchToMediaTarget = switchToMediaTarget
self.sharedMediaFromForumTopic = sharedMediaFromForumTopic
if let forumTopicThread = forumTopicThread {
@ -6683,8 +6705,15 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
initialPaneKey = .groupsInCommon
} else if self.switchToStoryFolder != nil {
initialPaneKey = .stories
} else if let switchToMediaTarget = self.switchToMediaTarget {
switch switchToMediaTarget.kind {
case .photoVideo:
initialPaneKey = .media
case .file:
initialPaneKey = .files
}
}
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, profileGiftsContext: self.profileGiftsContext, starsContext: self.starsContext, tonContext: self.tonContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, switchToGiftsTarget: self.switchToGiftsTarget, switchToStoryFolder: self.switchToStoryFolder, initialPaneKey: initialPaneKey, sharedMediaFromForumTopic: self.sharedMediaFromForumTopic)
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, profileGiftsContext: self.profileGiftsContext, starsContext: self.starsContext, tonContext: self.tonContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, switchToGiftsTarget: self.switchToGiftsTarget, switchToStoryFolder: self.switchToStoryFolder, switchToMediaTarget: self.switchToMediaTarget, initialPaneKey: initialPaneKey, sharedMediaFromForumTopic: self.sharedMediaFromForumTopic)
self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 })
self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get())
self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get())
@ -6933,6 +6962,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
}
self.controllerNode.refreshHasPersonalChannelsIfNeeded()
self.controllerNode.initialExpandPanes = false
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {

View file

@ -89,167 +89,6 @@ extension PeerInfoScreenNode {
}
}
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 buttonNode: HighlightTrackingButtonNode
private let textNode: ImmediateTextNode
private let avatarNode: AvatarNode
private let emojiStatusView: ComponentView<Empty>
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.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<Empty>()
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.isAccessibilityElement = true
self.buttonNode.accessibilityLabel = peerTitle
super.init()
self.addSubnode(self.textNode)
self.addSubnode(self.avatarNode)
self.addSubnode(self.buttonNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 18.0
let iconSideInset: CGFloat = 20.0
let verticalInset: CGFloat = 11.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: iconSideInset + 40.0, 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: iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
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) {
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) {
}
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

View file

@ -1113,6 +1113,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
private let directMediaImageCache: DirectMediaImageCache
private var items: SparseItemGrid.Items?
private var didUpdateItemsOnce: Bool = false
private var initialMessageIndex: EngineMessage.Index?
private var isDeceleratingAfterTracking = false
@ -1164,7 +1165,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
public init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, contentType: ContentType, captureProtected: Bool) {
public init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, contentType: ContentType, captureProtected: Bool, initialFocusMessageIndex: EngineMessage.Index?) {
self.context = context
self.peerId = peerId
self.chatLocation = chatLocation
@ -1173,6 +1174,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
self.contentType = contentType
self.contentTypePromise = ValuePromise<ContentType>(contentType)
self.stateTag = tagMaskForType(contentType)
self.initialMessageIndex = initialFocusMessageIndex
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
@ -1234,7 +1236,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
threadId = message.threadId
}
self.listSource = self.context.engine.messages.sparseMessageList(peerId: self.peerId, threadId: threadId, tag: tagMaskForType(self.contentType))
self.listSource = self.context.engine.messages.sparseMessageList(peerId: self.peerId, threadId: threadId, tag: tagMaskForType(self.contentType), initialMessageIndex: initialFocusMessageIndex)
if threadId == nil {
switch contentType {
case .photoOrVideo, .photo, .video:
@ -1247,6 +1249,11 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
}
super.init()
if self.initialMessageIndex != nil {
self.didSetReady = true
self.ready.set(.single(true))
}
let _ = (ApplicationSpecificNotice.getSharedMediaScrollingTooltip(accountManager: context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak self] count in
@ -1720,6 +1727,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
}
self.isRequestingView = true
var firstTime = true
var firstTimeRealData = true
let queue = Queue()
self.listDisposable.set((self.listSource.state
@ -1728,15 +1736,20 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
var mappedItems: [SparseItemGrid.Item] = []
var mappedHoles: [SparseItemGrid.HoleAnchor] = []
for item in list.items {
switch item.content {
case let .message(message, isLocal):
mappedItems.append(VisualMediaItem(index: item.index, message: message, localMonthTimestamp: Month(localTimestamp: message.timestamp + timezoneOffset).packedValue))
if !isLocal {
mappedHoles.append(VisualMediaHoleAnchor(index: item.index, messageId: message.id, localMonthTimestamp: Month(localTimestamp: message.timestamp + timezoneOffset).packedValue))
var totalCount = list.totalCount
if list.items.isEmpty && list.isLoading && list.totalCount == 0 {
totalCount = 100
} else {
for item in list.items {
switch item.content {
case let .message(message, isLocal):
mappedItems.append(VisualMediaItem(index: item.index, message: message, localMonthTimestamp: Month(localTimestamp: message.timestamp + timezoneOffset).packedValue))
if !isLocal {
mappedHoles.append(VisualMediaHoleAnchor(index: item.index, messageId: message.id, localMonthTimestamp: Month(localTimestamp: message.timestamp + timezoneOffset).packedValue))
}
case let .placeholder(id, timestamp):
mappedHoles.append(VisualMediaHoleAnchor(index: item.index, messageId: id, localMonthTimestamp: Month(localTimestamp: timestamp + timezoneOffset).packedValue))
}
case let .placeholder(id, timestamp):
mappedHoles.append(VisualMediaHoleAnchor(index: item.index, messageId: id, localMonthTimestamp: Month(localTimestamp: timestamp + timezoneOffset).packedValue))
}
}
@ -1748,7 +1761,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
let items = SparseItemGrid.Items(
items: mappedItems,
holeAnchors: mappedHoles,
count: list.totalCount,
count: totalCount,
itemBinding: strongSelf.itemGridBinding,
headerText: nil,
snapTopInset: true
@ -1756,24 +1769,34 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
let currentSynchronous = synchronous && firstTime
let currentReloadAtTop = reloadAtTop && firstTime
var crossfade = currentReloadAtTop
if firstTimeRealData && !list.isLoading {
firstTimeRealData = false
if !firstTime {
crossfade = true
}
}
firstTime = false
strongSelf.updateHistory(items: items, synchronous: currentSynchronous, reloadAtTop: currentReloadAtTop)
strongSelf.updateHistory(items: items, synchronous: currentSynchronous, crossfade: crossfade)
strongSelf.isRequestingView = false
}
}))
}
private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) {
private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, crossfade: Bool) {
self.items = items
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
var gridSnapshot: UIView?
if reloadAtTop {
if crossfade {
gridSnapshot = self.itemGrid.view.snapshotView(afterScreenUpdates: false)
}
self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: false, transition: .immediate)
if let gridSnapshot = gridSnapshot {
self.view.addSubview(gridSnapshot)
self.itemGrid.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
gridSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak gridSnapshot] _ in
gridSnapshot?.removeFromSuperview()
})
@ -2194,6 +2217,23 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
}
self.itemGrid.update(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), useSideInsets: !isList, scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), lockScrollingAtTop: isScrollingLockedAtTop, fixedItemHeight: fixedItemHeight, fixedItemAspect: nil, items: items, theme: self.itemGridBinding.chatPresentationData.theme.theme, synchronous: wasFirstTime ? .full : .none)
if let initialMessageIndexValue = self.initialMessageIndex, items.items.contains(where: { item in
if let _ = item as? VisualMediaItem {
return true
} else {
return false
}
}) {
self.initialMessageIndex = nil
for item in items.items {
if let item = item as? VisualMediaItem {
if item.message.index <= initialMessageIndexValue {
self.itemGrid.scrollToItem(at: item.index, force: true)
break
}
}
}
}
}
}

View file

@ -28,6 +28,7 @@ swift_library(
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/Markdown",
"//submodules/TelegramUI/Components/LottieComponent",
],
visibility = [
"//visibility:public",

View file

@ -2,11 +2,12 @@ import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramCore
import ViewControllerComponent
import AccountContext
import SheetComponent
import ButtonComponent
import TelegramCore
import LottieComponent
private final class ArchiveInfoSheetContentComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -36,6 +37,9 @@ private final class ArchiveInfoSheetContentComponent: Component {
private let content = ComponentView<Empty>()
private let button = ComponentView<Empty>()
fileprivate let playButtonAnimation = ActionSlot<Void>()
private var didPlayAnimation = false
private var component: ArchiveInfoSheetContentComponent?
override init(frame: CGRect) {
@ -76,16 +80,34 @@ private final class ArchiveInfoSheetContentComponent: Component {
contentHeight += contentSize.height
contentHeight += 30.0
var buttonTitle: [AnyComponentWithIdentity<Empty>] = []
buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: "anim_ok"),
color: environment.theme.list.itemCheckColors.foregroundColor,
startingPosition: .begin,
size: CGSize(width: 28.0, height: 28.0),
playOnce: playButtonAnimation
))))
buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent(
text: environment.strings.ArchiveInfo_CloseAction,
badge: 0,
textColor: environment.theme.list.itemCheckColors.foregroundColor,
badgeBackground: environment.theme.list.itemCheckColors.foregroundColor,
badgeForeground: environment.theme.list.itemCheckColors.fillColor
))))
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)
),
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
Text(text: environment.strings.ArchiveInfo_CloseAction, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
content: AnyComponentWithIdentity(id: "ok", component: AnyComponent(
HStack(buttonTitle, spacing: 2.0)
)),
isEnabled: true,
displaysProgress: false,
@ -97,20 +119,19 @@ private final class ArchiveInfoSheetContentComponent: 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)
self.playButtonAnimation.invoke(Void())
}
transition.setFrame(view: buttonView, frame: buttonFrame)
}
contentHeight += buttonSize.height
let bottomPanelPadding: CGFloat = 12.0
let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding
contentHeight += bottomInset
contentHeight += buttonInsets.bottom
return CGSize(width: availableSize.width, height: contentHeight)
}
@ -224,6 +245,7 @@ private final class ArchiveInfoScreenComponent: Component {
})
}
)),
style: .glass,
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
animateOut: self.sheetAnimateOut
)),

View file

@ -448,7 +448,7 @@ public class WallpaperGalleryController: ViewController {
}, currentItemNode: { [weak self] in
return self?.galleryNode.pager.centralItemNode()
})
self.displayNode = WallpaperGalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction, pageGap: 0.0, disableTapNavigation: true)
self.displayNode = WallpaperGalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction, titleView: nil, pageGap: 0.0, disableTapNavigation: true)
self.displayNodeDidLoad()
(self.displayNode as? WallpaperGalleryControllerNode)?.nativeStatusBar = self.statusBar

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "openicon.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -456,6 +456,25 @@ func updateChatPresentationInterfaceStateImpl(
selfController.tempHideAccessoryPanels = selfController.presentationInterfaceState.search != nil
if let chatTitleContent = selfController.contentData?.state.chatTitleContent {
var titleTransition = ComponentTransition(transition)
if case .messageOptions = selfController.subject {
titleTransition = titleTransition.withAnimation(.none)
}
selfController.chatTitleView?.update(
context: selfController.context,
theme: selfController.presentationData.theme,
preferClearGlass: selfController.presentationInterfaceState.preferredGlassType == .clear,
wallpaper: selfController.presentationInterfaceState.chatWallpaper,
strings: selfController.presentationData.strings,
dateTimeFormat: selfController.presentationData.dateTimeFormat,
nameDisplayOrder: selfController.presentationData.nameDisplayOrder,
content: chatTitleContent,
transition: titleTransition,
ignoreParentTransitionRequests: true
)
}
if selfController.isNodeLoaded {
selfController.chatDisplayNode.updateChatPresentationInterfaceState(updatedChatPresentationInterfaceState, transition: transition, interactive: interactive, completion: completion)
} else {
@ -609,23 +628,5 @@ func updateChatPresentationInterfaceStateImpl(
}
}
if let chatTitleContent = selfController.contentData?.state.chatTitleContent {
var titleTransition = ComponentTransition(transition)
if case .messageOptions = selfController.subject {
titleTransition = titleTransition.withAnimation(.none)
}
selfController.chatTitleView?.update(
context: selfController.context,
theme: selfController.presentationData.theme,
preferClearGlass: selfController.presentationInterfaceState.preferredGlassType == .clear,
wallpaper: selfController.presentationInterfaceState.chatWallpaper,
strings: selfController.presentationData.strings,
dateTimeFormat: selfController.presentationData.dateTimeFormat,
nameDisplayOrder: selfController.presentationData.nameDisplayOrder,
content: chatTitleContent,
transition: titleTransition
)
}
selfController.stateUpdated?(transition)
}

View file

@ -1204,15 +1204,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.push(controller)
return true
case .starGift, .starGiftUnique:
let controller = self.context.sharedContext.makeGiftViewScreen(context: self.context, message: EngineMessage(message), shareStory: { [weak self] uniqueGift in
Queue.mainQueue().after(0.15) {
if let self {
let controller = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .gift(uniqueGift), parentController: self)
self.push(controller)
if case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = action.action, case let .unique(uniqueGift) = gift, uniqueGift.flags.contains(.isBurned) {
self.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: self.presentationData.strings.Resolve_GiftErrorBurned, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} else {
let controller = self.context.sharedContext.makeGiftViewScreen(context: self.context, message: EngineMessage(message), shareStory: { [weak self] uniqueGift in
Queue.mainQueue().after(0.15) {
if let self {
let controller = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .gift(uniqueGift), parentController: self)
self.push(controller)
}
}
}
})
self.push(controller)
})
self.push(controller)
}
return true
case .giftStars:
let controller = self.context.sharedContext.makeStarsGiftScreen(context: self.context, message: EngineMessage(message))
@ -1377,189 +1381,232 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
let openChatMessageParams = OpenChatMessageParams(context: context, updatedPresentationData: self.updatedPresentationData, chatLocation: openChatLocation, chatFilterTag: chatFilterTag, chatLocationContextHolder: self.chatLocationContextHolder, message: message, mediaIndex: params.mediaIndex, standalone: standalone, reverseMessageGalleryOrder: false, mode: mode, navigationController: self.effectiveNavigationController, dismissInput: { [weak self] in
self?.chatDisplayNode.dismissInput()
}, present: { [weak self] c, a, i in
guard let self else {
return
}
if case .current = i {
c.presentationArguments = a
c.statusBar.alphaUpdated = { [weak self] transition in
guard let self else {
return
}
self.updateStatusBarPresentation(animated: transition.isAnimated)
}
self.galleryPresentationContext.present(c, on: PresentationSurfaceLevel(rawValue: 0), blockInteraction: true, completion: {})
} else {
self.present(c, in: .window(.root), with: a, blockInteraction: true)
}
}, transitionNode: { [weak self] messageId, media, adjustRect in
var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
if let self {
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView {
if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: adjustRect) {
selectedNode = result
}
}
}
}
return selectedNode
}, addToTransitionSurface: { [weak self] view in
guard let self else {
return
}
if let contextController = self.currentContextController {
contextController.view.addSubview(view)
} else {
self.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: self.chatDisplayNode.historyNode.view)
}
}, openUrl: { [weak self] url in
self?.openUrl(url, concealed: false, skipConcealedAlert: isLocation, message: nil)
}, openPeer: { [weak self] peer, navigation in
self?.openPeer(peer: EnginePeer(peer), navigation: navigation, fromMessage: nil)
}, callPeer: { [weak self] peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId, isVideo)
}, openConferenceCall: { [weak self] message in
self?.controllerInteraction?.openConferenceCall(message)
}, enqueueMessage: { [weak self] message in
self?.sendMessages([message])
}, sendSticker: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] fileReference, sourceNode, sourceRect in
return self?.controllerInteraction?.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, []) ?? false
} : nil, sendEmoji: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] text, attribute in
self?.controllerInteraction?.sendEmoji(text, attribute, false)
} : nil, setupTemporaryHiddenMedia: { [weak self] signal, centralIndex, galleryMedia in
if let self {
self.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] entry in
if let self, let controllerInteraction = self.controllerInteraction {
var messageIdAndMedia: [MessageId: [Media]] = [:]
if let entry = entry as? InstantPageGalleryEntry, entry.index == centralIndex {
messageIdAndMedia[message.id] = [galleryMedia]
}
controllerInteraction.hiddenMedia = messageIdAndMedia
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateHiddenMedia()
}
}
}
}))
}
}, chatAvatarHiddenMedia: { [weak self] signal, media in
if let self {
self.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] messageId in
if let self, let controllerInteraction = self.controllerInteraction {
var messageIdAndMedia: [MessageId: [Media]] = [:]
if let messageId = messageId {
messageIdAndMedia[messageId] = [media]
}
controllerInteraction.hiddenMedia = messageIdAndMedia
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateHiddenMedia()
}
}
}
}))
}
}, actionInteraction: GalleryControllerActionInteraction(
openUrl: { [weak self] url, concealed, forceExternal in
if let self {
self.openUrl(url, concealed: concealed, forceExternal: forceExternal, message: nil)
}
}, openUrlIn: { [weak self] url in
if let self {
self.openUrlIn(url)
}
}, openPeerMention: { [weak self] mention in
if let self {
self.controllerInteraction?.openPeerMention(mention, nil)
}
}, openPeer: { [weak self] peer in
if let self {
self.controllerInteraction?.openPeer(peer, .default, nil, .default)
}
}, openHashtag: { [weak self] peerName, hashtag in
if let self {
self.controllerInteraction?.openHashtag(peerName, hashtag)
}
}, openBotCommand: { [weak self] command in
if let self {
self.controllerInteraction?.sendBotCommand(nil, command)
}
}, openAd: { [weak self] messageId in
if let self {
self.controllerInteraction?.activateAdAction(messageId, nil, true, true)
}
}, addContact: { [weak self] phoneNumber in
if let self {
self.controllerInteraction?.addContact(phoneNumber)
}
}, storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in
guard let self else {
return
}
var storedState: MediaPlaybackStoredState?
if let timestamp = timestamp {
storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: AudioPlaybackRate(playbackRate))
}
let _ = updateMediaPlaybackStoredStateInteractively(engine: self.context.engine, messageId: messageId, state: storedState).startStandalone()
}, editMedia: { [weak self] messageId, snapshots, transitionCompletion in
let openChatMessageParams = OpenChatMessageParams(
context: context,
updatedPresentationData: self.updatedPresentationData,
chatLocation: openChatLocation,
chatFilterTag: chatFilterTag,
chatLocationContextHolder: self.chatLocationContextHolder,
message: message,
mediaIndex: params.mediaIndex,
standalone: standalone,
reverseMessageGalleryOrder: false,
mode: mode,
navigationController: self.effectiveNavigationController, dismissInput: { [weak self] in
self?.chatDisplayNode.dismissInput()
},
present: { [weak self] c, a, i in
guard let self else {
return
}
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
|> deliverOnMainQueue).startStandalone(next: { [weak self] message in
guard let self, let message = message else {
return
if case .current = i {
c.presentationArguments = a
c.statusBar.alphaUpdated = { [weak self] transition in
guard let self else {
return
}
self.updateStatusBarPresentation(animated: transition.isAnimated)
}
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)
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let image = content.image {
mediaReference = AnyMediaReference.standalone(media: image)
} else if let file = content.file {
mediaReference = AnyMediaReference.standalone(media: file)
self.galleryPresentationContext.present(c, on: PresentationSurfaceLevel(rawValue: 0), blockInteraction: true, completion: {})
} else {
self.present(c, in: .window(.root), with: a, blockInteraction: true)
}
},
transitionNode: { [weak self] messageId, media, adjustRect in
var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
if let self {
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView {
if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: adjustRect) {
selectedNode = result
}
}
}
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
legacyMediaEditor(context: self.context, peer: peer, threadTitle: self.contentData?.state.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: {
transitionCompletion()
}, getCaptionPanelView: { [weak self] in
return self?.getCaptionPanelView(isFile: false)
}, sendMessagesWithSignals: { [weak self] signals, _, _, isCaptionAbove in
if let self {
var parameters: ChatSendMessageActionSheetController.SendParameters?
if isCaptionAbove {
parameters = ChatSendMessageActionSheetController.SendParameters(effect: nil, textIsAboveMedia: true)
}
self.enqueueMediaMessages(signals: signals, silentPosting: false, parameters: parameters)
}
return selectedNode
},
addToTransitionSurface: { [weak self] view in
guard let self else {
return
}
if let contextController = self.currentContextController {
contextController.view.addSubview(view)
} else {
self.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: self.chatDisplayNode.historyNode.view)
}
},
openUrl: { [weak self] url in
self?.openUrl(url, concealed: false, skipConcealedAlert: isLocation, message: nil)
},
openPeer: { [weak self] peer, navigation in
self?.openPeer(peer: EnginePeer(peer), navigation: navigation, fromMessage: nil)
},
callPeer: { [weak self] peerId, isVideo in
self?.controllerInteraction?.callPeer(peerId, isVideo)
},
openConferenceCall: { [weak self] message in
self?.controllerInteraction?.openConferenceCall(message)
},
enqueueMessage: { [weak self] message in
self?.sendMessages([message])
},
sendSticker: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] fileReference, sourceNode, sourceRect in
return self?.controllerInteraction?.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, []) ?? false
} : nil,
sendEmoji: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] text, attribute in
self?.controllerInteraction?.sendEmoji(text, attribute, false)
} : nil,
setupTemporaryHiddenMedia: { [weak self] signal, centralIndex, galleryMedia in
if let self {
self.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] entry in
if let self, let controllerInteraction = self.controllerInteraction {
var messageIdAndMedia: [MessageId: [Media]] = [:]
if let entry = entry as? InstantPageGalleryEntry, entry.index == centralIndex {
messageIdAndMedia[message.id] = [galleryMedia]
}
}, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
})
controllerInteraction.hiddenMedia = messageIdAndMedia
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateHiddenMedia()
}
}
}
}))
}
},
chatAvatarHiddenMedia: { [weak self] signal, media in
if let self {
self.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] messageId in
if let self, let controllerInteraction = self.controllerInteraction {
var messageIdAndMedia: [MessageId: [Media]] = [:]
if let messageId = messageId {
messageIdAndMedia[messageId] = [media]
}
controllerInteraction.hiddenMedia = messageIdAndMedia
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateHiddenMedia()
}
}
}
}))
}
},
actionInteraction: GalleryControllerActionInteraction(
openUrl: { [weak self] url, concealed, forceExternal in
if let self {
self.openUrl(url, concealed: concealed, forceExternal: forceExternal, message: nil)
}
})
}, updateCanReadHistory: { [weak self] canReadHistory in
self?.canReadHistory.set(canReadHistory)
}),
},
openUrlIn: { [weak self] url in
if let self {
self.openUrlIn(url)
}
},
openPeerMention: { [weak self] mention in
if let self {
self.controllerInteraction?.openPeerMention(mention, nil)
}
},
openPeer: { [weak self] peer in
if let self {
self.controllerInteraction?.openPeer(peer, .default, nil, .default)
}
},
openHashtag: { [weak self] peerName, hashtag in
if let self {
self.controllerInteraction?.openHashtag(peerName, hashtag)
}
},
openBotCommand: { [weak self] command in
if let self {
self.controllerInteraction?.sendBotCommand(nil, command)
}
},
openAd: { [weak self] messageId in
if let self {
self.controllerInteraction?.activateAdAction(messageId, nil, true, true)
}
},
addContact: { [weak self] phoneNumber in
if let self {
self.controllerInteraction?.addContact(phoneNumber)
}
},
storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in
guard let self else {
return
}
var storedState: MediaPlaybackStoredState?
if let timestamp = timestamp {
storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: AudioPlaybackRate(playbackRate))
}
let _ = updateMediaPlaybackStoredStateInteractively(engine: self.context.engine, messageId: messageId, state: storedState).startStandalone()
},
editMedia: { [weak self] messageId, snapshots, transitionCompletion in
guard let self else {
return
}
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
|> deliverOnMainQueue).startStandalone(next: { [weak self] message in
guard let 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)
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let image = content.image {
mediaReference = AnyMediaReference.standalone(media: image)
} else if let file = content.file {
mediaReference = AnyMediaReference.standalone(media: file)
}
}
}
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
legacyMediaEditor(context: self.context, peer: peer, threadTitle: self.contentData?.state.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: {
transitionCompletion()
}, getCaptionPanelView: { [weak self] in
return self?.getCaptionPanelView(isFile: false)
}, sendMessagesWithSignals: { [weak self] signals, _, _, isCaptionAbove in
if let self {
var parameters: ChatSendMessageActionSheetController.SendParameters?
if isCaptionAbove {
parameters = ChatSendMessageActionSheetController.SendParameters(effect: nil, textIsAboveMedia: true)
}
self.enqueueMediaMessages(signals: signals, silentPosting: false, parameters: parameters)
}
}, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
})
}
})
},
updateCanReadHistory: { [weak self] canReadHistory in
self?.canReadHistory.set(canReadHistory)
}
),
navigateToMessageContext: { [weak self] message in
Task {
guard let self else {
return
}
await self.openMediaMessageContext(message: message)
}
},
getSourceRect: { [weak self] in
guard let self else {
return nil
@ -2112,7 +2159,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let context = self?.context, let navigationController = self?.effectiveNavigationController {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone()
}
}, tapMessage: nil, clickThroughMessage: { [weak self] view, location in
}, tapMessage: nil,
clickThroughMessage: { [weak self] view, location in
self?.chatDisplayNode.dismissInput(view: view, location: location)
}, toggleMessagesSelection: { [weak self] ids, value in
guard let strongSelf = self, strongSelf.isNodeLoaded else {

View file

@ -137,4 +137,36 @@ extension ChatControllerImpl {
}
}
}
func openMediaMessageContext(message: EngineMessage) async {
guard let peer = await self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId)
).get() else {
return
}
var kind: PeerInfoControllerMode.PeerInfoMediaKind?
if message.tags.contains(MessageTags.photoOrVideo) {
kind = .photoVideo
} else if message.tags.contains(MessageTags.file) {
kind = .file
}
guard let kind else {
return
}
let peerInfoController = self.context.sharedContext.makePeerInfoController(
context: self.context,
updatedPresentationData: self.updatedPresentationData,
peer: peer._asPeer(),
mode: .media(kind: kind, messageIndex: message.index),
avatarInitiallyExpanded: false,
fromChat: true,
requestsContext: nil
)
if let peerInfoController {
self.push(peerInfoController)
}
}
}

View file

@ -358,6 +358,10 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
return nil
})
gallery.navigateToMessageContext = { message in
params.navigateToMessageContext?(message)
}
params.present(gallery, arguments, presentInCurrent ? .current : .window(.root))
})
return true

View file

@ -1632,33 +1632,36 @@ func openResolvedUrlImpl(
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
})
case let .collectible(gift):
if let gift {
var dismissedImpl: (() -> Void)?
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
let updateExternalController = storyProgressPauseContext.update
dismissedImpl = {
updateExternalController(nil)
}
case let .collectible(result):
switch result {
case let .gift(gift):
var dismissedImpl: (() -> Void)?
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
let updateExternalController = storyProgressPauseContext.update
dismissedImpl = {
updateExternalController(nil)
}
let controller = context.sharedContext.makeGiftViewScreen(context: context, gift: gift, shareStory: { [weak navigationController] uniqueGift in
Queue.mainQueue().after(0.15) {
if let lastController = navigationController?.viewControllers.last as? ViewController {
let controller = context.sharedContext.makeStorySharingScreen(context: context, subject: .gift(gift), parentController: lastController)
navigationController?.pushViewController(controller)
}
}
}, openChatTheme: nil, dismissed: {
dismissedImpl?()
})
navigationController?.pushViewController(controller)
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
storyProgressPauseContext.update(controller)
}
} else {
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
let controller = context.sharedContext.makeGiftViewScreen(context: context, gift: gift, shareStory: { [weak navigationController] uniqueGift in
Queue.mainQueue().after(0.15) {
if let lastController = navigationController?.viewControllers.last as? ViewController {
let controller = context.sharedContext.makeStorySharingScreen(context: context, subject: .gift(gift), parentController: lastController)
navigationController?.pushViewController(controller)
}
}
}, openChatTheme: nil, dismissed: {
dismissedImpl?()
})
navigationController?.pushViewController(controller)
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
storyProgressPauseContext.update(controller)
}
case .alreadyBurned:
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Resolve_GiftErrorBurned, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
case .invalidSlug:
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Resolve_GiftErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
case let .auction(auctionContext):
if let auctionContext, case let .generic(gift) = auctionContext.gift {
if !auctionContext.isFinished, let currentBidPeerId = auctionContext.currentBidPeerId {
@ -1818,8 +1821,27 @@ func openResolvedUrlImpl(
if case .request = result {
var dismissImpl: (() -> Void)?
let controller = AuthConfirmationScreen(context: context, subject: result, completion: { allowWriteAccess, sharePhoneNumber in
let _ = context.engine.messages.acceptMessageActionUrlAuth(subject: .url(url), allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber).start(next: { _ in
let _ = (context.engine.messages.acceptMessageActionUrlAuth(subject: .url(url), allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber)
|> deliverOnMainQueue).start(next: { _ in
dismissImpl?()
Queue.mainQueue().after(0.3) {
let text: String
if case let .request(domain, _, _, flags) = result {
if flags.contains(.requestPhoneNumber) && !sharePhoneNumber {
text = presentationData.strings.AuthConfirmation_LoginSuccess_TextNoNumber(domain).string
} else {
text = presentationData.strings.AuthConfirmation_LoginSuccess_Text(domain).string
}
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AuthConfirmation_LoginSuccess_Title, text: text, cancel: nil, destructive: false), action: { _ in return true })
(navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root))
}
}
}, error: { _ in
if case let .request(domain, _, _, _) = result {
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AuthConfirmation_LoginFail_Title, text: presentationData.strings.AuthConfirmation_LoginFail_Text(domain).string, cancel: nil, destructive: false), action: { _ in return true })
(navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root))
}
})
})
navigationController?.pushViewController(controller)

View file

@ -4114,8 +4114,23 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {
var switchToMediaTarget: PeerInfoSwitchToMediaTarget?
switch mode {
case let .media(kind, index):
let mappedKind: PeerInfoSwitchToMediaTarget.Kind
switch kind {
case .photoVideo:
mappedKind = .photoVideo
case .file:
mappedKind = .file
}
switchToMediaTarget = PeerInfoSwitchToMediaTarget(kind: mappedKind, messageIndex: index)
default:
break
}
if let _ = peer as? TelegramGroup {
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [])
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], switchToMediaTarget: switchToMediaTarget)
} else if let _ = peer as? TelegramChannel {
var forumTopicThread: ChatReplyThreadMessage?
var switchToRecommendedChannels = false
@ -4138,7 +4153,7 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
default:
break
}
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], forumTopicThread: forumTopicThread, switchToRecommendedChannels: switchToRecommendedChannels, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder)
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], forumTopicThread: forumTopicThread, switchToRecommendedChannels: switchToRecommendedChannels, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder, switchToMediaTarget: switchToMediaTarget)
} else if peer is TelegramUser {
var nearbyPeerDistance: Int32?
var reactionSourceMessageId: MessageId?
@ -4191,9 +4206,9 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
default:
break
}
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, sharedMediaFromForumTopic: sharedMediaFromForumTopic, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder)
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, sharedMediaFromForumTopic: sharedMediaFromForumTopic, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder, switchToMediaTarget: switchToMediaTarget)
} else if peer is TelegramSecretChat {
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [])
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], switchToMediaTarget: switchToMediaTarget)
}
return nil
}

View file

@ -28,6 +28,12 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) {
@interface CALayerSpringParametersOverrideParameters : NSObject
- (instancetype _Nonnull)init;
@end
@interface CALayerSpringParametersOverrideParametersSpring : CALayerSpringParametersOverrideParameters
@property (nonatomic, readonly) CGFloat stiffness;
@property (nonatomic, readonly) CGFloat damping;
@property (nonatomic, readonly) double duration;
@ -36,6 +42,15 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) {
@end
@interface CALayerSpringParametersOverrideParametersCustomCurve : CALayerSpringParametersOverrideParameters
@property (nonatomic, readonly) CGPoint cp1;
@property (nonatomic, readonly) CGPoint cp2;
- (instancetype _Nonnull)initWithCp1:(CGPoint)cp1 cp2:(CGPoint)cp2;
@end
@interface CALayerSpringParametersOverride : NSObject
@property (nonatomic, strong, readonly) CALayerSpringParametersOverrideParameters * _Nullable parameters;

View file

@ -115,18 +115,42 @@ static bool notyfyingShiftState = false;
@implementation CALayerSpringParametersOverrideParameters
- (instancetype _Nonnull)initWithStiffness:(CGFloat)stiffness damping:(CGFloat)damping duration:(double)duration; {
- (instancetype _Nonnull)init {
self = [super init];
if (self != nil) {
_stiffness = stiffness;
_damping = damping;
_duration = duration;
}
return self;
}
@end
@implementation CALayerSpringParametersOverrideParametersSpring
- (instancetype _Nonnull)initWithStiffness:(CGFloat)stiffness damping:(CGFloat)damping duration:(double)duration {
self = [super init];
if (self != nil) {
_stiffness = stiffness;
_damping = damping;
}
return self;
}
@end
@implementation CALayerSpringParametersOverrideParametersCustomCurve
- (instancetype _Nonnull)initWithCp1:(CGPoint)cp1 cp2:(CGPoint)cp2 {
self = [super init];
if (self != nil) {
_cp1 = cp1;
_cp2 = cp2;
}
return self;
}
@end
@implementation CALayerSpringParametersOverride
- (instancetype _Nonnull)initWithParameters:(CALayerSpringParametersOverrideParameters * _Nullable)parameters {
@ -167,12 +191,13 @@ static NSMutableArray<CALayerSpringParametersOverride *> *currentSpringParameter
if (currentSpringParametersOverrideStack().count != 0 && [anim isKindOfClass:[CASpringAnimation class]]) {
CALayerSpringParametersOverride *overrideData = [currentSpringParametersOverrideStack() lastObject];
if (overrideData) {
if (overrideData.parameters) {
if ([overrideData.parameters isKindOfClass:[CALayerSpringParametersOverrideParametersSpring class]]) {
CALayerSpringParametersOverrideParametersSpring *parameters = (CALayerSpringParametersOverrideParametersSpring *)overrideData.parameters;
CABasicAnimation *sourceAnimation = (CABasicAnimation *)anim;
CASpringAnimation *animation = makeSpringBounceAnimationImpl(sourceAnimation.keyPath, 0.0, overrideData.parameters.damping);
CASpringAnimation *animation = makeSpringBounceAnimationImpl(sourceAnimation.keyPath, 0.0, parameters.damping);
animation.stiffness = overrideData.parameters.stiffness;
animation.stiffness = parameters.stiffness;
animation.fromValue = sourceAnimation.fromValue;
animation.toValue = sourceAnimation.toValue;
animation.byValue = sourceAnimation.byValue;
@ -189,7 +214,34 @@ static NSMutableArray<CALayerSpringParametersOverride *> *currentSpringParameter
if (k != 0.0 && k != 1.0) {
speed = 1.0f / k;
}
animation.speed = sourceAnimation.speed * (float)(animation.duration / overrideData.parameters.duration);
animation.speed = sourceAnimation.speed * (float)(animation.duration / parameters.duration);
updatedAnimation = animation;
} else if ([overrideData.parameters isKindOfClass:[CALayerSpringParametersOverrideParametersCustomCurve class]]) {
CALayerSpringParametersOverrideParametersCustomCurve *parameters = (CALayerSpringParametersOverrideParametersCustomCurve *)overrideData.parameters;
CABasicAnimation *sourceAnimation = (CABasicAnimation *)anim;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:sourceAnimation.keyPath];
animation.fromValue = sourceAnimation.fromValue;
animation.toValue = sourceAnimation.toValue;
animation.byValue = sourceAnimation.byValue;
animation.additive = sourceAnimation.additive;
animation.duration = sourceAnimation.duration;
animation.timingFunction = [[CAMediaTimingFunction alloc] initWithControlPoints:parameters.cp1.x :parameters.cp1.y :parameters.cp2.x :parameters.cp2.y];
animation.removedOnCompletion = sourceAnimation.isRemovedOnCompletion;
animation.fillMode = sourceAnimation.fillMode;
animation.speed = sourceAnimation.speed;
animation.beginTime = sourceAnimation.beginTime;
animation.timeOffset = sourceAnimation.timeOffset;
animation.repeatCount = sourceAnimation.repeatCount;
animation.autoreverses = sourceAnimation.autoreverses;
float k = animationDurationFactorImpl();
float speed = 1.0f;
if (k != 0.0 && k != 1.0) {
speed = 1.0f / k;
}
animation.speed = speed * sourceAnimation.speed;
updatedAnimation = animation;
} else {

View file

@ -846,7 +846,14 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
if let peer {
return .single(peer)
} else {
return context.engine.peers.findChannelById(channelId: monoforumId.id._internalGetInt64Value())
return context.engine.peers.fetchAndUpdateCachedPeerData(peerId: channel.id)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
if result {
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: monoforumId))
} else {
return .single(nil)
}
}
}
}
|> map { peer -> ResolveInternalUrlResult in
@ -1209,8 +1216,16 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
case let .collectible(slug):
return .single(.progress) |> then(context.engine.payments.getUniqueStarGift(slug: slug)
|> map { gift -> ResolveInternalUrlResult in
return .result(.collectible(gift: gift))
return .result(.collectible(.gift(gift)))
})
|> `catch` { error -> Signal<ResolveInternalUrlResult, NoError> in
switch error {
case .alreadyBurned:
return .single(.result(.collectible(.alreadyBurned)))
default:
return .single(.result(.collectible(.invalidSlug)))
}
}
case let .auction(slug):
if let giftAuctionsManager = context.giftAuctionsManager {
return .single(.progress) |> then(giftAuctionsManager.auctionContext(for: .slug(slug))

View file

@ -82,10 +82,12 @@ class WebSearchGalleryController: ViewController {
private var centralEntryIndex: Int?
private let centralItemTitle = Promise<String>()
private let centralItemTitleView = Promise<UIView?>()
private let centralItemTitleContent = Promise<GalleryTitleView.Content?>()
private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>()
private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>()
private let centralItemAttributesDisposable = DisposableSet();
private let centralItemAttributesDisposable = DisposableSet()
private let titleView: GalleryTitleView
private let checkedDisposable = MetaDisposable()
private var checkNode: GalleryNavigationCheckNode?
@ -105,6 +107,8 @@ class WebSearchGalleryController: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.titleView = GalleryTitleView(context: context, presentationData: self.presentationData)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: WebSearchGalleryController.navigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
self.controllerInteraction = WebSearchGalleryControllerInteraction(dismiss: { [weak self] animated in
@ -151,8 +155,8 @@ class WebSearchGalleryController: ViewController {
self?.navigationItem.title = title
}))
self.centralItemAttributesDisposable.add(self.centralItemTitleView.get().start(next: { [weak self] titleView in
self?.navigationItem.titleView = titleView
self.centralItemAttributesDisposable.add(self.centralItemTitleContent.get().start(next: { [weak self] titleContent in
self?.titleView.setContent(content: titleContent)
}))
self.centralItemAttributesDisposable.add(self.centralItemFooterContentNode.get().start(next: { [weak self] footerContentNode, _ in
@ -232,7 +236,7 @@ class WebSearchGalleryController: ViewController {
}, currentItemNode: { [weak self] in
return self?.galleryNode.pager.centralItemNode()
})
self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction)
self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction, titleView: self.titleView)
self.displayNodeDidLoad()
self.galleryNode.statusBar = self.statusBar
@ -265,7 +269,7 @@ class WebSearchGalleryController: ViewController {
if let node = strongSelf.galleryNode.pager.centralItemNode() {
strongSelf.centralItemTitle.set(node.title())
strongSelf.centralItemTitleView.set(node.titleView())
strongSelf.centralItemTitleContent.set(node.titleContent())
strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
strongSelf.centralItemFooterContentNode.set(node.footerContent())
}
@ -317,7 +321,7 @@ class WebSearchGalleryController: ViewController {
if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? WebSearchGalleryControllerPresentationArguments {
self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemTitleContent.set(centralItemNode.titleContent())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
self.centralItemFooterContentNode.set(centralItemNode.footerContent())

View file

@ -43,7 +43,7 @@ final class WebSearchGalleryFooterContentNode: GalleryFooterContentNode {
}
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> LayoutInfo {
let width = size.width
let panelSize: CGFloat = 49.0
var panelHeight: CGFloat = panelSize + bottomInset
@ -52,7 +52,7 @@ final class WebSearchGalleryFooterContentNode: GalleryFooterContentNode {
self.cancelButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - panelSize), size: CGSize(width: panelSize, height: panelSize))
self.sendButton.frame = CGRect(origin: CGPoint(x: width - panelSize - rightInset, y: panelHeight - bottomInset - panelSize), size: CGSize(width: panelSize, height: panelSize))
return panelHeight
return LayoutInfo(height: panelHeight, needsShadow: false)
}
override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {