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

This commit is contained in:
Ilya Laktyushin 2026-01-30 19:45:23 +04:00
commit 7f75219a03
66 changed files with 2170 additions and 1008 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

@ -15751,3 +15751,6 @@ Error: %8$@";
"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

@ -738,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)
@ -753,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

@ -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

@ -107,32 +107,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 +150,18 @@ 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 {
ComponentTransition(transition).setAlpha(view: self.edgeEffectView, alpha: 1.0)
} else {
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

@ -205,7 +205,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
text = nil
entities = nil
}
return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, isUnclaimed: (flags & (1 << 5)) != 0, boostPeerId: boostPeer?.peerId, days: days, currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, text: text, entities: entities))
return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, isUnclaimed: (flags & (1 << 5)) != 0, boostPeerId: boostPeer?.peerId, months: days, currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, text: text, entities: entities))
case let .messageActionGiveawayLaunch(messageActionGiveawayLaunchData):
return TelegramMediaAction(action: .giveawayLaunched(stars: messageActionGiveawayLaunchData.stars))
case let .messageActionGiveawayResults(messageActionGiveawayResultsData):
@ -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])
@ -247,7 +279,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case requestedPeer(buttonId: Int32, peerIds: [PeerId])
case setChatWallpaper(wallpaper: TelegramWallpaper, forBoth: Bool)
case setSameChatWallpaper(wallpaper: TelegramWallpaper)
case giftCode(slug: String, fromGiveaway: Bool, isUnclaimed: Bool, boostPeerId: PeerId?, days: Int32, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?, text: String?, entities: [MessageTextEntity]?)
case giftCode(slug: String, fromGiveaway: Bool, isUnclaimed: Bool, boostPeerId: PeerId?, months: Int32, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?, text: String?, entities: [MessageTextEntity]?)
case giveawayLaunched(stars: Int64?)
case joinedChannel
case giveawayResults(winners: Int32, unclaimed: Int32, stars: Bool)
@ -269,6 +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 groupCreatorChange(GroupCreatorChange)
public init(decoder: PostboxDecoder) {
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
@ -374,7 +407,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case 35:
self = .botAppAccessGranted(appName: decoder.decodeOptionalStringForKey("app"), type: decoder.decodeOptionalInt32ForKey("atp").flatMap { BotSendMessageAccessGrantedType(rawValue: $0) })
case 36:
self = .giftCode(slug: decoder.decodeStringForKey("slug", orElse: ""), fromGiveaway: decoder.decodeBoolForKey("give", orElse: false), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: decoder.decodeOptionalInt64ForKey("pi").flatMap { PeerId($0) }, days: decoder.decodeInt32ForKey("days", orElse: decoder.decodeInt32ForKey("months", orElse: 0)), currency: decoder.decodeOptionalStringForKey("currency"), amount: decoder.decodeOptionalInt64ForKey("amount"), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"))
self = .giftCode(slug: decoder.decodeStringForKey("slug", orElse: ""), fromGiveaway: decoder.decodeBoolForKey("give", orElse: false), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: decoder.decodeOptionalInt64ForKey("pi").flatMap { PeerId($0) }, months: decoder.decodeInt32ForKey("months", orElse: 0), currency: decoder.decodeOptionalStringForKey("currency"), amount: decoder.decodeOptionalInt64ForKey("amount"), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"))
case 37:
self = .giveawayLaunched(stars: decoder.decodeOptionalInt64ForKey("stars"))
case 38:
@ -437,9 +470,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case 55:
self = .suggestedBirthday(decoder.decodeCodable(TelegramBirthday.self, forKey: "birthday") ?? TelegramBirthday(day: 1, month: 1, year: nil))
case 56:
self = .starGiftPurchaseOffer(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amount")!, expireDate: decoder.decodeInt32ForKey("expireDate", orElse: 0), isAccepted: decoder.decodeBoolForKey("isAccepted", orElse: false), isDeclined: decoder.decodeBoolForKey("isDeclined", orElse: false))
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")!, 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
}
@ -632,7 +667,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else {
encoder.encodeNil(forKey: "atp")
}
case let .giftCode(slug, fromGiveaway, unclaimed, boostPeerId, days, currency, amount, cryptoCurrency, cryptoAmount, text, entities):
case let .giftCode(slug, fromGiveaway, unclaimed, boostPeerId, months, currency, amount, cryptoCurrency, cryptoAmount, text, entities):
encoder.encodeInt32(36, forKey: "_rawValue")
encoder.encodeString(slug, forKey: "slug")
encoder.encodeBool(fromGiveaway, forKey: "give")
@ -642,7 +677,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else {
encoder.encodeNil(forKey: "pi")
}
encoder.encodeInt32(days, forKey: "days")
encoder.encodeInt32(months, forKey: "months")
if let currency = currency {
encoder.encodeString(currency, forKey: "currency")
} else {
@ -931,6 +966,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
encoder.encodeObject(gift, forKey: "gift")
encoder.encodeCodable(amount, forKey: "amount")
encoder.encodeBool(hasExpired, forKey: "hasExpired")
case let .groupCreatorChange(groupCreatorChange):
encoder.encodeInt32(59, forKey: "_rawValue")
encoder.encodeCodable(groupCreatorChange, forKey: "d")
}
}
@ -987,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

@ -180,7 +180,7 @@ public enum MessageActionUrlAuthResult {
public static let requestPhoneNumber = Flags(rawValue: 1 << 1)
}
public struct ClientData {
public struct ClientData : Equatable {
public let browser: String
public let platform: String
public let ip: String

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

@ -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

@ -471,6 +471,36 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
return 0
}
}
public var isPermille: Bool {
if case .permille = self {
return true
}
return false
}
public var badgeText: String {
switch self {
case let .permille(value):
if value == 0 {
return "<0.1%"
}
let percent = Double(value) / 10.0
if percent.truncatingRemainder(dividingBy: 1) == 0 {
return "\(Int(percent))%"
} else {
return String(format: "%.1f%%", percent)
}
case .rare:
return "rare"
case .epic:
return "epic"
case .legendary:
return "legendary"
case .uncommon:
return "uncommon"
}
}
}
case model(name: String, file: TelegramMediaFile, rarity: Rarity, crafted: Bool)
@ -1279,7 +1309,7 @@ extension StarGift {
} else if let ownerName {
owner = .name(ownerName)
} else {
owner = nil
owner = .none
}
let resellAmounts = apiResellAmount?.compactMap { CurrencyAmount(apiAmount: $0) }
var flags = StarGift.UniqueGift.Flags()

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

@ -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,63 @@ private final class ChatButtonKeyboardInputButtonNode: HighlightTrackingButtonNo
self.addSubnode(self.backgroundContainerNode)
self.backgroundContainerNode.addSubnode(self.backgroundColorNode)
self.backgroundContainerNode.addSubnode(self.backgroundAdditionalColorNode)
self.backgroundContainerNode.view.addSubview(self.backgroundView)
self.addSubnode(self.textNode)
self.backgroundContainerNode.addSubnode(self.highlightNode)
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) {
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
}
}
@ -129,71 +122,83 @@ 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 {
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 +209,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 +255,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 +281,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 +311,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 +326,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 +341,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

@ -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

@ -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

@ -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

@ -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

@ -213,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
@ -346,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
@ -363,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
@ -386,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()
@ -2827,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)
@ -5748,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
@ -6218,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>)?
@ -6238,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)
@ -6321,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
@ -6339,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 {
@ -6684,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())
@ -6934,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

@ -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

@ -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

@ -1377,189 +1377,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 +2155,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

@ -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

@ -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

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) {