chore: migrate to new version + fixed several critical bugs

- Migrated project to latest Telegram iOS base (v12.3.2+)
- Fixed circular dependency between GhostModeManager and MiscSettingsManager
- Fixed multiple Bazel build configuration errors (select() default conditions)
- Fixed duplicate type definitions in PeerInfoScreen
- Fixed swiftmodule directory resolution in build scripts
- Added Ghostgram Settings tab in main Settings menu with all 5 features
- Cleared sensitive credentials from config.json (template-only now)
- Excluded bazel-cache from version control
This commit is contained in:
ichmagmaus 812
2026-02-23 23:04:32 +01:00
parent 703e291bcb
commit db53826061
1017 changed files with 62337 additions and 40559 deletions
+11
View File
@@ -22,9 +22,20 @@ build --spawn_strategy=standalone
build --strategy=SwiftCompile=standalone
build --define RULES_SWIFT_BUILD_DUMMY_WORKER=1
build:swift_profile --jobs=1
build:swift_profile --local_cpu_resources=1
build:swift_profile --features=-swift.enable_batch_mode
build:swift_profile --experimental_ui_max_stdouterr_bytes=104857600
build:swift_profile --@build_bazel_rules_swift//swift:copt=-Xfrontend
build:swift_profile --@build_bazel_rules_swift//swift:copt=-warn-long-function-bodies=350
build:swift_profile --@build_bazel_rules_swift//swift:copt=-Xfrontend
build:swift_profile --@build_bazel_rules_swift//swift:copt=-warn-long-expression-type-checking=350
common:index_build --experimental_convenience_symlinks=ignore
common:index_build --bes_backend= --bes_results_url=
common:index_build --nolegacy_important_outputs
common:index_build --show_result=0
common:index_build --define=buildNumber=10000
common:index_build --define=telegramVersion=12.2.2
common --registry=https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/
+3 -8
View File
@@ -59,8 +59,6 @@ bazel-telegram-ios
bazel-telegram-ios/*
bazel-testlogs
bazel-testlogs/*
bazel-telegram-antidelete
bazel-telegram-antidelete/*
xcodeproj.bazelrc
*/*.swp
*.swp
@@ -76,10 +74,7 @@ buildServer.json
Telegram.LSP.json
**/.build/**
spm-files
xcode-files
.bsp/**
*.mobileprovision
build-system/fake-codesigning/profiles/*
*.backup
*_BACKUP*
/.claude/
/buildbox/*
+6 -1
View File
@@ -50,6 +50,11 @@ internal_testflight:
- python3 -u build-system/Make/Make.py remote-deploy-testflight --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
environment:
name: testflight_llc
artifacts:
when: always
paths:
- build/artifacts
expire_in: 1 week
appstore_development:
tags:
@@ -80,7 +85,7 @@ experimental_i:
- export PATH=/opt/homebrew/opt/ruby/bin:$PATH
- export PATH=`gem environment gemdir`/bin:$PATH
- python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appcenter-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=adhoc --configuration=release_arm64
- python3 -u build-system/Make/DeployToFirebase.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/firebase-configurations/firebase-internal.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
- python3 -u build-system/Make/Make.py remote-deploy-testflight --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip"
environment:
name: experimental
artifacts:
+2
View File
@@ -1,6 +1,8 @@
{
"backgroundIndexing": true,
"backgroundPreparationMode": "build",
"buildServerWorkspaceRequestsTimeout": 999,
"buildSettingsTimeout": 999,
"defaultWorkspaceType": "buildServer",
"logging": {
"level": "error",
+1 -1
View File
@@ -2,7 +2,7 @@ load("@sourcekit_bazel_bsp//rules:setup_sourcekit_bsp.bzl", "setup_sourcekit_bsp
setup_sourcekit_bsp(
name = "setup_sourcekit_bsp_telegram_project",
bazel_wrapper = "./build-input/bazel-8.4.2-darwin-arm64",
bazel_wrapper = "./build-input/bazel-8.4.2-darwin-x86_64",
files_to_watch = [
"**/*.swift",
],
+150 -150
View File
@@ -1,156 +1,156 @@
{
"lockFileVersion": 18,
"registryFileHashes": {
"https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
"https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
"https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0",
"https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb",
"https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16",
"https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915",
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da",
"https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
"https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
"https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d",
"https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d",
"https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
"https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
"https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65",
"https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d",
"https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
"https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b",
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
"https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
"https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686",
"https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a",
"https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5",
"https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d",
"https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651",
"https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138",
"https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
"https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d",
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953",
"https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
"https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
"https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb",
"https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4",
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d",
"https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
"https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74",
"https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9",
"https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
"https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
"https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29",
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
"https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
"https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d",
"https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
"https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
"https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c",
"https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d",
"https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df",
"https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92",
"https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e",
"https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981",
"https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
"https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e",
"https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022",
"https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206",
"https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4",
"https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8",
"https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e",
"https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
"https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002",
"https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191",
"https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac",
"https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
"https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
"https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0",
"https://bcr.bazel.build/modules/rules_cc/0.1.2/source.json": "53fcb09b5816c83ca60d9d7493faf3bfaf410dfc2f15deb52d6ddd146b8d43f0",
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
"https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
"https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86",
"https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39",
"https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6",
"https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31",
"https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a",
"https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6",
"https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
"https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2",
"https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
"https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615",
"https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc",
"https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
"https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
"https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
"https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d",
"https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4",
"https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0",
"https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197",
"https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59",
"https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3",
"https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5",
"https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
"https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
"https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c",
"https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb",
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a",
"https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
"https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73",
"https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2",
"https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1",
"https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f",
"https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300",
"https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382",
"https://bcr.bazel.build/modules/rules_python/0.27.1/MODULE.bazel": "65dc875cc1a06c30d5bbdba7ab021fd9e551a6579e408a3943a61303e2228a53",
"https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed",
"https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58",
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
"https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7",
"https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13",
"https://bcr.bazel.build/modules/rules_python/1.3.0/source.json": "25932f917cd279c7baefa6cb1d3fa8750a7a29de522024449b19af6eab51f4a0",
"https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
"https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b",
"https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3",
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
"https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
"https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef",
"https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c",
"https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7",
"https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5",
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91",
"https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_cc/0.1.2/source.json": "53fcb09b5816c83ca60d9d7493faf3bfaf410dfc2f15deb52d6ddd146b8d43f0",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.27.1/MODULE.bazel": "65dc875cc1a06c30d5bbdba7ab021fd9e551a6579e408a3943a61303e2228a53",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_python/1.3.0/source.json": "25932f917cd279c7baefa6cb1d3fa8750a7a29de522024449b19af6eab51f4a0",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
"https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
},
"selectedYankedVersions": {},
"moduleExtensions": {
+95 -51
View File
@@ -1,72 +1,116 @@
# 👻 Ghostgram iOS
# Telegram iOS Source Code Compilation Guide
<p align="center">
<img src="https://raw.githubusercontent.com/TelegramMessenger/Telegram-iOS/master/Telegram/Telegram-iOS/Images.xcassets/AppIcon.appiconset/Icon-60%403v.png" width="128" height="128" alt="Ghostgram Logo">
</p>
We welcome all developers to use our API and source code to create applications on our platform.
There are several things we require from **all developers** for the moment.
<p align="center">
<b>The ultimate Telegram fork for privacy, control, and freedom.</b>
</p>
# Creating your Telegram Application
<p align="center">
<a href="https://t.me/ceopoco"><img src="https://img.shields.io/badge/Telegram-Support-blue.svg?logo=telegram" alt="Telegram Support"></a>
<img src="https://img.shields.io/badge/Platform-iOS-lightgrey.svg" alt="Platform iOS">
<img src="https://img.shields.io/badge/License-GPLv2-green.svg" alt="License GPLv2">
</p>
1. [**Obtain your own api_id**](https://core.telegram.org/api/obtaining_api_id) for your application.
2. Please **do not** use the name Telegram for your app — or make sure your users understand that it is unofficial.
3. Kindly **do not** use our standard logo (white paper plane in a blue circle) as your app's logo.
3. Please study our [**security guidelines**](https://core.telegram.org/mtproto/security_guidelines) and take good care of your users' data and privacy.
4. Please remember to publish **your** code too in order to comply with the licences.
---
# Quick Compilation Guide
## 🌟 Key Features
## Get the Code
### 🛡️ Anti-Delete System
*Never miss a message again. Ghostgram keeps what others try to hide.*
- **Local Archive:** Automatically saves messages deleted by the sender for everyone.
- **Media Preservation:** Photos and videos from deleted messages are archived locally.
- **Edit History:** View original content of edited messages.
- **Visual Indicators:** Deleted messages are clearly marked with a 🗑️ icon.
```
git clone --recursive -j8 https://github.com/TelegramMessenger/Telegram-iOS.git
```
### 👤 Ghost Mode
- **Invisible Reading:** Read messages without sending "Read" receipts (single checkmark).
- **Stealth Stories:** View stories anonymously without appearing in the viewer list.
- **Hidden Input:** Hide "Typing..." status from your contacts.
- **Online Privacy:** Hide your online status or use **Force Offline** to always appear as "last seen recently".
## Setup Xcode
### 🎙️ Advanced Audio Tools
- **Free Transcription:** Get text from voice and video messages without Telegram Premium (On-device processing).
- **Voice Morpher:** Real-time voice changer for your outgoing audio messages.
Install Xcode (directly from https://developer.apple.com/download/applications or using the App Store).
### 🔓 Bypass Limits
- **Copy Protection Bypass:** Forward or save content from channels that have restricted it.
- **Infinite View-Once:** View-once media stays available as long as you need.
- **Secret Screenshots:** Take screenshots in secret chats without notification.
## Adjust Configuration
### 🛠️ More Features
- **Device Spoofing:** Change how your device appears to others.
- **Ad Blocker:** Say goodbye to sponsored messages in channels.
- **Always Online:** Keep your status active even when the app is in the background.
1. Generate a random identifier:
```
openssl rand -hex 8
```
2. Create a new Xcode project. Use `Telegram` as the Product Name. Use `org.{identifier from step 1}` as the Organization Identifier.
3. Open `Keychain Access` and navigate to `Certificates`. Locate `Apple Development: your@email.address (XXXXXXXXXX)` and double tap the certificate. Under `Details`, locate `Organizational Unit`. This is the Team ID.
4. Edit `build-system/template_minimal_development_configuration.json`. Use data from the previous steps.
---
## Generate an Xcode project
## 🛠 Installation & Building
```
python3 build-system/Make/Make.py \
--cacheDir="$HOME/telegram-bazel-cache" \
generateProject \
--configurationPath=build-system/template_minimal_development_configuration.json \
--xcodeManagedCodesigning
```
Ghostgram is a developer-centric fork. To build it from source, follow our detailed guide:
# Advanced Compilation Guide
📖 **[Build & Compilation Guide](BUILD.md)**
## Xcode
---
1. Copy and edit `build-system/appstore-configuration.json`.
2. Copy `build-system/fake-codesigning`. Create and download provisioning profiles, using the `profiles` folder as a reference for the entitlements.
3. Generate an Xcode project:
```
python3 build-system/Make/Make.py \
--cacheDir="$HOME/telegram-bazel-cache" \
generateProject \
--configurationPath=configuration_from_step_1.json \
--codesigningInformationPath=directory_from_step_2
```
## ⚠️ Disclaimer & Legal
## IPA
> **Educational Use Only:** This is a modified version of the Telegram client. Use it at your own risk. The developer is not responsible for any account bans or data loss.
1. Repeat the steps from the previous section. Use distribution provisioning profiles.
2. Run:
```
python3 build-system/Make/Make.py \
--cacheDir="$HOME/telegram-bazel-cache" \
build \
--configurationPath=...see previous section... \
--codesigningInformationPath=...see previous section... \
--buildNumber=100001 \
--configuration=release_arm64
```
- **Unofficial:** This project is not affiliated with, endorsed by, or in any way officially connected with Telegram Messenger Inc.
- **Terms of Service:** Using third-party clients may violate Telegram's ToS.
- **License:** Based on [Telegram-iOS](https://github.com/TelegramMessenger/Telegram-iOS), licensed under **GPLv2**.
# FAQ
---
## Xcode is stuck at "build-request.json not updated yet"
### 👨‍💻 Support & Community
Report issues or join the discussion:
- **Telegram:** [@ceopoco](https://t.me/ceosppw)
Occasionally, you might observe the following message in your build log:
```
"/Users/xxx/Library/Developer/Xcode/DerivedData/Telegram-xxx/Build/Intermediates.noindex/XCBuildData/xxx.xcbuilddata/build-request.json" not updated yet, waiting...
```
**Made with 🖤 for the community.**
Should this occur, simply cancel the ongoing build and initiate a new one.
## Telegram_xcodeproj: no such package
Following a system restart, the auto-generated Xcode project might encounter a build failure accompanied by this error:
```
ERROR: Skipping '@rules_xcodeproj_generated//generator/Telegram/Telegram_xcodeproj:Telegram_xcodeproj': no such package '@rules_xcodeproj_generated//generator/Telegram/Telegram_xcodeproj': BUILD file not found in directory 'generator/Telegram/Telegram_xcodeproj' of external repository @rules_xcodeproj_generated. Add a BUILD file to a directory to mark it as a package.
```
If you encounter this issue, re-run the project generation steps in the README.
# Tips
## Codesigning is not required for simulator-only builds
Add `--disableProvisioningProfiles`:
```
python3 build-system/Make/Make.py \
--cacheDir="$HOME/telegram-bazel-cache" \
generateProject \
--configurationPath=path-to-configuration.json \
--codesigningInformationPath=path-to-provisioning-data \
--disableProvisioningProfiles
```
## Versions
Each release is built using a specific Xcode version (see `versions.json`). The helper script checks the versions of the installed software and reports an error if they don't match the ones specified in `versions.json`. It is possible to bypass these checks:
```
python3 build-system/Make/Make.py --overrideXcodeVersion build ... # Don't check the version of Xcode
```
+3 -4
View File
@@ -8,8 +8,6 @@ load("@build_bazel_rules_apple//apple:ios.bzl",
"ios_application",
"ios_extension",
"ios_framework",
"ios_unit_test",
"ios_ui_test",
)
load("@build_bazel_rules_apple//apple:resources.bzl",
@@ -22,7 +20,6 @@ load("@build_bazel_rules_swift//swift:swift.bzl",
load(
"@rules_xcodeproj//xcodeproj:defs.bzl",
"top_level_target",
"top_level_targets",
"xcodeproj",
"xcode_provisioning_profile",
@@ -338,6 +335,7 @@ filegroup(
objc_library(
name = "Main",
module_name = "Main",
srcs = [
"Telegram-iOS/main.m"
],
@@ -345,6 +343,7 @@ objc_library(
swift_library(
name = "Lib",
module_name = "Lib",
srcs = glob([
"Telegram-iOS/Application.swift",
]),
@@ -1810,7 +1809,7 @@ ios_application(
#"//third-party/boringssl:ssl",
#"//third-party/boringssl:crypto",
#"//submodules/TelegramVoip",
#"//third-party/recaptcha:RecaptchaEnterprise",
#"//third-party/recaptcha:RecaptchaEnterpriseSDK",
"//submodules/TelegramUI",
],
)
@@ -5,4 +5,4 @@ APP_SPECIFIC_URL_SCHEME=tgapp
GLOBAL_CONSTANTS = APP_CONFIG_IS_INTERNAL_BUILD=false APP_CONFIG_IS_APPSTORE_BUILD=true APP_CONFIG_APPSTORE_ID=686449807 APP_SPECIFIC_URL_SCHEME="\"$(APP_SPECIFIC_URL_SCHEME)\""
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) $(GLOBAL_CONSTANTS)
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) APP_CONFIG_API_ID=YOUR_API_ID APP_CONFIG_API_HASH="\"YOUR_API_HASH\"" APP_CONFIG_HOCKEYAPP_ID="\"ad8831329ffc8f8aff9a2b0b86558b24\""
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) APP_CONFIG_API_ID=8 APP_CONFIG_API_HASH="\"7245de8e747a0d6fbe11f7cc14fcc0bb\"" APP_CONFIG_HOCKEYAPP_ID="\"ad8831329ffc8f8aff9a2b0b86558b24\""
+1 -1
View File
@@ -5,4 +5,4 @@ APP_SPECIFIC_URL_SCHEME=tgfork
GLOBAL_CONSTANTS = APP_CONFIG_IS_INTERNAL_BUILD=false APP_CONFIG_IS_APPSTORE_BUILD=true APP_CONFIG_APPSTORE_ID=0 APP_SPECIFIC_URL_SCHEME="\"$(APP_SPECIFIC_URL_SCHEME)\""
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) $(GLOBAL_CONSTANTS)
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) APP_CONFIG_API_ID=YOUR_API_ID APP_CONFIG_API_HASH="\"YOUR_API_HASH\"" APP_CONFIG_HOCKEYAPP_ID="\"\""
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) APP_CONFIG_API_ID=8 APP_CONFIG_API_HASH="\"7245de8e747a0d6fbe11f7cc14fcc0bb\"" APP_CONFIG_HOCKEYAPP_ID="\"\""
@@ -1,115 +1,116 @@
{
"images" : [
"images": [
{
"filename" : "BlueNotificationIcon@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
"filename": "GhostIcon@40x40.png",
"idiom": "iphone",
"scale": "2x",
"size": "20x20"
},
{
"filename" : "BlueNotificationIcon@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
"filename": "GhostIcon@60x60.png",
"idiom": "iphone",
"scale": "3x",
"size": "20x20"
},
{
"filename" : "Simple@58x58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
"filename": "GhostIcon@58x58.png",
"idiom": "iphone",
"scale": "2x",
"size": "29x29"
},
{
"filename" : "Simple@87x87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
"filename": "GhostIcon@87x87.png",
"idiom": "iphone",
"scale": "3x",
"size": "29x29"
},
{
"filename" : "Simple@80x80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
"filename": "GhostIcon@80x80.png",
"idiom": "iphone",
"scale": "2x",
"size": "40x40"
},
{
"filename" : "BlueIcon@2x-1.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
"filename": "GhostIcon@120x120.png",
"idiom": "iphone",
"scale": "3x",
"size": "40x40"
},
{
"filename" : "BlueIcon@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
"filename": "GhostIcon@120x120.png",
"idiom": "iphone",
"scale": "2x",
"size": "60x60"
},
{
"filename" : "BlueIcon@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
"filename": "GhostIcon@180x180.png",
"idiom": "iphone",
"scale": "3x",
"size": "60x60"
},
{
"filename" : "BlueNotificationIcon.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
"filename": "GhostIcon@20x20.png",
"idiom": "ipad",
"scale": "1x",
"size": "20x20"
},
{
"filename" : "BlueNotificationIcon@2x-1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
"filename": "GhostIcon@40x40.png",
"idiom": "ipad",
"scale": "2x",
"size": "20x20"
},
{
"filename" : "Simple@29x29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
"filename": "GhostIcon@29x29.png",
"idiom": "ipad",
"scale": "1x",
"size": "29x29"
},
{
"filename" : "Simple@58x58-1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
"filename": "GhostIcon@58x58.png",
"idiom": "ipad",
"scale": "2x",
"size": "29x29"
},
{
"filename" : "Simple@40x40-1.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
"filename": "GhostIcon@40x40.png",
"idiom": "ipad",
"scale": "1x",
"size": "40x40"
},
{
"filename" : "Simple@80x80-1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
"filename": "GhostIcon@80x80.png",
"idiom": "ipad",
"scale": "2x",
"size": "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
"filename": "GhostIcon@76x76.png",
"idiom": "ipad",
"scale": "1x",
"size": "76x76"
},
{
"filename" : "BlueIconIpad@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
"filename": "GhostIcon@152x152.png",
"idiom": "ipad",
"scale": "2x",
"size": "76x76"
},
{
"filename" : "BlueIconLargeIpad@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
"filename": "GhostIcon@167x167.png",
"idiom": "ipad",
"scale": "2x",
"size": "83.5x83.5"
},
{
"filename" : "Simple-iTunesArtwork.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
"filename": "GhostIcon@1024x1024.png",
"idiom": "ios-marketing",
"scale": "1x",
"size": "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
"info": {
"author": "xcode",
"version": 1
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 925 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

+5 -5
View File
@@ -110,17 +110,17 @@
<key>NSCameraUsageDescription</key>
<string>We need this so that you can take and share photos and videos.</string>
<key>NSContactsUsageDescription</key>
<string>Telegram stores your contacts heavily encrypted in the cloud to let you connect with your friends across all your devices.</string>
<string>Ghostgram stores your contacts heavily encrypted in the cloud to let you connect with your friends across all your devices.</string>
<key>NSFaceIDUsageDescription</key>
<string>You can use Face ID to unlock the app.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>When you send your location to your friends, Telegram needs access to show them a map. You also need this to send locations from an Apple Watch.</string>
<string>When you send your location to your friends, Ghostgram needs access to show them a map. You also need this to send locations from an Apple Watch.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>When you send your location to your friends, Telegram needs access to show them a map.</string>
<string>When you send your location to your friends, Ghostgram needs access to show them a map.</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need this so that you can record and share voice messages and videos with sound.</string>
<key>NSMotionUsageDescription</key>
<string>When you send your location to your friends, Telegram needs access to show them a map.</string>
<string>When you send your location to your friends, Ghostgram needs access to show them a map.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need this so that you can share photos and videos from your photo library.</string>
<key>NSPhotoLibraryUsageDescription</key>
@@ -188,7 +188,7 @@
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Telegram iOS Color Theme File</string>
<string>Ghostgram iOS Color Theme File</string>
<key>UTTypeIconFiles</key>
<array>
<string>BlueIcon@3x.png</string>
Binary file not shown.
@@ -1,12 +1,16 @@
/* Localized versions of Info.plist keys */
"NSContactsUsageDescription" = "Telegram will continuously upload your contacts to its heavily encrypted cloud servers to let you connect with your friends across all your devices.";
"NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Telegram needs access to show them a map.";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "When you choose to share your Live Location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing.";
"NSLocationAlwaysUsageDescription" = "When you choose to share your live location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing. You also need this to send locations from an Apple Watch.";
/* GHOSTGRAM: App display name */
"CFBundleDisplayName" = "Ghostgram";
"CFBundleName" = "Ghostgram";
"NSContactsUsageDescription" = "Ghostgram will continuously upload your contacts to its heavily encrypted cloud servers to let you connect with your friends across all your devices.";
"NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Ghostgram needs access to show them a map.";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "When you choose to share your Live Location with friends in a chat, Ghostgram needs background access to your location to keep them updated for the duration of the live sharing.";
"NSLocationAlwaysUsageDescription" = "When you choose to share your live location with friends in a chat, Ghostgram needs background access to your location to keep them updated for the duration of the live sharing. You also need this to send locations from an Apple Watch.";
"NSCameraUsageDescription" = "We need this so that you can take and share photos and videos, as well as make video calls.";
"NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library.";
"NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library.";
"NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library and save the ones you capture.";
"NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library and save the ones you capture.";
"NSMicrophoneUsageDescription" = "We need this so that you can record and share voice messages and videos with sound.";
"NSSiriUsageDescription" = "You can use Siri to send messages.";
"NSFaceIDUsageDescription" = "You can use Face ID to unlock the app.";
@@ -4856,12 +4856,9 @@ Sorry for the inconvenience.";
"Conversation.SendMessage.SetReminder" = "Set a Reminder";
"Conversation.SelectedMessages_1" = "%@ Selected";
"Conversation.SelectedMessages_2" = "%@ Selected";
"Conversation.SelectedMessages_3_10" = "%@ Selected";
"Conversation.SelectedMessages_any" = "%@ Selected";
"Conversation.SelectedMessages_many" = "%@ Selected";
"Conversation.SelectedMessages_0" = "%@ Selected";
"Conversation.SelectedMessagesFormat_1" = "{} Selected";
"Conversation.SelectedMessagesFormat_any" = "{} Selected";
"AccentColor.Title" = "Accent Color";
@@ -15459,6 +15456,8 @@ Error: %8$@";
"Notification.StarsGiftOffer.Expired" = "%1$@ didn't respond to your offer for %2$@ within %3$@ your %4$@ have been refunded";
"Notification.StarsGiftOffer.Expired.Stars_1" = "%@ Star";
"Notification.StarsGiftOffer.Expired.Stars_any" = "%@ Stars";
"Notification.StarsGiftOffer.Expired.Hours_1" = "%@ hours";
"Notification.StarsGiftOffer.Expired.Hours_any" = "%@ hours";
"Notification.StarsGiftOffer.ExpiredYou" = "The offer from %1$@ to buy your %2$@ for %3$@ has expired";
"Notification.StarsGiftOffer.ExpiredYou.Stars_1" = "%@ Star";
"Notification.StarsGiftOffer.ExpiredYou.Stars_any" = "%@ Stars";
@@ -15623,3 +15622,50 @@ Error: %8$@";
"Chat.GiftPurchaseOffer.AcceptConfirmation.BadValue" = "The value of this gift is **%@** higher than the offer.";
"Chat.GiftPurchaseOffer.AcceptConfirmation.Confirm" = "Confirm Sale";
"Notification.PremiumGift.DaysTitle_1" = "%@ Day Premium";
"Notification.PremiumGift.DaysTitle_any" = "%@ Days Premium";
"MediaEditor.Audio.Title" = "Audio";
"MediaEditor.Audio.SavedMusic" = "SAVED MUSIC";
"MediaEditor.Audio.ShowMore" = "Show More";
"WebApp.AddToAttachmentTitle" = "Add to Attachment Menu";
"Gift.Upgrade.Wearable.Title" = "Wearable";
"Gift.Upgrade.Wearable.Text" = "Display gifts on your page and set them as profile covers or statuses.";
"Gift.Upgrade.ViewAllVariants" = "View all variants";
"CocoonInfo.Title" = "COCOON";
"CocoonInfo.Description" = "**Cocoon** (**Co**nfidential **Co**mpute **O**pen **N**etwork) handles AI tasks **safely** and **efficiently**.";
"CocoonInfo.Private.Title" = "Private";
"CocoonInfo.Private.Text" = "No third party can access any data, such as translations, inside Cocoon.";
"CocoonInfo.Efficient.Title" = "Efficient";
"CocoonInfo.Efficient.Text" = "Cocoon has allowed Telegram to reduce translation costs by 6x.";
"CocoonInfo.ForEveryone.Title" = "For Everyone";
"CocoonInfo.ForEveryone.Text" = "Any developer can use Cocoon for AI features. Learn more at [@cocoon](telegram) or [cocoon.org](web).";
"CocoonInfo.IntergrateInfo" = "Want to integrate Cocoon into your projects?\nReach out at [t.me/cocoon?direct]()";
"CocoonInfo.Understood" = "Understood";
"Conversation.Translation.CocoonInfo" = "Translations are powered by\n**#Cocoon**. [How does it work?]()";
"Conversation.EmojiStake.Won" = "%1$@ won %2$@";
"Conversation.EmojiStake.WonYou" = "You won %@";
"Conversation.EmojiStake.Lost" = "%1$@ lost %2$@";
"Conversation.EmojiStake.LostYou" = "You lost %@";
"Conversation.Dice.Stake" = "Stake:";
"Conversation.Dice.Change" = "change";
"EmojiStake.Title" = "Emoji Stake";
"EmojiStake.Description" = "An experimental feature for Telegram Premium users.";
"EmojiStake.ResultsTitle" = "RESULTS AND RETURNS";
"EmojiStake.StreakInfo" = "A streak resets after 3 # or a stake change.";
"EmojiStake.StakeTitle" = "STAKE";
"EmojiStake.StakePlaceholder" = "Amount";
"EmojiStake.Roll" = "Save and Roll";
"Conversation.Summary.Title" = "AI Summary";
"Conversation.Summary.Text" = "Tap to see original text";
"Conversation.Summary.Limit.Title" = "AI Summary";
"Conversation.Summary.Limit.Text" = "Summarize large messages with AI unlimited with Telegram Premium.";
@@ -1,7 +1,11 @@
/* Localized versions of Info.plist keys */
"NSContactsUsageDescription" = "Актуальная информация о ваших контактах будет храниться зашифрованной в облаке Telegram, чтобы вы могли связаться с друзьями с любого устройства.";
"NSLocationWhenInUseUsageDescription" = "Когда вы отправляете друзьям геопозицию, Telegram нужно разрешение, чтобы показать им карту.";
/* GHOSTGRAM: App display name */
"CFBundleDisplayName" = "Ghostgram";
"CFBundleName" = "Ghostgram";
"NSContactsUsageDescription" = "Актуальная информация о ваших контактах будет храниться зашифрованной в облаке Ghostgram, чтобы вы могли связаться с друзьями с любого устройства.";
"NSLocationWhenInUseUsageDescription" = "Когда вы отправляете друзьям геопозицию, Ghostgram нужно разрешение, чтобы показать им карту.";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "Фоновый доступ к геопозиции требуется, чтобы обновлять вашу геопозицию, когда вы транслируете её в чат с друзьями.";
"NSLocationAlwaysUsageDescription" = "Фоновый доступ к геопозиции требуется, чтобы обновлять вашу геопозицию, когда вы транслируете её в чат с друзьями. Он также необходим для отправки геопозиции с Apple Watch.";
"NSCameraUsageDescription" = "Это необходимо, чтобы вы могли делиться снятыми фотографиями и видео.";
@@ -10,3 +14,4 @@
"NSMicrophoneUsageDescription" = "Это необходимо, чтобы вы могли делиться голосовыми сообщениями и видео со звуком.";
"NSSiriUsageDescription" = "Вы можете использовать Siri для отправки сообщений.";
"NSFaceIDUsageDescription" = "Вы можете разблокировать приложение с помощью Face ID.";
+1
View File
@@ -0,0 +1 @@
/private/var/tmp/_bazel_ichmagmaus812/b1f80ab1863cefaee5ee828c8e6100cf/execroot/_main
+141 -16
View File
@@ -5,40 +5,165 @@ import shutil
import tempfile
import plistlib
import argparse
import subprocess
import base64
from BuildEnvironment import run_executable_with_output, check_run_system
def get_certificate_base64():
certificate_data = run_executable_with_output('security', arguments=['find-certificate', '-c', 'Apple Distribution: Telegram FZ-LLC (C67CF9S4VU)', '-p'])
certificate_data = certificate_data.replace('-----BEGIN CERTIFICATE-----', '')
certificate_data = certificate_data.replace('-----END CERTIFICATE-----', '')
certificate_data = certificate_data.replace('\n', '')
return certificate_data
def setup_temp_keychain(p12_path, p12_password=''):
"""Create a temporary keychain and import the p12 certificate."""
keychain_name = 'generate-profiles-temp.keychain'
keychain_password = 'temp123'
# Delete if exists
run_executable_with_output('security', arguments=['delete-keychain', keychain_name], check_result=False)
# Create keychain
run_executable_with_output('security', arguments=[
'create-keychain', '-p', keychain_password, keychain_name
], check_result=True)
# Add to search list
existing = run_executable_with_output('security', arguments=['list-keychains', '-d', 'user'])
run_executable_with_output('security', arguments=[
'list-keychains', '-d', 'user', '-s', keychain_name, existing.replace('"', '')
], check_result=True)
# Unlock and set settings
run_executable_with_output('security', arguments=['set-keychain-settings', keychain_name])
run_executable_with_output('security', arguments=[
'unlock-keychain', '-p', keychain_password, keychain_name
])
# Import p12
run_executable_with_output('security', arguments=[
'import', p12_path, '-k', keychain_name, '-P', p12_password,
'-T', '/usr/bin/codesign', '-T', '/usr/bin/security'
], check_result=True)
# Set partition list for access
run_executable_with_output('security', arguments=[
'set-key-partition-list', '-S', 'apple-tool:,apple:', '-k', keychain_password, keychain_name
], check_result=True)
return keychain_name
def process_provisioning_profile(source, destination, certificate_data):
def cleanup_temp_keychain(keychain_name):
"""Remove the temporary keychain."""
run_executable_with_output('security', arguments=['delete-keychain', keychain_name], check_result=False)
def get_signing_identity_from_p12(p12_path, p12_password=''):
"""Extract the common name (signing identity) from the p12 certificate."""
proc = subprocess.Popen(
['openssl', 'pkcs12', '-in', p12_path, '-passin', 'pass:' + p12_password, '-nokeys', '-legacy'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
cert_pem, _ = proc.communicate()
proc2 = subprocess.Popen(
['openssl', 'x509', '-noout', '-subject', '-nameopt', 'oneline,-esc_msb'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
subject, _ = proc2.communicate(cert_pem)
subject = subject.decode('utf-8').strip()
# Parse CN from subject line like: subject= C = AE, O = ..., CN = Some Name
if 'CN = ' in subject:
cn = subject.split('CN = ')[-1].split(',')[0].strip()
return cn
return None
def get_certificate_base64_from_p12(p12_path, p12_password=''):
"""Extract the certificate as base64 from p12 file."""
# Extract certificate in PEM format
proc = subprocess.Popen(
['openssl', 'pkcs12', '-in', p12_path, '-passin', 'pass:' + p12_password, '-nokeys', '-legacy'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
cert_pem, _ = proc.communicate()
# Convert to DER format
proc2 = subprocess.Popen(
['openssl', 'x509', '-outform', 'DER'],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
cert_der, _ = proc2.communicate(cert_pem)
return base64.b64encode(cert_der).decode('utf-8')
def process_provisioning_profile(source, destination, certificate_data, signing_identity, keychain_name):
parsed_plist = run_executable_with_output('security', arguments=['cms', '-D', '-i', source], check_result=True)
parsed_plist_file = tempfile.mktemp()
with open(parsed_plist_file, 'w+') as file:
file.write(parsed_plist)
run_executable_with_output('plutil', arguments=['-remove', 'DeveloperCertificates.0', parsed_plist_file])
# Remove all existing developer certificates
while True:
result = run_executable_with_output('plutil', arguments=['-remove', 'DeveloperCertificates.0', parsed_plist_file], check_result=False)
if result is None or 'Could not' in str(result) or result == '':
# Check if the removal actually failed by trying to extract
check = run_executable_with_output('plutil', arguments=['-extract', 'DeveloperCertificates.0', 'raw', parsed_plist_file], check_result=False)
if check is None or 'Could not' in str(check):
break
# Insert the new certificate
run_executable_with_output('plutil', arguments=['-insert', 'DeveloperCertificates.0', '-data', certificate_data, parsed_plist_file])
# Remove the DER-Encoded-Profile (signature)
run_executable_with_output('plutil', arguments=['-remove', 'DER-Encoded-Profile', parsed_plist_file])
run_executable_with_output('security', arguments=['cms', '-S', '-N', 'Apple Distribution: Telegram FZ-LLC (C67CF9S4VU)', '-i', parsed_plist_file, '-o', destination])
# Sign with the certificate from the temporary keychain
run_executable_with_output('security', arguments=[
'cms', '-S', '-k', keychain_name, '-N', signing_identity, '-i', parsed_plist_file, '-o', destination
], check_result=True)
os.unlink(parsed_plist_file)
def generate_provisioning_profiles(source_path, destination_path):
certificate_data = get_certificate_base64()
def generate_provisioning_profiles(source_path, destination_path, certs_path):
p12_path = os.path.join(certs_path, 'SelfSigned.p12')
if not os.path.exists(destination_path):
print('{} does not exits'.format(destination_path))
if not os.path.exists(p12_path):
print('{} does not exist'.format(p12_path))
sys.exit(1)
for file_name in os.listdir(source_path):
if file_name.endswith('.mobileprovision'):
process_provisioning_profile(source=source_path + '/' + file_name, destination=destination_path + '/' + file_name, certificate_data=certificate_data)
if not os.path.exists(destination_path):
print('{} does not exist'.format(destination_path))
sys.exit(1)
# Extract certificate info from p12
p12_password = '' # fake-codesigning uses empty password
certificate_data = get_certificate_base64_from_p12(p12_path, p12_password)
signing_identity = get_signing_identity_from_p12(p12_path, p12_password)
if not signing_identity:
print('Could not extract signing identity from {}'.format(p12_path))
sys.exit(1)
print('Using signing identity: {}'.format(signing_identity))
# Setup temporary keychain with the certificate
keychain_name = setup_temp_keychain(p12_path, p12_password)
try:
for file_name in os.listdir(source_path):
if file_name.endswith('.mobileprovision'):
print('Processing {}'.format(file_name))
process_provisioning_profile(
source=os.path.join(source_path, file_name),
destination=os.path.join(destination_path, file_name),
certificate_data=certificate_data,
signing_identity=signing_identity,
keychain_name=keychain_name
)
print('Done. Generated {} profiles.'.format(
len([f for f in os.listdir(destination_path) if f.endswith('.mobileprovision')])
))
finally:
cleanup_temp_keychain(keychain_name)
+28 -19
View File
@@ -46,6 +46,7 @@ class BazelCommandLine:
self.show_actions = False
self.enable_sandbox = False
self.disable_provisioning_profiles = False
self.profile_swift = False
self.common_args = [
# https://docs.bazel.build/versions/master/command-line-reference.html
@@ -143,6 +144,9 @@ class BazelCommandLine:
def set_disable_provisioning_profiles(self):
self.disable_provisioning_profiles = True
def set_profile_swift(self, value):
self.profile_swift = value
def set_configuration(self, configuration):
if configuration == 'debug_arm64':
self.configuration_args = [
@@ -300,6 +304,8 @@ class BazelCommandLine:
]
combined_arguments += self.configuration_args
if self.profile_swift:
combined_arguments += ['--config=swift_profile']
print('TelegramBuild: running')
print(subprocess.list2cmdline(combined_arguments))
@@ -369,17 +375,15 @@ class BazelCommandLine:
print(subprocess.list2cmdline(combined_arguments))
call_executable(combined_arguments)
def get_spm_aspect_invocation(self):
def invoke_spm_build(self):
combined_arguments = [
self.build_environment.bazel_path
]
combined_arguments += self.get_startup_bazel_arguments()
combined_arguments += ['build']
if self.custom_target is not None:
combined_arguments += [self.custom_target]
else:
combined_arguments += ['Telegram/Telegram']
# Build the generate_spm target directly to get the dependency tree JSON
combined_arguments += ['//Telegram:spm_build_root']
if self.continue_on_error:
combined_arguments += ['--keep_going']
@@ -409,8 +413,6 @@ class BazelCommandLine:
combined_arguments += self.configuration_args
combined_arguments += ['--aspects', '//build-system/bazel-utils:spm.bzl%spm_text_aspect']
print(subprocess.list2cmdline(combined_arguments))
call_executable(combined_arguments)
@@ -624,6 +626,7 @@ def build(bazel, arguments):
bazel_command_line.set_continue_on_error(arguments.continueOnError)
bazel_command_line.set_show_actions(arguments.showActions)
bazel_command_line.set_enable_sandbox(arguments.sandbox)
bazel_command_line.set_profile_swift(arguments.profileSwift)
bazel_command_line.set_split_swiftmodules(arguments.enableParallelSwiftmoduleGeneration)
@@ -719,7 +722,7 @@ def query(bazel, arguments):
bazel_command_line.invoke_query(query_args)
def get_spm_aspect_invocation(bazel, arguments):
def build_spm(bazel, arguments):
bazel_command_line = BazelCommandLine(
bazel=bazel,
override_bazel_version=arguments.overrideBazelVersion,
@@ -741,13 +744,12 @@ def get_spm_aspect_invocation(bazel, arguments):
bazel_command_line.set_configuration(arguments.configuration)
bazel_command_line.set_build_number(arguments.buildNumber)
bazel_command_line.set_custom_target(arguments.target)
bazel_command_line.set_continue_on_error(False)
bazel_command_line.set_show_actions(False)
bazel_command_line.set_enable_sandbox(False)
bazel_command_line.set_split_swiftmodules(False)
bazel_command_line.get_spm_aspect_invocation()
bazel_command_line.invoke_spm_build()
def add_codesigning_common_arguments(current_parser: argparse.ArgumentParser):
configuration_group = current_parser.add_mutually_exclusive_group(required=True)
@@ -977,6 +979,12 @@ if __name__ == '__main__':
help='Generate .swiftmodule files in parallel to building modules, can speed up compilation on multi-core '
'systems. '
)
buildParser.add_argument(
'--profileSwift',
action='store_true',
default=False,
help='Enable single-core Swift compile profiling flags.'
)
buildParser.add_argument(
'--target',
type=str,
@@ -1068,6 +1076,13 @@ if __name__ == '__main__':
type=str,
help='Path to the destination directory.'
)
generate_profiles_build_parser.add_argument(
'--certsPath',
required=False,
type=str,
default='build-system/fake-codesigning/certs',
help='Path to the directory containing SelfSigned.p12 certificate.'
)
remote_upload_testflight_parser = subparsers.add_parser('remote-deploy-testflight', help='Build the app using a remote environment.')
remote_upload_testflight_parser.add_argument(
@@ -1188,13 +1203,7 @@ if __name__ == '__main__':
metavar='query_string'
)
spm_parser = subparsers.add_parser('spm', help='Generate SPM package')
spm_parser.add_argument(
'--target',
type=str,
help='A custom bazel target name to build.',
metavar='target_name'
)
spm_parser = subparsers.add_parser('spm', help='Generate SPM package (outputs bazel-bin/Telegram/spm_build_root_modules.json)')
spm_parser.add_argument(
'--buildNumber',
required=False,
@@ -1315,7 +1324,7 @@ if __name__ == '__main__':
additional_codesigning_output_path=remote_input_path
)
GenerateProfiles.generate_provisioning_profiles(source_path=remote_input_path + '/profiles', destination_path=args.destination)
GenerateProfiles.generate_provisioning_profiles(source_path=remote_input_path + '/profiles', destination_path=args.destination, certs_path=args.certsPath)
elif args.commandName == 'remote-deploy-testflight':
env = os.environ
if 'APPSTORE_CONNECT_USERNAME' not in env:
@@ -1351,7 +1360,7 @@ if __name__ == '__main__':
elif args.commandName == 'query':
query(bazel=bazel_path, arguments=args)
elif args.commandName == 'spm':
get_spm_aspect_invocation(bazel=bazel_path, arguments=args)
build_spm(bazel=bazel_path, arguments=args)
else:
raise Exception('Unknown command')
except KeyboardInterrupt:
+1 -1
View File
@@ -604,7 +604,7 @@ def remote_build_tart(macos_version, bazel_cache_host, configuration, build_inpu
else:
guest_build_sh += '--cacheHost="$CACHE_HOST" \\'
guest_build_sh += 'build \\'
guest_build_sh += '--lock \\'
#guest_build_sh += '--lock \\'
guest_build_sh += '--buildNumber={} \\'.format(build_number)
guest_build_sh += '--configuration={} \\'.format(configuration)
guest_build_sh += '--configurationPath=$HOME/telegram-build-input/configuration.json \\'
+50
View File
@@ -0,0 +1,50 @@
{
"pins" : [
{
"identity" : "aexml",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tadija/AEXML.git",
"state" : {
"revision" : "38f7d00b23ecd891e1ee656fa6aeebd6ba04ecc3",
"version" : "4.6.1"
}
},
{
"identity" : "pathkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kylef/PathKit.git",
"state" : {
"revision" : "3bfd2737b700b9a36565a8c94f4ad2b050a5e574",
"version" : "1.0.1"
}
},
{
"identity" : "spectre",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kylef/Spectre.git",
"state" : {
"revision" : "26cc5e9ae0947092c7139ef7ba612e34646086c7",
"version" : "0.10.1"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"state" : {
"revision" : "309a47b2b1d9b5e991f36961c983ecec72275be3",
"version" : "1.6.1"
}
},
{
"identity" : "xcodeproj",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tuist/XcodeProj.git",
"state" : {
"revision" : "dc3b87a4e69f9cd06c6cb16199f5d0472e57ef6b",
"version" : "8.24.3"
}
}
],
"version" : 2
}
+26
View File
@@ -0,0 +1,26 @@
// swift-tools-version:5.8
import PackageDescription
let package = Package(
name: "MakeProject",
platforms: [
.macOS(.v13)
],
products: [
.executable(name: "MakeProject", targets: ["MakeProject"])
],
dependencies: [
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.15.0"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
],
targets: [
.executableTarget(
name: "MakeProject",
dependencies: [
.product(name: "XcodeProj", package: "XcodeProj"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]
)
]
)
@@ -0,0 +1,54 @@
import Foundation
struct ModuleDefinition: Codable {
let name: String
let moduleName: String?
let type: String
let path: String
let sources: [String]
let deps: [String]?
let copts: [String]?
let cxxopts: [String]?
let defines: [String]?
let includes: [String]?
let sdkFrameworks: [String]?
let sdkDylibs: [String]?
let hdrs: [String]?
let textualHdrs: [String]?
let weakSdkFrameworks: [String]?
enum CodingKeys: String, CodingKey {
case name
case moduleName = "module_name"
case type
case path
case sources
case deps
case copts
case cxxopts
case defines
case includes
case sdkFrameworks = "sdk_frameworks"
case sdkDylibs = "sdk_dylibs"
case hdrs
case textualHdrs = "textual_hdrs"
case weakSdkFrameworks = "weak_sdk_frameworks"
}
}
enum ModuleType: String {
case swiftLibrary = "swift_library"
case objcLibrary = "objc_library"
case ccLibrary = "cc_library"
case xcframework = "apple_static_xcframework_import"
init?(from definition: ModuleDefinition) {
self.init(rawValue: definition.type)
}
}
func loadModules(from path: String) throws -> [String: ModuleDefinition] {
let url = URL(fileURLWithPath: path)
let data = try Data(contentsOf: url)
return try JSONDecoder().decode([String: ModuleDefinition].self, from: data)
}
@@ -0,0 +1,147 @@
import Foundation
import PathKit
import XcodeProj
class ProjectGenerator {
let modulesPath: Path
let outputDir: Path
let projectRoot: Path
init(modulesPath: Path, outputDir: Path, projectRoot: Path) {
self.modulesPath = modulesPath
self.outputDir = outputDir
self.projectRoot = projectRoot
}
func generate() throws {
print("Loading modules from \(modulesPath)...")
let modules = try loadModules(from: modulesPath.string)
print("Loaded \(modules.count) modules")
// Filter out empty modules, but keep:
// - Modules with source files (excluding .a)
// - Static library modules (only .a files)
// - XCFramework imports
let validModules = modules.filter { name, module in
let nonStaticSources = module.sources.filter { !$0.hasSuffix(".a") }
let staticLibs = module.sources.filter { $0.hasSuffix(".a") }
return !nonStaticSources.isEmpty ||
!staticLibs.isEmpty ||
module.type == "apple_static_xcframework_import"
}
print("Processing \(validModules.count) non-empty modules")
// Setup output directory
try outputDir.mkpath()
// Create symlink manager
let symlinkManager = SymlinkManager(outputDir: outputDir, projectRoot: projectRoot)
symlinkManager.scanExistingFiles()
// Create project
let projectPath = outputDir + "Telegram.xcodeproj"
let pbxproj = PBXProj()
// Create main group
let mainGroup = PBXGroup(children: [], sourceTree: .group)
pbxproj.add(object: mainGroup)
// Create project-level build configurations
let projectDebugSettings: BuildSettings = [
"ALWAYS_SEARCH_USER_PATHS": "NO",
"CLANG_CXX_LANGUAGE_STANDARD": "c++17",
"CLANG_ENABLE_MODULES": "YES",
"CLANG_ENABLE_OBJC_ARC": "YES",
"CLANG_ENABLE_EXPLICIT_MODULES": "NO", // Disable explicit module builds for ObjC-Swift interop
"SWIFT_ENABLE_EXPLICIT_MODULES": "NO", // Disable explicit module builds for Swift
"ENABLE_STRICT_OBJC_MSGSEND": "YES",
"GCC_NO_COMMON_BLOCKS": "YES",
"IPHONEOS_DEPLOYMENT_TARGET": "13.0",
"MTL_ENABLE_DEBUG_INFO": "INCLUDE_SOURCE",
"ONLY_ACTIVE_ARCH": "YES",
"SDKROOT": "iphoneos",
"SWIFT_VERSION": "5.0",
"TARGETED_DEVICE_FAMILY": "1,2",
"DEBUG_INFORMATION_FORMAT": "dwarf",
"ENABLE_BITCODE": "NO",
]
var projectReleaseSettings = projectDebugSettings
projectReleaseSettings["DEBUG_INFORMATION_FORMAT"] = "dwarf-with-dsym"
projectReleaseSettings["MTL_ENABLE_DEBUG_INFO"] = "NO"
projectReleaseSettings["ONLY_ACTIVE_ARCH"] = "NO"
let projectDebugConfig = XCBuildConfiguration(name: "Debug", buildSettings: projectDebugSettings)
let projectReleaseConfig = XCBuildConfiguration(name: "Release", buildSettings: projectReleaseSettings)
pbxproj.add(object: projectDebugConfig)
pbxproj.add(object: projectReleaseConfig)
let projectConfigList = XCConfigurationList(
buildConfigurations: [projectDebugConfig, projectReleaseConfig],
defaultConfigurationName: "Release"
)
pbxproj.add(object: projectConfigList)
// Create project
let project = PBXProject(
name: "Telegram",
buildConfigurationList: projectConfigList,
compatibilityVersion: "Xcode 14.0",
preferredProjectObjectVersion: 56,
minimizedProjectReferenceProxies: 0,
mainGroup: mainGroup
)
pbxproj.add(object: project)
// Create target builder
let targetBuilder = TargetBuilder(
project: project,
pbxproj: pbxproj,
mainGroup: mainGroup,
outputDir: outputDir,
symlinkManager: symlinkManager
)
// Build targets
print("Creating targets...")
var builtCount = 0
for (name, module) in validModules.sorted(by: { $0.key < $1.key }) {
do {
if let _ = try targetBuilder.buildTarget(for: module, allModules: validModules) {
builtCount += 1
if builtCount % 50 == 0 {
print(" Created \(builtCount) targets...")
}
}
} catch {
print("Warning: Failed to build target \(name): \(error)")
}
}
print("Created \(builtCount) targets")
// Wire up dependencies
print("Wiring up dependencies...")
try targetBuilder.wireUpDependencies(modules: validModules)
// Write project
print("Writing project to \(projectPath)...")
pbxproj.rootObject = project
let xcodeproj = XcodeProj(workspace: XCWorkspace(), pbxproj: pbxproj)
try xcodeproj.write(path: projectPath)
// Generate scheme for main target
if let telegramTarget = targetBuilder.getTarget(named: "TelegramUI") {
print("Generating scheme...")
let schemeGenerator = SchemeGenerator(projectPath: projectPath, pbxproj: pbxproj)
try schemeGenerator.generateScheme(for: telegramTarget, named: "TelegramUI")
} else {
print("Warning: Could not find TelegramUI target for scheme")
}
// Clean up stale files
print("Cleaning up stale symlinks...")
symlinkManager.cleanupStaleFiles()
print("Done! Project written to \(projectPath)")
}
}
@@ -0,0 +1,74 @@
import Foundation
import PathKit
import XcodeProj
class SchemeGenerator {
let projectPath: Path
let pbxproj: PBXProj
init(projectPath: Path, pbxproj: PBXProj) {
self.projectPath = projectPath
self.pbxproj = pbxproj
}
func generateScheme(for target: PBXNativeTarget, named schemeName: String) throws {
let schemesDir = projectPath + "xcshareddata" + "xcschemes"
try schemesDir.mkpath()
let buildableReference = XCScheme.BuildableReference(
referencedContainer: "container:Telegram.xcodeproj",
blueprint: target,
buildableName: "\(target.name).framework",
blueprintName: target.name
)
let buildAction = XCScheme.BuildAction(
buildActionEntries: [
XCScheme.BuildAction.Entry(
buildableReference: buildableReference,
buildFor: [.running, .testing, .profiling, .archiving, .analyzing]
)
],
parallelizeBuild: true,
buildImplicitDependencies: true
)
let launchAction = XCScheme.LaunchAction(
runnable: nil,
buildConfiguration: "Debug"
)
let testAction = XCScheme.TestAction(
buildConfiguration: "Debug",
macroExpansion: buildableReference
)
let profileAction = XCScheme.ProfileAction(
runnable: nil,
buildConfiguration: "Release",
macroExpansion: buildableReference
)
let analyzeAction = XCScheme.AnalyzeAction(buildConfiguration: "Debug")
let archiveAction = XCScheme.ArchiveAction(
buildConfiguration: "Release",
revealArchiveInOrganizer: true
)
let scheme = XCScheme(
name: schemeName,
lastUpgradeVersion: nil,
version: nil,
buildAction: buildAction,
testAction: testAction,
launchAction: launchAction,
profileAction: profileAction,
analyzeAction: analyzeAction,
archiveAction: archiveAction
)
let schemePath = schemesDir + "\(schemeName).xcscheme"
try scheme.write(path: schemePath, override: true)
}
}
@@ -0,0 +1,116 @@
import Foundation
import PathKit
class SymlinkManager {
let outputDir: Path
let projectRoot: Path
private var previousFiles: Set<Path> = []
private var currentFiles: Set<Path> = []
init(outputDir: Path, projectRoot: Path) {
self.outputDir = outputDir
self.projectRoot = projectRoot
}
func scanExistingFiles() {
previousFiles = []
scanDirectory(outputDir)
}
private func scanDirectory(_ path: Path) {
guard path.exists else { return }
do {
for item in try path.children() {
let name = item.lastComponent
// Skip build artifacts and xcodeproj bundles
if name == ".build" || name.hasSuffix(".xcodeproj") { continue }
previousFiles.insert(item)
if item.isDirectory && !item.isSymlink {
scanDirectory(item)
}
}
} catch {
print("Warning: Could not scan \(path): \(error)")
}
}
func createDirectory(_ path: Path) throws {
currentFiles.insert(path)
var parent = path.parent()
while parent != outputDir && parent.string.count > outputDir.string.count {
currentFiles.insert(parent)
parent = parent.parent()
}
if !path.exists {
try path.mkpath()
}
}
func createSymlink(from source: Path, to target: Path) throws {
currentFiles.insert(target)
var parent = target.parent()
while parent != outputDir && parent.string.count > outputDir.string.count {
currentFiles.insert(parent)
parent = parent.parent()
}
// Calculate relative path from target back to source
let targetDir = target.parent()
let depth = targetDir.components.count - outputDir.components.count + 1
let relativePrefix = Array(repeating: "..", count: depth).joined(separator: "/")
let relativePath = Path(relativePrefix) + source
if target.isSymlink {
let existingTarget = try? target.symlinkDestination()
if existingTarget == relativePath {
return // Already correct
}
try target.delete()
} else if target.exists {
try target.delete()
}
try targetDir.mkpath()
try FileManager.default.createSymbolicLink(
atPath: target.string,
withDestinationPath: relativePath.string
)
}
func cleanupStaleFiles() {
let staleFiles = previousFiles.subtracting(currentFiles)
let sortedStale = staleFiles.sorted { $0.components.count > $1.components.count }
for path in sortedStale {
do {
if path.isSymlink || path.isFile {
try path.delete()
} else if path.isDirectory {
if (try? path.children().isEmpty) == true {
try path.delete()
}
}
} catch {
print("Warning: Could not remove \(path): \(error)")
}
}
}
func markFile(_ path: Path) {
currentFiles.insert(path)
}
}
extension Path {
var isSymlink: Bool {
var isDir: ObjCBool = false
let exists = FileManager.default.fileExists(atPath: self.string, isDirectory: &isDir)
guard exists else { return false }
do {
let attrs = try FileManager.default.attributesOfItem(atPath: self.string)
return attrs[.type] as? FileAttributeType == .typeSymbolicLink
} catch {
return false
}
}
}
@@ -0,0 +1,792 @@
import Foundation
import PathKit
import XcodeProj
class TargetBuilder {
let project: PBXProject
let pbxproj: PBXProj
let mainGroup: PBXGroup
let outputDir: Path
let symlinkManager: SymlinkManager
private var targetsByName: [String: PBXNativeTarget] = [:]
private var groupsByPath: [String: PBXGroup] = [:]
init(project: PBXProject, pbxproj: PBXProj, mainGroup: PBXGroup, outputDir: Path, symlinkManager: SymlinkManager) {
self.project = project
self.pbxproj = pbxproj
self.mainGroup = mainGroup
self.outputDir = outputDir
self.symlinkManager = symlinkManager
}
// Track which modules are header-only (have no linkable code)
private var headerOnlyModules: Set<String> = []
// Track which modules are static library collections (.a files)
private var staticLibraryModules: [String: [String]] = [:] // module name -> list of .a file paths
func isHeaderOnlyModule(_ name: String) -> Bool {
return headerOnlyModules.contains(name)
}
func isStaticLibraryModule(_ name: String) -> Bool {
return staticLibraryModules[name] != nil
}
func getStaticLibraries(for name: String) -> [String] {
return staticLibraryModules[name] ?? []
}
func buildTarget(for module: ModuleDefinition, allModules: [String: ModuleDefinition]) throws -> PBXNativeTarget? {
guard let moduleType = ModuleType(from: module) else {
print("Warning: Unknown module type \(module.type) for \(module.name)")
return nil
}
// Check for static library modules (only .a files)
let staticLibs = module.sources.filter { $0.hasSuffix(".a") }
let nonStaticLibs = module.sources.filter { !$0.hasSuffix(".a") }
let isStaticLibOnly = !staticLibs.isEmpty && nonStaticLibs.allSatisfy { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") }
if isStaticLibOnly && moduleType != .xcframework {
// This is a static library module - track its .a files but don't create a framework
staticLibraryModules[module.name] = staticLibs
return nil // Don't create a target, just track the static libs
}
// Check if module is header-only (only header files, no real sources)
let sourceFiles = module.sources.filter { source in
!source.hasSuffix(".a") && !source.hasSuffix(".h") && !source.hasSuffix(".hpp")
}
let isHeaderOnly = sourceFiles.isEmpty && moduleType != .xcframework
// Skip modules with no sources at all
if module.sources.isEmpty && moduleType != .xcframework {
return nil
}
switch moduleType {
case .xcframework:
return try buildXCFrameworkTarget(for: module)
case .swiftLibrary, .objcLibrary, .ccLibrary:
if isHeaderOnly {
headerOnlyModules.insert(module.name)
return try buildHeaderOnlyFrameworkTarget(for: module)
} else {
return try buildFrameworkTarget(for: module, moduleType: moduleType)
}
}
}
private func buildXCFrameworkTarget(for module: ModuleDefinition) throws -> PBXNativeTarget {
// For xcframeworks, we create a reference but no build phases
let target = PBXNativeTarget(
name: module.name,
buildConfigurationList: createConfigurationList(for: module, isXCFramework: true),
buildPhases: [],
productName: module.name,
productType: .framework
)
pbxproj.add(object: target)
project.targets.append(target)
targetsByName[module.name] = target
return target
}
private func buildFrameworkTarget(for module: ModuleDefinition, moduleType: ModuleType) throws -> PBXNativeTarget {
// Create group for module
let moduleGroup = try getOrCreateGroup(for: module.path)
// Create symlinks and file references
var sourceRefs: [PBXFileReference] = []
var publicHeaderRefs: [PBXFileReference] = []
var seenPublicHeaderNames: Set<String> = [] // Track header filenames to avoid duplicates in headers build phase
// Determine public header detection:
// 1. If hdrs is provided, use those (explicit public headers)
// 2. Otherwise, headers in includes directories are public
let explicitPublicHeaders = Set(module.hdrs ?? [])
let allSourceFiles = module.sources + (module.hdrs ?? []) + (module.textualHdrs ?? [])
for source in allSourceFiles {
if source.hasSuffix(".a") { continue }
let sourcePath = Path(source)
let fileName = sourcePath.lastComponent
// Calculate relative path within module
let relativeToModule: String
if source.hasPrefix(module.path + "/") {
relativeToModule = String(source.dropFirst(module.path.count + 1))
} else if source.hasPrefix("bazel-out/") {
// Generated file
if let range = source.range(of: module.path + "/") {
relativeToModule = String(source[range.upperBound...])
} else {
relativeToModule = fileName
}
} else {
relativeToModule = fileName
}
// Create symlink
let symlinkPath = outputDir + module.path + relativeToModule
try symlinkManager.createDirectory(symlinkPath.parent())
try symlinkManager.createSymlink(from: Path(source), to: symlinkPath)
// Create file reference
let fileRef = try getOrCreateFileReference(
path: relativeToModule,
in: moduleGroup,
modulePath: module.path,
fileName: fileName
)
let isHeader = source.hasSuffix(".h") || source.hasSuffix(".hpp")
// Determine if this is a public header:
// 1. Explicitly in hdrs array
// 2. Located in an includes directory
let isPublicHeader: Bool
if !isHeader {
isPublicHeader = false
} else if !explicitPublicHeaders.isEmpty {
// If hdrs is provided, only those are public
isPublicHeader = explicitPublicHeaders.contains(source)
} else {
// Headers in include directories are public (handles bazel-out paths)
isPublicHeader = isInIncludesDirectory(source: source, modulePath: module.path, includes: module.includes)
}
if isPublicHeader {
// Skip duplicate header filenames to avoid "multiple commands produce" errors
if !seenPublicHeaderNames.contains(fileName) {
seenPublicHeaderNames.insert(fileName)
publicHeaderRefs.append(fileRef)
}
} else if !source.hasSuffix(".inc") && !isHeader {
// Source files (not headers)
sourceRefs.append(fileRef)
}
// Private headers are not added to any build phase
}
// Build phases
var buildPhases: [PBXBuildPhase] = []
// Sources build phase
let sourcesBuildPhase = PBXSourcesBuildPhase(
files: sourceRefs.map { ref in
let buildFile = PBXBuildFile(file: ref)
pbxproj.add(object: buildFile)
return buildFile
}
)
pbxproj.add(object: sourcesBuildPhase)
buildPhases.append(sourcesBuildPhase)
// Generate modulemap for ObjC/C++ modules (SPM-style explicit headers)
var modulemapPath: Path? = nil
if moduleType == .objcLibrary || moduleType == .ccLibrary {
modulemapPath = try generateModulemap(for: module)
// Headers build phase with public headers - needed for ObjC #import to work
if !publicHeaderRefs.isEmpty {
let headersBuildPhase = PBXHeadersBuildPhase(
files: publicHeaderRefs.map { ref in
let buildFile = PBXBuildFile(file: ref, settings: ["ATTRIBUTES": ["Public"]])
pbxproj.add(object: buildFile)
return buildFile
}
)
pbxproj.add(object: headersBuildPhase)
buildPhases.append(headersBuildPhase)
}
}
// Frameworks build phase
let frameworksBuildPhase = PBXFrameworksBuildPhase(files: [])
pbxproj.add(object: frameworksBuildPhase)
buildPhases.append(frameworksBuildPhase)
// Create target with custom modulemap if generated
let configList = createConfigurationList(for: module, isXCFramework: false, modulemapPath: modulemapPath)
let target = PBXNativeTarget(
name: module.name,
buildConfigurationList: configList,
buildPhases: buildPhases,
productName: module.name,
productType: .framework
)
pbxproj.add(object: target)
project.targets.append(target)
targetsByName[module.name] = target
return target
}
/// Build a framework target for header-only modules (just headers + modulemap, no sources)
private func buildHeaderOnlyFrameworkTarget(for module: ModuleDefinition) throws -> PBXNativeTarget {
// Create group for module
let moduleGroup = try getOrCreateGroup(for: module.path)
// Symlink header files and create file references
var publicHeaderRefs: [PBXFileReference] = []
var seenPublicHeaderNames: Set<String> = [] // Track header filenames to avoid duplicates
let allHeaders = module.sources.filter { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") } + (module.hdrs ?? []) + (module.textualHdrs ?? [])
for source in allHeaders {
let sourcePath = Path(source)
let fileName = sourcePath.lastComponent
let relativeToModule = relativePathInModule(source: source, modulePath: module.path)
// Create parent group
let parentPath = (Path(module.path) + Path(relativeToModule).parent()).string
let parentGroup = try getOrCreateGroup(for: parentPath)
// Create symlink
let symlinkPath = outputDir + module.path + relativeToModule
try symlinkManager.createDirectory(symlinkPath.parent())
try symlinkManager.createSymlink(from: Path(source), to: symlinkPath)
// Create file reference
let fileType = lastKnownFileType(for: fileName)
let fileRef = PBXFileReference(
sourceTree: .group,
name: fileName,
lastKnownFileType: fileType,
path: fileName
)
pbxproj.add(object: fileRef)
parentGroup.children.append(fileRef)
// Check if it's a public header (in includes directories)
// Use helper that properly handles bazel-out paths
let isPublic = isInIncludesDirectory(source: source, modulePath: module.path, includes: module.includes)
if isPublic {
// Skip duplicate header filenames to avoid "multiple commands produce" errors
if !seenPublicHeaderNames.contains(fileName) {
seenPublicHeaderNames.insert(fileName)
publicHeaderRefs.append(fileRef)
}
}
}
// Generate modulemap for this header-only module
let modulemapPath = try generateModulemap(for: module)
// Create build phases
var buildPhases: [PBXBuildPhase] = []
// Headers build phase with public headers - needed for ObjC #import to work
if !publicHeaderRefs.isEmpty {
let headersBuildPhase = PBXHeadersBuildPhase(
files: publicHeaderRefs.map { ref in
let buildFile = PBXBuildFile(file: ref, settings: ["ATTRIBUTES": ["Public"]])
pbxproj.add(object: buildFile)
return buildFile
}
)
pbxproj.add(object: headersBuildPhase)
buildPhases.append(headersBuildPhase)
}
// Empty frameworks phase (needed for Xcode, but won't link anything)
let frameworksBuildPhase = PBXFrameworksBuildPhase(files: [])
pbxproj.add(object: frameworksBuildPhase)
buildPhases.append(frameworksBuildPhase)
// Create target with custom modulemap
let configList = createConfigurationList(for: module, isXCFramework: false, modulemapPath: modulemapPath)
let target = PBXNativeTarget(
name: module.name,
buildConfigurationList: configList,
buildPhases: buildPhases,
productName: module.name,
productType: .framework
)
pbxproj.add(object: target)
project.targets.append(target)
targetsByName[module.name] = target
return target
}
/// Collect all transitive dependencies for a module
private func collectAllDependencies(for moduleName: String, modules: [String: ModuleDefinition], visited: inout Set<String>) -> Set<String> {
guard !visited.contains(moduleName) else { return [] }
visited.insert(moduleName)
guard let module = modules[moduleName], let deps = module.deps else {
return []
}
var allDeps = Set(deps)
for depName in deps {
allDeps.formUnion(collectAllDependencies(for: depName, modules: modules, visited: &visited))
}
return allDeps
}
func wireUpDependencies(modules: [String: ModuleDefinition]) throws {
for (name, module) in modules {
guard let target = targetsByName[name],
let deps = module.deps else { continue }
// Find frameworks build phase
guard let frameworksPhase = target.buildPhases.compactMap({ $0 as? PBXFrameworksBuildPhase }).first else {
continue
}
// Track all frameworks we've added to avoid duplicates
var linkedFrameworks: Set<String> = []
// Track library search paths for static libraries
var staticLibSearchPaths: Set<String> = []
// Add target dependency (only for direct deps)
for depName in deps {
if let depTarget = targetsByName[depName] {
let dependency = PBXTargetDependency(target: depTarget)
pbxproj.add(object: dependency)
target.dependencies.append(dependency)
}
}
// Collect all transitive dependencies to link
var visited = Set<String>()
let allDeps = collectAllDependencies(for: name, modules: modules, visited: &visited)
// Collect header paths from ALL dependencies (direct and transitive)
var depHeaderPaths: [String] = []
for depName in allDeps {
if let depModule = modules[depName] {
depHeaderPaths.append(contentsOf: exportedHeaderPaths(for: depModule))
}
}
// Link all dependencies (direct and transitive) that have targets
for depName in allDeps {
// Skip if already linked
guard !linkedFrameworks.contains(depName) else { continue }
// Check if this is a static library module - link .a files directly
if isStaticLibraryModule(depName) {
// Link static libraries directly
for libPath in getStaticLibraries(for: depName) {
let libName = Path(libPath).lastComponent
// Create an absolute path to the project root then to the static library
// SRCROOT is xcode-files, so we need to go up one level to get to telegram-ios
let projectRoot = outputDir.parent()
let absoluteLibPath = (projectRoot + libPath).string
let libRef = PBXFileReference(
sourceTree: .absolute,
name: libName,
lastKnownFileType: "archive.ar",
path: absoluteLibPath
)
pbxproj.add(object: libRef)
let buildFile = PBXBuildFile(file: libRef)
pbxproj.add(object: buildFile)
frameworksPhase.files?.append(buildFile)
// Add the library's directory to LIBRARY_SEARCH_PATHS
let libDir = Path(absoluteLibPath).parent().string
if !staticLibSearchPaths.contains(libDir) {
staticLibSearchPaths.insert(libDir)
}
}
linkedFrameworks.insert(depName)
continue
}
// Skip header-only modules (no framework to link)
if isHeaderOnlyModule(depName) { continue }
// Regular framework dependency
guard targetsByName[depName] != nil else { continue }
linkedFrameworks.insert(depName)
let frameworkRef = PBXFileReference(
sourceTree: .buildProductsDir,
name: "\(depName).framework",
lastKnownFileType: "wrapper.framework",
path: "\(depName).framework"
)
pbxproj.add(object: frameworkRef)
let buildFile = PBXBuildFile(file: frameworkRef)
pbxproj.add(object: buildFile)
frameworksPhase.files?.append(buildFile)
}
// Update build configurations with dependency paths
if !depHeaderPaths.isEmpty || !deps.isEmpty || !staticLibSearchPaths.isEmpty {
for config in target.buildConfigurationList?.buildConfigurations ?? [] {
// Add header search paths
if !depHeaderPaths.isEmpty {
let existing = config.buildSettings["HEADER_SEARCH_PATHS"] as? String ?? "$(inherited)"
config.buildSettings["HEADER_SEARCH_PATHS"] = existing + " " + depHeaderPaths.joined(separator: " ")
}
// Ensure framework and module search paths include built products
config.buildSettings["FRAMEWORK_SEARCH_PATHS"] = "$(inherited) $(BUILT_PRODUCTS_DIR)"
config.buildSettings["SWIFT_INCLUDE_PATHS"] = "$(inherited) $(BUILT_PRODUCTS_DIR)"
// Add library search paths for static libraries
if !staticLibSearchPaths.isEmpty {
let existing = config.buildSettings["LIBRARY_SEARCH_PATHS"] as? String ?? "$(inherited)"
config.buildSettings["LIBRARY_SEARCH_PATHS"] = existing + " " + staticLibSearchPaths.sorted().joined(separator: " ")
}
}
}
// Collect SDK frameworks from this module and all transitive dependencies
var allSdkFrameworks: Set<String> = []
if let sdkFrameworks = module.sdkFrameworks {
allSdkFrameworks.formUnion(sdkFrameworks)
}
for depName in allDeps {
if let depModule = modules[depName], let depFrameworks = depModule.sdkFrameworks {
allSdkFrameworks.formUnion(depFrameworks)
}
}
// Add SDK frameworks
for framework in allSdkFrameworks {
let fileRef = PBXFileReference(
sourceTree: .sdkRoot,
name: "\(framework).framework",
lastKnownFileType: "wrapper.framework",
path: "System/Library/Frameworks/\(framework).framework"
)
pbxproj.add(object: fileRef)
let buildFile = PBXBuildFile(file: fileRef)
pbxproj.add(object: buildFile)
frameworksPhase.files?.append(buildFile)
}
// Collect SDK dylibs from this module and all transitive dependencies
var allSdkDylibs: Set<String> = []
if let sdkDylibs = module.sdkDylibs {
allSdkDylibs.formUnion(sdkDylibs)
}
for depName in allDeps {
if let depModule = modules[depName], let depDylibs = depModule.sdkDylibs {
allSdkDylibs.formUnion(depDylibs)
}
}
// Add SDK dylibs (system libraries like libz, libiconv, etc.)
for dylib in allSdkDylibs {
// Clean up the library name - remove 'lib' prefix if present
let libName = dylib.hasPrefix("lib") ? String(dylib.dropFirst(3)) : dylib
let fileRef = PBXFileReference(
sourceTree: .sdkRoot,
name: "lib\(libName).tbd",
lastKnownFileType: "sourcecode.text-based-dylib-definition",
path: "usr/lib/lib\(libName).tbd"
)
pbxproj.add(object: fileRef)
let buildFile = PBXBuildFile(file: fileRef)
pbxproj.add(object: buildFile)
frameworksPhase.files?.append(buildFile)
}
}
}
/// Returns the header search paths that this module exports to its dependents
private func exportedHeaderPaths(for module: ModuleDefinition) -> [String] {
var paths: [String] = []
if let includes = module.includes, !includes.isEmpty {
for inc in includes {
if inc == "." {
paths.append("$(SRCROOT)/\(module.path)")
} else {
paths.append("$(SRCROOT)/\(module.path)/\(inc)")
}
}
} else {
// No includes specified, export module's own path
paths.append("$(SRCROOT)/\(module.path)")
}
return paths
}
func getTarget(named name: String) -> PBXNativeTarget? {
return targetsByName[name]
}
private func createConfigurationList(for module: ModuleDefinition, isXCFramework: Bool, modulemapPath: Path? = nil) -> XCConfigurationList {
let debugSettings = createBuildSettings(for: module, isDebug: true, isXCFramework: isXCFramework, modulemapPath: modulemapPath)
let releaseSettings = createBuildSettings(for: module, isDebug: false, isXCFramework: isXCFramework, modulemapPath: modulemapPath)
let debugConfig = XCBuildConfiguration(name: "Debug", buildSettings: debugSettings)
let releaseConfig = XCBuildConfiguration(name: "Release", buildSettings: releaseSettings)
pbxproj.add(object: debugConfig)
pbxproj.add(object: releaseConfig)
let configList = XCConfigurationList(
buildConfigurations: [debugConfig, releaseConfig],
defaultConfigurationName: "Release"
)
pbxproj.add(object: configList)
return configList
}
private func createBuildSettings(for module: ModuleDefinition, isDebug: Bool, isXCFramework: Bool, modulemapPath: Path? = nil) -> BuildSettings {
var settings: BuildSettings = [
"PRODUCT_NAME": "$(TARGET_NAME)",
"PRODUCT_BUNDLE_IDENTIFIER": "org.telegram.\(module.name)",
"INFOPLIST_FILE": "",
"SKIP_INSTALL": "YES",
"GENERATE_INFOPLIST_FILE": "YES",
]
let moduleType = ModuleType(from: module)
// Swift settings
if moduleType == .swiftLibrary {
settings["SWIFT_VERSION"] = "5.0"
settings["DEFINES_MODULE"] = "YES"
if let copts = module.copts, !copts.isEmpty {
let filtered = copts.filter { !$0.hasPrefix("-warnings") }
if !filtered.isEmpty {
settings["OTHER_SWIFT_FLAGS"] = "$(inherited) " + filtered.joined(separator: " ")
}
}
if let defines = module.defines, !defines.isEmpty {
settings["SWIFT_ACTIVE_COMPILATION_CONDITIONS"] = "$(inherited) " + defines.joined(separator: " ")
}
}
// C/ObjC settings
if moduleType == .objcLibrary || moduleType == .ccLibrary {
// Always suppress deprecated warnings (e.g., OSSpinLock) and don't treat warnings as errors
var cflags = ["-Wno-deprecated-declarations"]
if let copts = module.copts, !copts.isEmpty {
let filtered = copts.filter { !$0.hasPrefix("-warnings") && !$0.hasPrefix("-W") }
cflags.append(contentsOf: filtered)
}
settings["OTHER_CFLAGS"] = "$(inherited) " + cflags.joined(separator: " ")
settings["GCC_TREAT_WARNINGS_AS_ERRORS"] = "NO"
if let cxxopts = module.cxxopts, !cxxopts.isEmpty {
let filtered = cxxopts.filter { !$0.hasPrefix("-std=") }
if !filtered.isEmpty {
settings["OTHER_CPLUSPLUSFLAGS"] = "$(inherited) " + filtered.joined(separator: " ")
}
}
if let defines = module.defines, !defines.isEmpty {
settings["GCC_PREPROCESSOR_DEFINITIONS"] = "$(inherited) " + defines.joined(separator: " ")
}
// Always include module's own path for header search
var headerPaths = ["$(SRCROOT)/\(module.path)"]
if let includes = module.includes {
for inc in includes where inc != "." {
headerPaths.append("$(SRCROOT)/\(module.path)/\(inc)")
}
}
settings["HEADER_SEARCH_PATHS"] = "$(inherited) " + headerPaths.joined(separator: " ")
// Use custom modulemap if provided (SPM-style)
if let modmap = modulemapPath {
// Get relative path from SRCROOT
let relativePath = modmap.string.replacingOccurrences(of: outputDir.string + "/", with: "")
settings["MODULEMAP_FILE"] = "$(SRCROOT)/\(relativePath)"
}
settings["DEFINES_MODULE"] = "YES"
}
return settings
}
private func getOrCreateGroup(for path: String) throws -> PBXGroup {
if let existing = groupsByPath[path] {
return existing
}
let components = path.split(separator: "/").map(String.init)
var currentGroup = mainGroup
var currentPath = ""
for component in components {
currentPath = currentPath.isEmpty ? component : currentPath + "/" + component
if let existing = groupsByPath[currentPath] {
currentGroup = existing
} else {
let newGroup = PBXGroup(children: [], sourceTree: .group, name: component, path: component)
pbxproj.add(object: newGroup)
currentGroup.children.append(newGroup)
groupsByPath[currentPath] = newGroup
currentGroup = newGroup
}
}
return currentGroup
}
private func getOrCreateFileReference(path: String, in group: PBXGroup, modulePath: String, fileName: String) throws -> PBXFileReference {
let pathComponents = path.split(separator: "/").map(String.init)
var currentGroup = group
var currentPath = modulePath
// Navigate/create intermediate groups
for component in pathComponents.dropLast() {
currentPath = currentPath + "/" + component
if let existing = groupsByPath[currentPath] {
currentGroup = existing
} else {
let newGroup = PBXGroup(children: [], sourceTree: .group, name: component, path: component)
pbxproj.add(object: newGroup)
currentGroup.children.append(newGroup)
groupsByPath[currentPath] = newGroup
currentGroup = newGroup
}
}
// Create file reference
let fileType = lastKnownFileType(for: fileName)
let fileRef = PBXFileReference(
sourceTree: .group,
name: fileName,
lastKnownFileType: fileType,
path: fileName
)
pbxproj.add(object: fileRef)
currentGroup.children.append(fileRef)
return fileRef
}
private func lastKnownFileType(for fileName: String) -> String {
let ext = (fileName as NSString).pathExtension.lowercased()
switch ext {
case "swift": return "sourcecode.swift"
case "m": return "sourcecode.c.objc"
case "mm": return "sourcecode.cpp.objcpp"
case "c": return "sourcecode.c.c"
case "cc", "cpp", "cxx": return "sourcecode.cpp.cpp"
case "h": return "sourcecode.c.h"
case "hpp": return "sourcecode.cpp.h"
case "metal": return "sourcecode.metal"
case "json": return "text.json"
case "plist": return "text.plist.xml"
default: return "text"
}
}
/// Extract the relative path within a module from a source path
/// Handles both regular paths (submodules/Foo/...) and bazel-out paths (bazel-out/.../bin/submodules/Foo/...)
private func relativePathInModule(source: String, modulePath: String) -> String {
if source.hasPrefix(modulePath + "/") {
return String(source.dropFirst(modulePath.count + 1))
} else if source.hasPrefix("bazel-out/") {
// Generated file - extract path after the module path portion
if let range = source.range(of: modulePath + "/") {
return String(source[range.upperBound...])
} else {
return Path(source).lastComponent
}
} else {
return Path(source).lastComponent
}
}
/// Check if a source file is in one of the includes directories
private func isInIncludesDirectory(source: String, modulePath: String, includes: [String]?) -> Bool {
guard let includes = includes, !includes.isEmpty else {
return false
}
let relative = relativePathInModule(source: source, modulePath: modulePath)
return includes.contains { inc in
if inc == "." {
return true // All files in module are public
} else {
return relative.hasPrefix(inc + "/") || relative == inc
}
}
}
/// Generates an explicit module.modulemap for ObjC/C++ modules (SPM-style)
/// Returns the path to the modulemap, or nil if no public headers
func generateModulemap(for module: ModuleDefinition) throws -> Path? {
let moduleType = ModuleType(from: module)
guard moduleType == .objcLibrary || moduleType == .ccLibrary else {
return nil
}
// Determine public header prefix from includes
let publicHeaderPrefix: String
if let includes = module.includes, !includes.isEmpty {
let firstInclude = includes[0]
publicHeaderPrefix = firstInclude == "." ? "" : firstInclude
} else {
publicHeaderPrefix = ""
}
// Determine public headers
let explicitPublicHeaders = Set(module.hdrs ?? [])
let allFiles = module.sources + (module.hdrs ?? []) + (module.textualHdrs ?? [])
let allHeaders = allFiles.filter { $0.hasSuffix(".h") || $0.hasSuffix(".hpp") }
// Determine which headers are public
let publicHeaders: [String]
if !explicitPublicHeaders.isEmpty {
publicHeaders = allHeaders.filter { explicitPublicHeaders.contains($0) }
} else if module.includes != nil && !module.includes!.isEmpty {
// Use helper that properly handles bazel-out paths
publicHeaders = allHeaders.filter { header in
isInIncludesDirectory(source: header, modulePath: module.path, includes: module.includes)
}
} else {
publicHeaders = []
}
guard !publicHeaders.isEmpty else {
return nil
}
// Generate explicit modulemap content (SPM-style)
var content = "// module.modulemap for \(module.name)\n"
content += "// Auto-generated - do not edit\n\n"
content += "module \(module.name) {\n"
for header in publicHeaders {
// Calculate the symlinked path - matches the symlink creation in buildFrameworkTarget
// The symlink is at: outputDir + module.path + relativeToModule
let relativeToModule = relativePathInModule(source: header, modulePath: module.path)
let symlinkPath = outputDir + module.path + relativeToModule
content += " header \"\(symlinkPath.string)\"\n"
}
content += " export *\n"
content += "}\n"
// Write modulemap to the public headers directory
let modulemapDir: Path
if !publicHeaderPrefix.isEmpty {
modulemapDir = outputDir + module.path + publicHeaderPrefix
} else {
modulemapDir = outputDir + module.path
}
let modulemapPath = modulemapDir + "module.modulemap"
try modulemapDir.mkpath()
try modulemapPath.write(content)
// Track this file so it doesn't get cleaned up
symlinkManager.markFile(modulemapPath)
return modulemapPath
}
}
@@ -0,0 +1,51 @@
import Foundation
import ArgumentParser
import PathKit
struct MakeProject: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "MakeProject",
abstract: "Generate Xcode project from Bazel module definitions"
)
@Option(name: .long, help: "Path to modules JSON file")
var modulesJson: String = "bazel-bin/Telegram/spm_build_root_modules.json"
@Option(name: .long, help: "Output directory for generated project")
var output: String = "xcode-files"
func run() throws {
// Determine project root (where we find the modules JSON)
let currentDir = Path.current
var projectRoot = currentDir
// Walk up to find project root (contains bazel-bin or the modules file)
var searchDir = currentDir
for _ in 0..<5 {
if (searchDir + modulesJson).exists {
projectRoot = searchDir
break
}
searchDir = searchDir.parent()
}
let modulesPath = projectRoot + modulesJson
let outputDir = projectRoot + output
guard modulesPath.exists else {
print("Error: Modules JSON not found at \(modulesPath)")
print("Run 'bazel build //Telegram:spm_build_root' first")
throw ExitCode.failure
}
let generator = ProjectGenerator(
modulesPath: modulesPath,
outputDir: outputDir,
projectRoot: projectRoot
)
try generator.generate()
}
}
MakeProject.main()
+1 -1
View File
@@ -1,7 +1,7 @@
{
"bundle_id": "ph.telegra.Telegraph",
"api_id": "8",
"api_hash": "YOUR_API_HASH",
"api_hash": "7245de8e747a0d6fbe11f7cc14fcc0bb",
"team_id": "C67CF9S4VU",
"app_center_id": "4c816ed0-df83-423c-846b-a0a8467dc7d2",
"is_internal_build": "false",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"bundle_id": "ph.telegra.Telegraph",
"api_id": "8",
"api_hash": "YOUR_API_HASH",
"api_hash": "7245de8e747a0d6fbe11f7cc14fcc0bb",
"team_id": "C67CF9S4VU",
"app_center_id": "0",
"is_internal_build": "false",
@@ -1,7 +1,7 @@
telegram_bundle_id = "ph.telegra.Telegraph"
telegram_api_id = "8"
telegram_api_hash = "YOUR_API_HASH"
telegram_api_hash = "7245de8e747a0d6fbe11f7cc14fcc0bb"
telegram_team_id = "C67CF9S4VU"
telegram_app_center_id = "0"
telegram_is_internal_build = "false"
+1 -1
View File
@@ -7,7 +7,7 @@ export DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: Digital Fortress LL
export DEVELOPMENT_TEAM="C67CF9S4VU"
export API_ID="8"
export API_HASH="YOUR_API_HASH"
export API_HASH="7245de8e747a0d6fbe11f7cc14fcc0bb"
export BUNDLE_ID="ph.telegra.Telegraph"
export APP_CENTER_ID="0"
@@ -1376,7 +1376,7 @@ public protocol SharedAccountContext: AnyObject {
func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, transitionOut: @escaping () -> BotPreviewEditorTransitionOut?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?)
func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
func makeStickerPickerScreen(context: AccountContext, inputData: Promise<StickerPickerInput>, completion: @escaping (FileMediaReference) -> Void) -> ViewController
func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController
@@ -1407,10 +1407,10 @@ public protocol SharedAccountContext: AnyObject {
func makeGiftUpgradePreviewScreen(context: AccountContext, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController
func makeGiftAuctionInfoScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: (() -> Void)?) -> ViewController
func makeGiftAuctionBidScreen(context: AccountContext, toPeerId: EnginePeer.Id, text: String?, entities: [MessageTextEntity]?, hideName: Bool, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?) -> ViewController
func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController
func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, peerId: EnginePeer.Id?, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController
func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController
func makeGiftOfferScreen(context: AccountContext, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController
func makeGiftUpgradeVariantsPreviewScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute]) -> ViewController
func makeGiftOfferScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController
func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController
func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController
func makeGiftDemoScreen(context: AccountContext) -> ViewController
func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController
@@ -1428,6 +1428,8 @@ public protocol SharedAccountContext: AnyObject {
func makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController
func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController
func makeSendInviteLinkScreen(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme?) -> ViewController
func makeCocoonInfoScreen(context: AccountContext) -> ViewController
func makeLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, text: String, link: String?, apply: @escaping (String?) -> Void) -> ViewController
@available(iOS 13.0, *)
func makePostSuggestionsSettingsScreen(context: AccountContext, peerId: EnginePeer.Id) async -> ViewController
@@ -1678,7 +1680,7 @@ public struct StarsSubscriptionConfiguration {
return StarsSubscriptionConfiguration(
maxFee: 2500,
usdWithdrawRate: 1200,
tonUsdRate: 0,
tonUsdRate: 1.0,
paidMessageMaxAmount: 10000,
paidMessageCommissionPermille: 850,
paidMessagesAvailable: false,
@@ -1698,7 +1700,7 @@ public struct StarsSubscriptionConfiguration {
public let maxFee: Int64
public let usdWithdrawRate: Int64
public let tonUsdRate: Int64
public let tonUsdRate: Double
public let paidMessageMaxAmount: Int64
public let paidMessageCommissionPermille: Int32
public let paidMessagesAvailable: Bool
@@ -1717,7 +1719,7 @@ public struct StarsSubscriptionConfiguration {
fileprivate init(
maxFee: Int64,
usdWithdrawRate: Int64,
tonUsdRate: Int64,
tonUsdRate: Double,
paidMessageMaxAmount: Int64,
paidMessageCommissionPermille: Int32,
paidMessagesAvailable: Bool,
@@ -1756,7 +1758,7 @@ public struct StarsSubscriptionConfiguration {
if let data = appConfiguration.data {
let maxFee = (data["stars_subscription_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.maxFee
let usdWithdrawRate = (data["stars_usd_withdraw_rate_x1000"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.usdWithdrawRate
let tonUsdRate = (data["ton_usd_rate"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.tonUsdRate
let tonUsdRate = (data["ton_usd_rate"] as? Double) ?? StarsSubscriptionConfiguration.defaultValue.tonUsdRate
let paidMessageMaxAmount = (data["stars_paid_message_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageMaxAmount
let paidMessageCommissionPermille = (data["stars_paid_message_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageCommissionPermille
let paidMessagesAvailable = (data["stars_paid_messages_available"] as? Bool) ?? StarsSubscriptionConfiguration.defaultValue.paidMessagesAvailable
@@ -1216,7 +1216,7 @@ public enum ChatHistoryListDisplayHeaders {
public enum ChatHistoryListMode: Equatable {
case bubbles
case list(search: Bool, reversed: Bool, reverseGroups: Bool, displayHeaders: ChatHistoryListDisplayHeaders, hintLinks: Bool, isGlobalSearch: Bool)
case list(reversed: Bool, reverseGroups: Bool, displayHeaders: ChatHistoryListDisplayHeaders, hintLinks: Bool, isGlobalSearch: Bool)
}
public protocol ChatControllerInteractionProtocol: AnyObject {
@@ -4,8 +4,9 @@ import TelegramCore
import TelegramUIPreferences
import AccountContext
public let maximumNumberOfAccounts = 3
public let maximumPremiumNumberOfAccounts = 4
// GHOSTGRAM: Unlimited accounts bypass - always allow up to 10 accounts
public let maximumNumberOfAccounts = 10
public let maximumPremiumNumberOfAccounts = 10
public func activeAccountsAndPeers(context: AccountContext, includePrimary: Bool = false) -> Signal<((AccountContext, EnginePeer)?, [(AccountContext, EnginePeer, Int32)]), NoError> {
let sharedContext = context.sharedContext
+1 -1
View File
@@ -73,7 +73,7 @@ public final class AdInfoScreen: ViewController {
self.titleNode = ImmediateTextNode()
self.titleNode.maximumNumberOfLines = 1
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.SponsoredMessageInfoScreen_Title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor)
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.SponsoredMessageInfoScreen_Title, font: Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]), textColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor)
self.scrollNode = ASScrollNode()
self.scrollNode.view.showsVerticalScrollIndicator = true
+2
View File
@@ -44,6 +44,8 @@ swift_library(
"//submodules/TelegramUI/Components/MinimizedContainer",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/LiquidLens",
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
],
visibility = [
"//visibility:public",
@@ -24,6 +24,8 @@ import LegacyMessageInputPanelInputView
import ReactionSelectionNode
import TopMessageReactions
import GlassBackgroundComponent
import LiquidLens
import TabSelectionRecognizer
private let legacyButtonSize = CGSize(width: 88.0, height: 49.0)
private let glassButtonSize = CGSize(width: 72.0, height: 62.0)
@@ -887,7 +889,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
}
}
final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {
enum Style {
case glass
case legacy
@@ -912,12 +914,22 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
private let containerNode: ASDisplayNode
private var backgroundView: GlassBackgroundView?
private var liquidLensView: LiquidLensView?
private let backgroundNode: NavigationBackgroundNode
private let scrollNode: ASScrollNode
private let separatorNode: ASDisplayNode
private let selectionNode: ASImageNode
private var buttonViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var itemsContainer = UIView()
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var selectedItemsContainer = UIView()
private var selectedItemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var itemSizes: [AnyHashable: CGSize] = [:]
private var tabSelectionRecognizer: TabSelectionRecognizer?
private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable, isLifted: Bool)?
private var lensIsLifted = false
private var textInputPanelNode: AttachmentTextInputPanelNode?
private var progressNode: LoadingProgressNode?
@@ -971,7 +983,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
self.makeEntityInputView = makeEntityInputView
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.containerNode = ASDisplayNode()
self.containerNode.clipsToBounds = false
@@ -987,7 +999,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
self.mainButtonNode = MainButtonNode()
self.secondaryButtonNode = MainButtonNode()
super.init()
self.addSubnode(self.containerNode)
@@ -995,7 +1007,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
switch style {
case .glass:
self.scrollNode.cornerRadius = glassButtonSize.height * 0.5
self.scrollNode.addSubnode(self.selectionNode)
case .legacy:
self.containerNode.addSubnode(self.backgroundNode)
self.containerNode.addSubnode(self.separatorNode)
@@ -1135,7 +1146,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = chatTextLinkEditController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: (presentationData, .never()), account: strongSelf.context.account, text: text?.string ?? "", link: link, apply: { [weak self] link in
let controller = chatTextLinkEditController(context: strongSelf.context, updatedPresentationData: (presentationData, .never()), text: text?.string ?? "", link: link, apply: { [weak self] link in
if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange {
if let link = link {
strongSelf.updateChatPresentationInterfaceState(animated: true, { state in
@@ -1419,6 +1430,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
self.view.accessibilityTraits = .tabBar
}
func requestLayout(transition: ContainedViewLayoutTransition) {
if let layout = self.validLayout {
let _ = self.update(layout: layout, buttons: self.buttons, isSelecting: self.isSelecting, selectionCount: self.selectionCount, elevateProgress: self.elevateProgress, transition: transition)
}
}
@objc private func mainButtonPressed() {
self.onMainButtonPressed()
}
@@ -1473,6 +1490,105 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}
}
private func item(at point: CGPoint) -> AnyHashable? {
let contentOffset = self.scrollNode.view.contentOffset.x
let point = point.offsetBy(dx: contentOffset, dy: 0.0)
var closestItem: (AnyHashable, CGFloat)?
for (id, itemView) in self.itemViews {
if itemView.frame.contains(point) {
return id
} else {
let distance = abs(point.x - itemView.center.x)
if let closestItemValue = closestItem {
if closestItemValue.1 > distance {
closestItem = (id, distance)
}
} else {
closestItem = (id, distance)
}
}
}
return closestItem?.0
}
@objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) {
guard let liquidLensView = self.liquidLensView else {
return
}
let location = recognizer.location(in: liquidLensView.contentView)
switch recognizer.state {
case .began:
if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] {
let startX = itemView.frame.minX
self.selectionGestureState = (startX, startX, itemId, !self.scrollNode.view.isScrollEnabled)
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
}
case .changed:
if var selectionGestureState = self.selectionGestureState {
selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self.view).x
if let itemId = self.item(at: location) {
selectionGestureState.itemId = itemId
}
self.selectionGestureState = selectionGestureState
self.requestLayout(transition: .immediate)
}
case .ended, .cancelled:
if let selectionGestureState = self.selectionGestureState {
if !selectionGestureState.isLifted {
self.lensIsLifted = true
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
self.liquidLensView?.clipsToBounds = false
Queue.mainQueue().after(0.1, {
self.lensIsLifted = false
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
Queue.mainQueue().after(0.4, {
self.liquidLensView?.clipsToBounds = true
})
})
}
self.selectionGestureState = nil
if case .ended = recognizer.state {
guard let index = self.buttons.firstIndex(where: { AnyHashable($0.key) == selectionGestureState.itemId }) else {
return
}
let button = self.buttons[index]
self.skipLensUpdate = true
if self.selectionChanged(button) {
self.selectedIndex = index
if self.buttons.count > 5, let button = self.itemViews[button.key] {
let transition = ComponentTransition.spring(duration: 0.4)
let scrollView = self.scrollNode.view
let targetRect = button.frame.insetBy(dx: -35.0, dy: 0.0)
var newBounds = scrollView.bounds
if targetRect.minX < scrollView.bounds.minX {
newBounds.origin.x = targetRect.minX
}
else if targetRect.maxX > scrollView.bounds.maxX {
newBounds.origin.x = targetRect.maxX - scrollView.bounds.width
}
let minX = 0.0
let maxX = scrollView.contentSize.width - scrollView.bounds.width
newBounds.origin.x = max(minX, min(newBounds.origin.x, maxX))
transition.setBounds(view: scrollView, bounds: newBounds)
self.updateItemContainers(contentOffset: newBounds.minX, transition: transition)
}
}
self.skipLensUpdate = false
}
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
}
default:
break
}
}
func updateViews(transition: ComponentTransition) {
guard let layout = self.validLayout else {
return
@@ -1486,18 +1602,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
case .legacy:
panelSideInset = 3.0
}
let visibleRect = self.scrollNode.bounds.insetBy(dx: -180.0, dy: 0.0)
var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, self.buttons.count - 1)))
let internalWidth = distanceBetweenNodes * CGFloat(self.buttons.count - 1)
var buttonWidth = self.buttonSize.width
var leftNodeOriginX: CGFloat
var maxButtonsToFit = 6
var maxButtonsToFit = 5
switch self.panelStyle {
case .glass:
leftNodeOriginX = layout.safeInsets.left + 3.0 + buttonWidth / 2.0
if layout.size.width < 400.0 {
leftNodeOriginX = layout.safeInsets.left + buttonWidth / 2.0
if layout.size.width < 420.0 {
maxButtonsToFit = 5
}
case .legacy:
@@ -1513,7 +1627,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
buttonWidth = smallButtonWidth
distanceBetweenNodes = 60.0
}
leftNodeOriginX = layout.safeInsets.left + 3.0 + buttonWidth / 2.0
leftNodeOriginX = layout.safeInsets.left + buttonWidth / 2.0
}
var validIds = Set<AnyHashable>()
@@ -1521,25 +1635,28 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
var selectionFrame = CGRect()
var mostRightX = 0.0
for i in 0 ..< self.buttons.count {
let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0)
let buttonFrame = CGRect(origin: CGPoint(x: originX, y: 0.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height))
let originX = floorToScreenPixels(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0)
let buttonFrame = CGRect(origin: CGPoint(x: originX, y: -3.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height))
mostRightX = buttonFrame.maxX
if !visibleRect.intersects(buttonFrame) {
continue
}
let type = self.buttons[i]
let _ = validIds.insert(type.key)
var buttonTransition = transition
let buttonView: ComponentHostView<Empty>
if let current = self.buttonViews[type.key] {
let selectedButtonView: ComponentHostView<Empty>
if let current = self.itemViews[type.key], let currentSelected = self.selectedItemViews[type.key] {
buttonView = current
selectedButtonView = currentSelected
} else {
buttonTransition = .immediate
buttonView = ComponentHostView<Empty>()
self.buttonViews[type.key] = buttonView
self.scrollNode.view.addSubview(buttonView)
self.itemViews[type.key] = buttonView
self.itemsContainer.addSubview(buttonView)
selectedButtonView = ComponentHostView<Empty>()
self.selectedItemViews[type.key] = selectedButtonView
self.selectedItemsContainer.addSubview(selectedButtonView)
}
if case let .app(bot) = type {
@@ -1578,7 +1695,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
style: self.panelStyle == .glass ? .glass : .legacy,
type: type,
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
isSelected: i == self.selectedIndex,
isSelected: false,
strings: self.presentationData.strings,
theme: self.presentationData.theme,
action: { [weak self] in
@@ -1587,7 +1704,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
strongSelf.selectedIndex = i
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
if strongSelf.buttons.count > 5, let button = strongSelf.buttonViews[type.key] {
if strongSelf.buttons.count > 5, let button = strongSelf.itemViews[type.key] {
strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true)
}
}
@@ -1601,11 +1718,32 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
environment: {},
containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height)
)
self.itemSizes[type.key] = actualButtonSize
let _ = selectedButtonView.update(
transition: buttonTransition,
component: AnyComponent(AttachButtonComponent(
context: self.context,
style: self.panelStyle == .glass ? .glass : .legacy,
type: type,
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
isSelected: true,
strings: self.presentationData.strings,
theme: self.presentationData.theme,
action: {
},
longPressAction: {
})
),
environment: {},
containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height)
)
if i == self.selectedIndex {
selectionFrame = CGRect(origin: CGPoint(x: buttonFrame.midX - actualButtonSize.width * 0.5, y: buttonFrame.minY), size: actualButtonSize)
}
buttonTransition.setFrame(view: buttonView, frame: buttonFrame)
buttonTransition.setFrame(view: selectedButtonView, frame: buttonFrame)
var accessibilityTitle = ""
switch type {
case .gallery:
@@ -1634,14 +1772,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
buttonView.accessibilityTraits = [.button]
}
var removeIds: [AnyHashable] = []
for (id, itemView) in self.buttonViews {
for (id, itemView) in self.itemViews {
if !validIds.contains(id) {
removeIds.append(id)
itemView.removeFromSuperview()
}
}
for id in removeIds {
self.buttonViews.removeValue(forKey: id)
self.itemViews.removeValue(forKey: id)
}
selectionFrame = selectionFrame.insetBy(dx: 1.0, dy: 4.0)
@@ -1651,9 +1789,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
mostRightX += layout.safeInsets.right + 3.0
let contentSize = CGSize(width: mostRightX, height: self.buttonSize.height)
if contentSize != self.scrollNode.view.contentSize {
if contentSize != self.scrollNode.view.contentSize && self.scrollNode.view.bounds.width > 0.0 {
self.scrollNode.view.contentSize = contentSize
self.scrollNode.view.isScrollEnabled = abs(contentSize.width - self.scrollNode.view.bounds.width) > 1.0
self.scrollNode.view.isScrollEnabled = contentSize.width - self.scrollNode.view.bounds.width > 1.0
self.liquidLensView?.clipsToBounds = self.scrollNode.view.isScrollEnabled
}
}
@@ -1916,31 +2055,65 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
textPanelWidth = layout.size.width - panelSideInset * 2.0
}
self.updateViews(transition: .immediate)
let glassPanelHeight: CGFloat = 62.0
let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom))
if case .glass = self.panelStyle {
let backgroundView: GlassBackgroundView
if let current = self.backgroundView {
backgroundView = current
let liquidLensView: LiquidLensView
if let currentBackground = self.backgroundView, let currentLens = self.liquidLensView {
backgroundView = currentBackground
liquidLensView = currentLens
} else {
backgroundView = GlassBackgroundView()
self.containerNode.view.addSubview(backgroundView)
self.containerNode.view.addSubview(self.scrollNode.view)
self.backgroundView = backgroundView
liquidLensView = LiquidLensView(kind: .noContainer)
liquidLensView.clipsToBounds = true
liquidLensView.layer.cornerRadius = 28.0
self.containerNode.view.addSubview(backgroundView)
self.containerNode.view.addSubview(liquidLensView)
self.containerNode.view.addSubview(self.scrollNode.view)
self.liquidLensView = liquidLensView
liquidLensView.contentView.addSubview(self.itemsContainer)
liquidLensView.selectedContentView.addSubview(self.selectedItemsContainer)
self.itemsContainer.clipsToBounds = true
self.itemsContainer.layer.cornerRadius = 28.0
self.selectedItemsContainer.clipsToBounds = true
self.selectedItemsContainer.layer.cornerRadius = 28.0
let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:)))
tabSelectionRecognizer.delegate = self.wrappedGestureRecognizerDelegate
self.tabSelectionRecognizer = tabSelectionRecognizer
self.scrollNode.view.addGestureRecognizer(tabSelectionRecognizer)
}
let panelSize = CGSize(width: isSelecting ? textPanelWidth : layout.size.width - layout.safeInsets.left - layout.safeInsets.right - panelSideInset * 2.0, height: isSelecting ? textPanelHeight - 11.0 : glassPanelHeight)
let backgroundViewColor: UIColor
if self.presentationData.theme.overallDarkAppearance {
backgroundViewColor = self.presentationData.theme.list.modalBlocksBackgroundColor.withAlphaComponent(0.55)
} else {
backgroundViewColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.75)
}
let cornerRadius: CGFloat = isSelecting ? min(20.0, panelSize.height * 0.5) : panelSize.height * 0.5
let backgroundOriginX: CGFloat = isSelecting ? panelSideInset : floorToScreenPixels((layout.size.width - panelSize.width) / 2.0)
backgroundView.update(size: panelSize, cornerRadius: isSelecting ? 20.0 : glassPanelHeight * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: backgroundViewColor), transition: ComponentTransition(transition))
backgroundView.update(size: panelSize, cornerRadius: cornerRadius, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: self.presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: ComponentTransition(transition))
let lensSideInset: CGFloat = defaultPanelSideInset + layout.safeInsets.left
let lensPanelSize = CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - lensSideInset * 2.0, height: glassPanelHeight)
self.lensParams = (lensPanelSize, cornerRadius)
self.updateLiquidLens(transition: ComponentTransition(transition))
transition.updatePosition(layer: liquidLensView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5))
transition.updateBounds(layer: liquidLensView.layer, bounds: CGRect(origin: .zero, size: CGSize(width: lensPanelSize.width - 3.0 * 2.0, height: lensPanelSize.height - 3.0 * 2.0)))
transition.updatePosition(layer: backgroundView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5))
transition.updateBounds(layer: backgroundView.layer, bounds: CGRect(origin: .zero, size: panelSize))
let itemsContainerFrame = CGRect(origin: .zero, size: CGSize(width: lensPanelSize.width - 3.0 * 2.0, height: lensPanelSize.height - 3.0 * 2.0))
transition.updateBounds(layer: self.itemsContainer.layer, bounds: CGRect(origin: .zero, size: itemsContainerFrame.size))
transition.updateBounds(layer: self.selectedItemsContainer.layer, bounds: CGRect(origin: .zero, size: itemsContainerFrame.size))
transition.updatePosition(layer: self.itemsContainer.layer, position: itemsContainerFrame.center)
transition.updatePosition(layer: self.selectedItemsContainer.layer, position: itemsContainerFrame.center)
}
var containerTransition: ContainedViewLayoutTransition
@@ -2002,8 +2175,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
let alphaTransition = ContainedViewLayoutTransition.animated(duration: isSelecting ? 0.1 : 0.25, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0)
containerTransition.updateTransformScale(node: self.scrollNode, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0)
if let backgroundView = self.backgroundView {
containerTransition.updateTransformScale(layer: backgroundView.layer, scale: isAnyButtonVisible ? 0.85 : 1.0)
if let liquidLensView = self.liquidLensView {
alphaTransition.updateAlpha(layer: liquidLensView.layer, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0)
containerTransition.updateTransformScale(layer: liquidLensView.layer, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0)
}
if isSelectingUpdated {
@@ -2022,7 +2197,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 1.0)
let componentTransition = ComponentTransition.easeInOut(duration: 0.25)
componentTransition.animateBlur(layer: self.scrollNode.layer, fromRadius: 0.0, toRadius: 10.0)
if let liquidLensView = self.liquidLensView {
componentTransition.animateBlur(layer: liquidLensView.layer, fromRadius: 0.0, toRadius: 10.0)
transition.animatePositionAdditive(layer: liquidLensView.layer, offset: .zero, to: CGPoint(x: -27.0, y: 0.0))
}
let blurTransition = ComponentTransition.easeInOut(duration: 0.18)
transition.animateTransformScale(node: textInputPanelNode.opaqueActionButtons, from: 0.01)
@@ -2030,9 +2208,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
textInputPanelNode.opaqueActionButtons.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
transition.animatePosition(node: textInputPanelNode.opaqueActionButtons, from: CGPoint(x: textInputPanelNode.opaqueActionButtons.position.x, y: textInputPanelNode.opaqueActionButtons.position.y + heightDelta))
blurTransition.animateBlur(layer: textInputPanelNode.inputModeView.layer, fromRadius: 4.0, toRadius: 0.0)
componentTransition.animateAlpha(view: textInputPanelNode.inputModeView, from: 0.0, to: 1.0)
transition.animatePositionAdditive(layer: textInputPanelNode.textPlaceholderNode.layer, offset: CGPoint(x: 6.0, y: heightDelta))
transition.animatePositionAdditive(layer: textInputPanelNode.inputModeView.layer, offset: CGPoint(x: 64.0, y: heightDelta))
textInputPanelNode.animateIn(transition: transition)
} else {
textInputPanelNode.alpha = 1.0
@@ -2054,7 +2235,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 0.0)
let componentTransition = ComponentTransition.easeInOut(duration: 0.25)
componentTransition.animateBlur(layer: self.scrollNode.layer, fromRadius: 10.0, toRadius: 0.0)
if let liquidLensView = self.liquidLensView {
componentTransition.animateBlur(layer: liquidLensView.layer, fromRadius: 10.0, toRadius: 0.0)
transition.animatePositionAdditive(layer: liquidLensView.layer, offset: CGPoint(x: -27.0, y: 0.0))
}
let blurTransition = ComponentTransition.easeInOut(duration: 0.18)
transition.animateTransformScale(layer: textInputPanelNode.opaqueActionButtons.layer, from: CGPoint(x: 1.0, y: 1.0), to: CGPoint(x: 0.01, y: 0.01))
@@ -2062,6 +2246,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
textInputPanelNode.opaqueActionButtons.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
transition.animatePosition(node: textInputPanelNode.opaqueActionButtons, to: CGPoint(x: textInputPanelNode.opaqueActionButtons.position.x, y: textInputPanelNode.opaqueActionButtons.position.y + heightDelta))
blurTransition.animateBlur(layer: textInputPanelNode.inputModeView.layer, fromRadius: 0.0, toRadius: 4.0)
componentTransition.animateAlpha(view: textInputPanelNode.inputModeView, from: 1.0, to: 0.0)
transition.animatePositionAdditive(layer: textInputPanelNode.textPlaceholderNode.layer, offset: .zero, to: CGPoint(x: 6.0, y: heightDelta))
transition.animatePositionAdditive(layer: textInputPanelNode.inputModeView.layer, offset: .zero, to: CGPoint(x: 64.0, y: heightDelta))
} else {
@@ -2086,8 +2273,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: self.isSelecting ? -11.0 : 0.0), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height)))
}
self.updateViews(transition: .immediate)
if let progress = self.loadingProgress {
let loadingProgressNode: LoadingProgressNode
if let current = self.progressNode {
@@ -2183,8 +2368,70 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
return containerFrame.height
}
func updateItemContainers(contentOffset: CGFloat, transition: ComponentTransition) {
let transform = CATransform3DMakeTranslation(-contentOffset, 0.0, 0.0)
transition.setSublayerTransform(view: self.itemsContainer, transform: transform)
transition.setSublayerTransform(view: self.selectedItemsContainer, transform: transform)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if self.selectionGestureState != nil {
self.selectionGestureState = nil
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateItemContainers(contentOffset: scrollView.contentOffset.x, transition: .immediate)
self.updateViews(transition: .immediate)
self.updateLiquidLens(transition: .immediate)
}
private var skipLensUpdate = false
private var lensParams: (panelSize: CGSize, cornerRadius: CGFloat)?
func updateLiquidLens(transition: ComponentTransition) {
guard !self.skipLensUpdate, let liquidLensView = self.liquidLensView, let (panelSize, _) = self.lensParams else {
return
}
var selectionFrame = CGRect()
if self.selectedIndex >= 0 && self.selectedIndex < self.buttons.count, let itemView = self.itemViews[self.buttons[self.selectedIndex].key], let itemSize = self.itemSizes[self.buttons[self.selectedIndex].key] {
let contentOffset = self.scrollNode.view.contentOffset.x
selectionFrame = CGRect(origin: CGPoint(x: itemView.center.x - itemSize.width / 2.0 - contentOffset, y: itemView.frame.minY), size: itemSize)
}
var lensSelection: (x: CGFloat, width: CGFloat)
if let selectionGestureState = self.selectionGestureState, selectionGestureState.isLifted {
lensSelection = (selectionGestureState.currentX, selectionFrame.width)
} else {
lensSelection = (selectionFrame.minX, selectionFrame.width)
}
if !self.scrollNode.view.isScrollEnabled {
lensSelection.x = max(0.0, min(lensSelection.x, panelSize.width - lensSelection.width))
}
var isLifted = self.selectionGestureState?.isLifted == true || self.lensIsLifted
if let widthClass = self.validLayout?.metrics.widthClass, case .regular = widthClass {
isLifted = false
}
let inset: CGFloat = 3.0
liquidLensView.update(size: CGSize(width: panelSize.width - inset * 2.0, height: panelSize.height - inset * 2.0), cornerRadius: 28.0, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: panelSize.height - inset * 2.0), inset: 0.0, isDark: self.presentationData.theme.overallDarkAppearance, isLifted: isLifted, isCollapsed: self.isSelecting || self.buttons.count < 2, transition: transition)
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == self.tabSelectionRecognizer {
return true
}
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
+1
View File
@@ -47,6 +47,7 @@ swift_library(
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/Utils/DeviceModel",
"//submodules/PresentationDataUtils",
],
visibility = [
"//visibility:public",
@@ -5,7 +5,9 @@ import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import PresentationDataUtils
import ProgressNavigationButtonNode
import AccountContext
public final class AuthorizationSequenceCodeEntryController: ViewController {
private var controllerNode: AuthorizationSequenceCodeEntryControllerNode {
@@ -14,6 +16,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
private var validLayout: ContainerViewLayout?
private let sharedContext: SharedAccountContext
private let strings: PresentationStrings
private let theme: PresentationTheme
@@ -45,7 +48,8 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
return self.data?.6 ?? false
}
public init(presentationData: PresentationData, back: @escaping () -> Void) {
public init(sharedContext: SharedAccountContext, presentationData: PresentationData, back: @escaping () -> Void) {
self.sharedContext = sharedContext
self.strings = presentationData.strings
self.theme = presentationData.theme
@@ -61,11 +65,14 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
return false
}
self.navigationBar?.backPressed = { [weak self] in
guard let self else {
return
}
let text: String
let proceed: String
let stop: String
if let (_, _, type, _, _, _, _) = self?.data, case .email = type {
if let (_, _, type, _, _, _, _) = self.data, case .email = type {
text = presentationData.strings.Login_CancelEmailVerification
proceed = presentationData.strings.Login_CancelEmailVerificationContinue
stop = presentationData.strings.Login_CancelEmailVerificationStop
@@ -75,7 +82,7 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
stop = presentationData.strings.Login_CancelPhoneVerificationStop
}
self?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: proceed, action: {
self.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: proceed, action: {
}), TextAlertAction(type: .defaultAction, title: stop, action: {
back()
})]), in: .window(.root))
@@ -9,6 +9,7 @@ import MtProtoKit
import MessageUI
import CoreTelephony
import TelegramPresentationData
import PresentationDataUtils
import TextFormat
import AccountContext
import CountrySelectionUI
@@ -33,7 +34,7 @@ private enum InnerState: Equatable {
public final class AuthorizationSequenceController: NavigationController, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
static func navigationBarTheme(_ theme: PresentationTheme) -> NavigationBarTheme {
return NavigationBarTheme(buttonColor: theme.intro.accentTextColor, disabledButtonColor: theme.intro.disabledTextColor, primaryTextColor: theme.intro.primaryTextColor, backgroundColor: .clear, opaqueBackgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: theme.rootController.navigationBar.badgeBackgroundColor, badgeStrokeColor: theme.rootController.navigationBar.badgeStrokeColor, badgeTextColor: theme.rootController.navigationBar.badgeTextColor)
return NavigationBarTheme(overallDarkAppearance: theme.overallDarkAppearance, buttonColor: theme.chat.inputPanel.panelControlColor, disabledButtonColor: theme.intro.disabledTextColor, primaryTextColor: theme.intro.primaryTextColor, backgroundColor: .clear, opaqueBackgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: theme.rootController.navigationBar.badgeBackgroundColor, badgeStrokeColor: theme.rootController.navigationBar.badgeStrokeColor, badgeTextColor: theme.rootController.navigationBar.badgeTextColor, edgeEffectColor: .clear, style: .glass)
}
private let sharedContext: SharedAccountContext
@@ -247,7 +248,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
let carrier = CTCarrier()
let mnc = carrier.mobileNetworkCode ?? "none"
AuthorizationSequenceController.presentEmailComposeController(address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_InvalidPhoneEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_InvalidPhoneEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData)
AuthorizationSequenceController.presentEmailComposeController(sharedContext: strongSelf.sharedContext, address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_InvalidPhoneEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_InvalidPhoneEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData)
}))
case .phoneLimitExceeded:
text = strongSelf.presentationData.strings.Login_PhoneFloodError
@@ -273,7 +274,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
let carrier = CTCarrier()
let mnc = carrier.mobileNetworkCode ?? "none"
AuthorizationSequenceController.presentEmailComposeController(address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_PhoneBannedEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_PhoneBannedEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData)
AuthorizationSequenceController.presentEmailComposeController(sharedContext: strongSelf.sharedContext, address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_PhoneBannedEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_PhoneBannedEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData)
}))
case let .generic(info):
text = strongSelf.presentationData.strings.Login_UnknownError
@@ -295,7 +296,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
errorString = "unknown"
}
AuthorizationSequenceController.presentEmailComposeController(address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_PhoneGenericEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_PhoneGenericEmailBody(formattedNumber, errorString, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData)
AuthorizationSequenceController.presentEmailComposeController(sharedContext: strongSelf.sharedContext, address: "recover@telegram.org", subject: strongSelf.presentationData.strings.Login_PhoneGenericEmailSubject(formattedNumber).string, body: strongSelf.presentationData.strings.Login_PhoneGenericEmailBody(formattedNumber, errorString, appVersion, systemVersion, locale, mnc).string, from: controller, presentationData: strongSelf.presentationData)
}))
case .timeout:
text = strongSelf.presentationData.strings.Login_NetworkError
@@ -307,7 +308,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
controller.present(strongSelf.sharedContext.makeProxySettingsController(sharedContext: strongSelf.sharedContext, account: strongSelf.account), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}))
}
(controller.navigationController as? NavigationController)?.presentOverlay(controller: standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: actions), inGlobal: true, blockInteraction: true)
(controller.navigationController as? NavigationController)?.presentOverlay(controller: textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: actions), inGlobal: true, blockInteraction: true)
controller.dismissConfirmation()
}
@@ -352,7 +353,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
text = strongSelf.presentationData.strings.Login_UnknownError
}
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}
}))
@@ -376,7 +377,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
if let currentController = currentController {
controller = currentController
} else {
controller = AuthorizationSequenceCodeEntryController(presentationData: self.presentationData, back: { [weak self] in
controller = AuthorizationSequenceCodeEntryController(sharedContext: self.sharedContext, presentationData: self.presentationData, back: { [weak self] in
guard let strongSelf = self else {
return
}
@@ -451,7 +452,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
let _ = self.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)).startStandalone()
}
controller.presentInGlobalOverlay(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]))
controller.presentInGlobalOverlay(textAlertController(sharedContext: self.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]))
}
})
)
@@ -510,7 +511,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
controller.resetCode()
}
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}
}
@@ -623,7 +624,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
controller.resetCode()
}
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}
}
@@ -645,7 +646,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
let mnc = carrier.mobileNetworkCode ?? "none"
let _ = strongSelf.engine.auth.reportMissingCode(phoneNumber: number, phoneCodeHash: phoneCodeHash, mnc: mnc).start()
AuthorizationSequenceController.presentDidNotGetCodeUI(controller: controller, presentationData: strongSelf.presentationData, phoneNumber: number, mnc: mnc)
AuthorizationSequenceController.presentDidNotGetCodeUI(sharedContext: strongSelf.sharedContext, controller: controller, presentationData: strongSelf.presentationData, phoneNumber: number, mnc: mnc)
}
} else {
controller?.inProgress = true
@@ -681,7 +682,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
text = strongSelf.presentationData.strings.Login_NetworkError
}
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: actions), in: .window(.root))
controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: actions), in: .window(.root))
}
}))
}
@@ -780,7 +781,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
text = strongSelf.presentationData.strings.Login_EmailNotAllowedError
}
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}, completed: {
controller?.inProgress = false
@@ -827,7 +828,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
switch authorization.credential {
case let appleIdCredential as ASAuthorizationAppleIDCredential:
guard let tokenData = appleIdCredential.identityToken, let token = String(data: tokenData, encoding: .utf8) else {
lastController?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
lastController?.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return
}
@@ -850,7 +851,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
case .emailNotAllowed:
text = strongSelf.presentationData.strings.Login_EmailNotAllowedError
}
lastController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
lastController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}))
} else {
@@ -891,7 +892,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
text = strongSelf.presentationData.strings.Login_InvalidEmailAddressError
}
lastController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
lastController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}
})
@@ -907,7 +908,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
guard let lastController = self.viewControllers.last as? ViewController else {
return
}
lastController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: error.localizedDescription, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
lastController.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: error.localizedDescription, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
@available(iOS 13.0, *)
@@ -927,7 +928,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
if let currentController = currentController {
controller = currentController
} else {
controller = AuthorizationSequencePasswordEntryController(presentationData: self.presentationData, back: { [weak self] in
controller = AuthorizationSequencePasswordEntryController(sharedContext: self.sharedContext, presentationData: self.presentationData, back: { [weak self] in
guard let strongSelf = self else {
return
}
@@ -954,7 +955,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
text = strongSelf.presentationData.strings.Login_UnknownError
}
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
controller.passwordIsInvalid()
}
}
@@ -988,14 +989,14 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
strongController.inProgress = false
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongController.didForgotWithNoRecovery = true
}))
}
}
controller.reset = { [weak self, weak controller] in
if let strongSelf = self, let strongController = controller {
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: suggestReset ? strongSelf.presentationData.strings.TwoStepAuth_RecoveryFailed : strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [
strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: suggestReset ? strongSelf.presentationData.strings.TwoStepAuth_RecoveryFailed : strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Login_ResetAccountProtected_Reset, action: {
if let strongSelf = self, let strongController = controller {
@@ -1015,7 +1016,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
case .limitExceeded:
text = strongSelf.presentationData.strings.Login_ResetAccountProtected_LimitExceeded
}
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}))
}
@@ -1082,7 +1083,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
})
controller.reset = { [weak self, weak controller] in
if let strongSelf = self, let strongController = controller {
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_ResetAccountConfirmation, actions: [
strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_ResetAccountConfirmation, actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Login_ResetAccountProtected_Reset, action: {
if let strongSelf = self, let strongController = controller {
@@ -1102,7 +1103,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
case .limitExceeded:
text = strongSelf.presentationData.strings.Login_ResetAccountProtected_LimitExceeded
}
strongController.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}))
}
@@ -1132,7 +1133,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
if let currentController = currentController {
controller = currentController
} else {
controller = AuthorizationSequenceSignUpController(presentationData: self.presentationData, back: { [weak self] in
controller = AuthorizationSequenceSignUpController(sharedContext: self.sharedContext, presentationData: self.presentationData, back: { [weak self] in
guard let strongSelf = self else {
return
}
@@ -1231,7 +1232,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
text = strongSelf.presentationData.strings.Login_UnknownError
}
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
controller.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}
}))
@@ -1379,7 +1380,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
}
}
static func presentEmailComposeController(address: String, subject: String, body: String, from controller: ViewController, presentationData: PresentationData) {
static func presentEmailComposeController(sharedContext: SharedAccountContext, address: String, subject: String, body: String, from controller: ViewController, presentationData: PresentationData) {
if MFMailComposeViewController.canSendMail() {
final class ComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
@objc func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
@@ -1398,7 +1399,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
controller.view.window?.rootViewController?.present(composeController, animated: true, completion: nil)
} else {
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_EmailNotConfiguredError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
controller.present(textAlertController(sharedContext: sharedContext, title: nil, text: presentationData.strings.Login_EmailNotConfiguredError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}
@@ -1451,6 +1452,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
}
public static func presentDidNotGetCodeUI(
sharedContext: SharedAccountContext,
controller: ViewController,
presentationData: PresentationData,
phoneNumber: String,
@@ -1470,6 +1472,6 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
emailBody.append("Locale: \(locale)\n")
emailBody.append("MNC: \(mnc)")
AuthorizationSequenceController.presentEmailComposeController(address: "sms@telegram.org", subject: presentationData.strings.Login_EmailCodeSubject(formattedNumber).string, body: emailBody, from: controller, presentationData: presentationData)
AuthorizationSequenceController.presentEmailComposeController(sharedContext: sharedContext, address: "sms@telegram.org", subject: presentationData.strings.Login_EmailCodeSubject(formattedNumber).string, body: emailBody, from: controller, presentationData: presentationData)
}
}
@@ -3,7 +3,9 @@ import UIKit
import Display
import AsyncDisplayKit
import TelegramPresentationData
import PresentationDataUtils
import ProgressNavigationButtonNode
import AccountContext
final class AuthorizationSequencePasswordEntryController: ViewController {
private var controllerNode: AuthorizationSequencePasswordEntryControllerNode {
@@ -12,6 +14,7 @@ final class AuthorizationSequencePasswordEntryController: ViewController {
private var validLayout: ContainerViewLayout?
private let sharedContext: SharedAccountContext
private let presentationData: PresentationData
var loginWithPassword: ((String) -> Void)?
@@ -40,7 +43,8 @@ final class AuthorizationSequencePasswordEntryController: ViewController {
}
}
init(presentationData: PresentationData, back: @escaping () -> Void) {
init(sharedContext: SharedAccountContext, presentationData: PresentationData, back: @escaping () -> Void) {
self.sharedContext = sharedContext
self.presentationData = presentationData
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings)))
@@ -153,10 +157,8 @@ final class AuthorizationSequencePasswordEntryController: ViewController {
}
func forgotPressed() {
/*if self.suggestReset {
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.TwoStepAuth_RecoveryFailed, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} else*/ if self.didForgotWithNoRecovery {
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
if self.didForgotWithNoRecovery {
self.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} else {
self.forgot?()
}
@@ -107,7 +107,7 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT
self.codeField.textField.tintColor = self.theme.list.itemAccentColor
self.codeField.textField.accessibilityHint = self.strings.Login_VoiceOver_Password
self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0)
self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), glass: true, height: 50.0, cornerRadius: 50.0 * 0.5)
self.proceedNode.progressType = .embedded
self.proceedNode.isEnabled = false
@@ -215,7 +215,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
).string
let presentationData = component.presentationData
AuthorizationSequenceController.presentEmailComposeController(address: component.supportEmailAddress, subject: environment.strings.Login_PhonePaidEmailSubject, body: body, from: controller, presentationData: presentationData)
AuthorizationSequenceController.presentEmailComposeController(sharedContext: component.sharedContext, address: component.supportEmailAddress, subject: environment.strings.Login_PhonePaidEmailSubject, body: body, from: controller, presentationData: presentationData)
}
func update(component: AuthorizationSequencePaymentScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
@@ -6,6 +6,7 @@ import SwiftSignalKit
import TelegramCore
import Postbox
import TelegramPresentationData
import PresentationDataUtils
import ProgressNavigationButtonNode
import AccountContext
import CountrySelectionUI
@@ -91,7 +92,7 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
}
if !otherAccountPhoneNumbers.1.isEmpty {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed))
}
if let countriesConfiguration {
@@ -173,7 +174,7 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
self.controllerNode.selectCountryCode = { [weak self] in
if let strongSelf = self {
let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme)
let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, glass: true)
controller.completeWithCountryCode = { code, name in
if let strongSelf = self, let currentData = strongSelf.currentData {
strongSelf.updateData(countryCode: Int32(code), countryName: name, number: currentData.2)
@@ -404,7 +405,7 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
}))
}
actions.append(TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {}))
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_PhoneNumberAlreadyAuthorized, actions: actions), in: .window(.root))
self.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.Login_PhoneNumberAlreadyAuthorized, actions: actions), in: .window(.root))
} else {
if let validLayout = self.validLayout, validLayout.size.width > 320.0 {
let (code, formattedNumber) = self.controllerNode.formattedCodeAndNumber
@@ -425,7 +426,7 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
strongSelf.loginWithNumber?(strongSelf.controllerNode.currentNumber, strongSelf.controllerNode.syncContacts)
}
}))
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: logInNumber, text: self.presentationData.strings.Login_PhoneNumberConfirmation, actions: actions), in: .window(.root))
self.present(textAlertController(sharedContext: self.sharedContext, title: logInNumber, text: self.presentationData.strings.Login_PhoneNumberConfirmation, actions: actions), in: .window(.root))
}
}
} else {
@@ -423,7 +423,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.phoneAndCountryNode = PhoneAndCountryNode(strings: strings, theme: theme)
self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0)
self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), glass: true, height: 50.0, cornerRadius: 50 * 0.5)
self.proceedNode.progressType = .embedded
self.proceedNode.isEnabled = false
@@ -857,7 +857,7 @@ final class PhoneConfirmationController: ViewController {
self.cancelButton.accessibilityTraits = [.button]
self.cancelButton.accessibilityLabel = strings.Login_Edit
self.proceedNode = SolidRoundedButtonNode(title: strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 11.0)
self.proceedNode = SolidRoundedButtonNode(title: strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: theme), glass: true, height: 50.0, cornerRadius: 50.0 * 0.5)
self.proceedNode.progressType = .embedded
let font = Font.with(size: 20.0, design: .regular, traits: [.monospacedNumbers])
@@ -5,6 +5,7 @@ import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import PresentationDataUtils
import LegacyComponents
import ProgressNavigationButtonNode
import ImageCompression
@@ -13,6 +14,7 @@ import Postbox
import TextFormat
import MoreButtonNode
import ContextUI
import AccountContext
final class AuthorizationSequenceSignUpController: ViewController {
private var controllerNode: AuthorizationSequenceSignUpControllerNode {
@@ -23,6 +25,7 @@ final class AuthorizationSequenceSignUpController: ViewController {
private let moreButtonNode: MoreButtonNode
private let sharedContext: SharedAccountContext
private let presentationData: PresentationData
private let back: () -> Void
@@ -46,7 +49,8 @@ final class AuthorizationSequenceSignUpController: ViewController {
}
}
init(presentationData: PresentationData, back: @escaping () -> Void, displayCancel: Bool) {
init(sharedContext: SharedAccountContext, presentationData: PresentationData, back: @escaping () -> Void, displayCancel: Bool) {
self.sharedContext = sharedContext
self.presentationData = presentationData
self.back = back
@@ -68,7 +72,7 @@ final class AuthorizationSequenceSignUpController: ViewController {
guard let strongSelf = self else {
return
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Login_CancelSignUpConfirmation, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Login_CancelPhoneVerificationContinue, action: {
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: presentationData.strings.Login_CancelSignUpConfirmation, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Login_CancelPhoneVerificationContinue, action: {
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Login_CancelPhoneVerificationStop, action: {
back()
})]), in: .window(.root))
@@ -92,7 +96,7 @@ final class AuthorizationSequenceSignUpController: ViewController {
}
@objc private func cancelPressed() {
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_CancelSignUpConfirmation, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Login_CancelPhoneVerificationContinue, action: {
self.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.Login_CancelSignUpConfirmation, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Login_CancelPhoneVerificationContinue, action: {
}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Login_CancelPhoneVerificationStop, action: { [weak self] in
self?.back()
})]), in: .window(.root))
@@ -73,10 +73,12 @@ public final class AuthorizationSequenceSplashController: ViewController {
self.controller = RMIntroViewController(backgroundColor: theme.list.plainBackgroundColor, primaryColor: theme.list.itemPrimaryTextColor, buttonColor: theme.intro.startButtonColor, accentColor: theme.list.itemAccentColor, regularDotColor: theme.intro.dotColor, highlightedDotColor: theme.list.itemAccentColor, suggestedLocalizationSignal: localizationSignal)
self.startButton = SolidRoundedButtonNode(title: "Start Messaging", theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 13.0, isShimmering: true)
self.startButton = SolidRoundedButtonNode(title: "Start Messaging", theme: SolidRoundedButtonTheme(theme: theme), glass: true, height: 50.0, cornerRadius: 50.0 * 0.5, isShimmering: true)
super.init(navigationBarPresentationData: nil)
self._hasGlassStyle = true
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style
@@ -114,7 +114,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode {
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.imageNode)
@@ -113,7 +113,7 @@ class BotCheckoutPriceItemNode: ListViewItemNode {
self.maskNode = ASImageNode()
self.maskNode.isUserInteractionEnabled = false
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.backgroundNode)
self.addSubnode(self.titleNode)
@@ -509,7 +509,7 @@ class BotCheckoutTipItemNode: ListViewItemNode, UITextFieldDelegate {
self.maskNode = ASImageNode()
self.maskNode.isUserInteractionEnabled = false
super.init(layerBacked: false, dynamicBounce: false)
super.init(layerBacked: false)
self.addSubnode(self.backgroundNode)
+7
View File
@@ -50,6 +50,13 @@ swift_library(
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/Utils/DeviceModel",
"//submodules/LegacyMediaPickerUI",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
"//submodules/TelegramUI/Components/GlassControls",
],
visibility = [
"//visibility:public",
@@ -9,6 +9,9 @@ import AccountContext
import BundleIconComponent
import MultilineTextComponent
import UrlEscaping
import GlassBackgroundComponent
import GlassBarButtonComponent
import EdgeEffect
final class AddressBarContentComponent: Component {
public typealias EnvironmentType = BrowserNavigationBarEnvironment
@@ -19,6 +22,8 @@ final class AddressBarContentComponent: Component {
let url: String
let isSecure: Bool
let isExpanded: Bool
let readingProgress: CGFloat
let loadingProgress: Double?
let performAction: ActionSlot<BrowserScreen.Action>
init(
@@ -28,6 +33,8 @@ final class AddressBarContentComponent: Component {
url: String,
isSecure: Bool,
isExpanded: Bool,
readingProgress: CGFloat,
loadingProgress: Double?,
performAction: ActionSlot<BrowserScreen.Action>
) {
self.theme = theme
@@ -36,6 +43,8 @@ final class AddressBarContentComponent: Component {
self.url = url
self.isSecure = isSecure
self.isExpanded = isExpanded
self.readingProgress = readingProgress
self.loadingProgress = loadingProgress
self.performAction = performAction
}
@@ -58,6 +67,12 @@ final class AddressBarContentComponent: Component {
if lhs.isExpanded != rhs.isExpanded {
return false
}
if lhs.readingProgress != rhs.readingProgress {
return false
}
if lhs.loadingProgress != rhs.loadingProgress {
return false
}
return true
}
@@ -85,6 +100,8 @@ final class AddressBarContentComponent: Component {
var isSecure: Bool
var collapseFraction: CGFloat
var isTablet: Bool
var readingProgress: CGFloat
var loadingProgress: Double?
static func ==(lhs: Params, rhs: Params) -> Bool {
if lhs.theme !== rhs.theme {
@@ -111,26 +128,33 @@ final class AddressBarContentComponent: Component {
if lhs.isTablet != rhs.isTablet {
return false
}
if lhs.readingProgress != rhs.readingProgress {
return false
}
if lhs.loadingProgress != rhs.loadingProgress {
return false
}
return true
}
}
private let activated: (Bool) -> Void = { _ in }
private let deactivated: (Bool) -> Void = { _ in }
private let backgroundLayer: SimpleLayer
private let iconView: UIImageView
private let backgroundView: GlassBackgroundView
private let clearIconView: UIImageView
private let clearIconButton: HighlightTrackingButton
private let cancelButtonTitle: ComponentView<Empty>
private let cancelButton: HighlightTrackingButton
private let cancelButton = ComponentView<Empty>()
private var placeholderContent = ComponentView<Empty>()
private var titleContent = ComponentView<Empty>()
private let clippingView = UIView()
private var loadingProgress = ComponentView<Empty>()
private var readingProgressView = UIView()
private var textFrame: CGRect?
private var textField: TextField?
@@ -144,50 +168,30 @@ final class AddressBarContentComponent: Component {
}
init() {
self.backgroundLayer = SimpleLayer()
self.iconView = UIImageView()
self.backgroundView = GlassBackgroundView()
self.clearIconView = UIImageView()
self.clearIconButton = HighlightableButton()
self.clearIconView.isHidden = false
self.clearIconButton.isHidden = false
self.cancelButtonTitle = ComponentView()
self.cancelButton = HighlightTrackingButton()
super.init(frame: CGRect())
self.layer.addSublayer(self.backgroundLayer)
self.clippingView.clipsToBounds = true
self.addSubview(self.iconView)
self.addSubview(self.clearIconView)
self.addSubview(self.clearIconButton)
self.addSubview(self.backgroundView)
self.backgroundView.contentView.addSubview(self.clippingView)
self.clippingView.addSubview(self.readingProgressView)
self.backgroundView.contentView.addSubview(self.clearIconView)
self.backgroundView.contentView.addSubview(self.clearIconButton)
self.addSubview(self.cancelButton)
self.clipsToBounds = true
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
self.tapRecognizer = tapRecognizer
self.addGestureRecognizer(tapRecognizer)
self.cancelButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view {
cancelButtonTitleView.layer.removeAnimation(forKey: "opacity")
cancelButtonTitleView.alpha = 0.4
}
} else {
if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view {
cancelButtonTitleView.alpha = 1.0
cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
}
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside)
self.clearIconButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
@@ -263,11 +267,16 @@ final class AddressBarContentComponent: Component {
self.placeholderContent.view?.isHidden = !text.isEmpty
if let params = self.params {
self.update(theme: params.theme, strings: params.strings, size: params.size, isActive: params.isActive, title: params.title, isSecure: params.isSecure, collapseFraction: params.collapseFraction, isTablet: params.isTablet, transition: .immediate)
self.update(theme: params.theme, strings: params.strings, size: params.size, isActive: params.isActive, title: params.title, isSecure: params.isSecure, collapseFraction: params.collapseFraction, isTablet: params.isTablet, readingProgress: params.readingProgress, loadingProgress: params.loadingProgress, transition: .immediate)
}
}
func update(component: AddressBarContentComponent, availableSize: CGSize, environment: Environment<BrowserNavigationBarEnvironment>, transition: ComponentTransition) -> CGSize {
func update(
component: AddressBarContentComponent,
availableSize: CGSize,
environment: Environment<BrowserNavigationBarEnvironment>,
transition: ComponentTransition
) -> CGSize {
let collapseFraction = environment[BrowserNavigationBarEnvironment.self].fraction
let wasExpanded = self.component?.isExpanded ?? false
@@ -282,12 +291,36 @@ final class AddressBarContentComponent: Component {
let isActive = self.textField?.isFirstResponder ?? false
let title = getDisplayUrl(component.url, hostOnly: true)
self.update(theme: component.theme, strings: component.strings, size: availableSize, isActive: isActive, title: title.lowercased(), isSecure: component.isSecure, collapseFraction: collapseFraction, isTablet: component.metrics.isTablet, transition: transition)
self.update(
theme: component.theme,
strings: component.strings,
size: availableSize,
isActive: isActive,
title: title.lowercased(),
isSecure: component.isSecure,
collapseFraction: collapseFraction,
isTablet: component.metrics.isTablet,
readingProgress: component.readingProgress,
loadingProgress: component.loadingProgress,
transition: transition
)
return availableSize
}
public func update(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, isActive: Bool, title: String, isSecure: Bool, collapseFraction: CGFloat, isTablet: Bool, transition: ComponentTransition) {
public func update(
theme: PresentationTheme,
strings: PresentationStrings,
size: CGSize,
isActive: Bool,
title: String,
isSecure: Bool,
collapseFraction: CGFloat,
isTablet: Bool,
readingProgress: CGFloat,
loadingProgress: Double?,
transition: ComponentTransition
) {
let params = Params(
theme: theme,
strings: strings,
@@ -296,7 +329,9 @@ final class AddressBarContentComponent: Component {
title: title,
isSecure: isSecure,
collapseFraction: collapseFraction,
isTablet: isTablet
isTablet: isTablet,
readingProgress: readingProgress,
loadingProgress: loadingProgress
)
if self.params == params {
@@ -306,8 +341,6 @@ final class AddressBarContentComponent: Component {
let isActiveWithText = self.component?.isExpanded ?? false
if self.params?.theme !== theme {
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Lock"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.iconView.tintColor = theme.rootController.navigationSearchBar.inputIconColor
self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.clearIconView.tintColor = theme.rootController.navigationSearchBar.inputClearButtonColor
}
@@ -315,57 +348,9 @@ final class AddressBarContentComponent: Component {
self.params = params
let sideInset: CGFloat = 10.0
let inputHeight: CGFloat = 36.0
let inputHeight: CGFloat = 44.0
let topInset: CGFloat = (size.height - inputHeight) / 2.0
self.backgroundLayer.backgroundColor = theme.rootController.navigationSearchBar.inputFillColor.cgColor
self.backgroundLayer.cornerRadius = 10.5
transition.setAlpha(layer: self.backgroundLayer, alpha: max(0.0, min(1.0, 1.0 - collapseFraction * 1.5)))
let cancelTextSize = self.cancelButtonTitle.update(
transition: .immediate,
component: AnyComponent(Text(
text: strings.Common_Cancel,
font: Font.regular(17.0),
color: theme.rootController.navigationBar.accentTextColor
)),
environment: {},
containerSize: CGSize(width: size.width - 32.0, height: 100.0)
)
let cancelButtonSpacing: CGFloat = 8.0
var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight))
if isActiveWithText && !isTablet {
backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing
}
transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame)
transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height)))
self.cancelButton.isUserInteractionEnabled = isActiveWithText && !isTablet
let textX: CGFloat = backgroundFrame.minX + sideInset
let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height))
let placeholderSize = self.placeholderContent.update(
transition: transition,
component: AnyComponent(
Text(text: strings.WebBrowser_AddressPlaceholder, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
),
environment: {},
containerSize: size
)
if let placeholderContentView = self.placeholderContent.view {
if placeholderContentView.superview == nil {
placeholderContentView.alpha = 0.0
placeholderContentView.isHidden = true
self.addSubview(placeholderContentView)
}
let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.midY - placeholderSize.height / 2.0), size: placeholderSize)
transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame)
transition.setAlpha(view: placeholderContentView, alpha: isActiveWithText ? 1.0 : 0.0)
}
let titleSize = self.titleContent.update(
transition: transition,
component: AnyComponent(
@@ -379,51 +364,96 @@ final class AddressBarContentComponent: Component {
environment: {},
containerSize: CGSize(width: size.width - 36.0, height: size.height)
)
var titleContentFrame = CGRect(origin: CGPoint(x: isActiveWithText ? textFrame.minX : backgroundFrame.midX - titleSize.width / 2.0, y: backgroundFrame.midY - titleSize.height / 2.0), size: titleSize)
if isSecure && !isActiveWithText {
titleContentFrame.origin.x += 7.0
let expandedBackgroundWidth = size.width - 14.0 * 2.0
let collapsedBackgroundWidth = titleSize.width + 32.0
var backgroundSize = CGSize(width: expandedBackgroundWidth * (1.0 - collapseFraction) + collapsedBackgroundWidth * collapseFraction, height: 44.0)
let cancelButtonSpacing: CGFloat = 8.0
let cancelSize = self.cancelButton.update(
transition: transition,
component: AnyComponent(
GlassBarButtonComponent(
size: CGSize(width: 44.0, height: 44.0),
backgroundColor: nil,
isDark: theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(name: "Navigation/Close", tintColor: theme.chat.inputPanel.panelControlColor)
)),
action: { [weak self] _ in
self?.cancelPressed()
}
)
),
environment: {},
containerSize: CGSize(width: 44.0, height: 44.0)
)
if isActiveWithText && !isTablet {
backgroundSize.width -= cancelSize.width + cancelButtonSpacing + 4.0
}
var titleSizeChanged = false
self.backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
var backgroundFrame = CGRect(origin: CGPoint(x: floor((size.width - backgroundSize.width) / 2.0), y: topInset), size: backgroundSize)
if isActiveWithText && !isTablet {
backgroundFrame.origin.x = 16.0
}
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
transition.setFrame(view: self.clippingView, frame: CGRect(origin: .zero, size: backgroundFrame.size))
transition.setCornerRadius(layer: self.clippingView.layer, cornerRadius: backgroundFrame.size.height * 0.5)
let textX: CGFloat = sideInset
let textFrame = CGRect(origin: CGPoint(x: textX, y: 0.0), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height))
let placeholderSize = self.placeholderContent.update(
transition: transition,
component: AnyComponent(
Text(text: strings.WebBrowser_AddressPlaceholder, font: Font.regular(17.0), color: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
),
environment: {},
containerSize: size
)
if let placeholderContentView = self.placeholderContent.view {
if placeholderContentView.superview == nil {
placeholderContentView.alpha = 0.0
placeholderContentView.isHidden = true
self.backgroundView.contentView.addSubview(placeholderContentView)
}
let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.size.height / 2.0 - placeholderSize.height / 2.0), size: placeholderSize)
transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame)
transition.setAlpha(view: placeholderContentView, alpha: isActiveWithText ? 1.0 : 0.0)
}
let titleContentFrame = CGRect(origin: CGPoint(x: isActiveWithText ? textFrame.minX : backgroundFrame.width / 2.0 - titleSize.width / 2.0, y: backgroundFrame.height / 2.0 - titleSize.height / 2.0), size: titleSize)
if let titleContentView = self.titleContent.view {
if titleContentView.superview == nil {
self.addSubview(titleContentView)
}
if titleContentView.frame.width != titleContentFrame.size.width {
titleSizeChanged = true
self.backgroundView.contentView.addSubview(titleContentView)
}
transition.setPosition(view: titleContentView, position: titleContentFrame.center)
titleContentView.bounds = CGRect(origin: .zero, size: titleContentFrame.size)
transition.setAlpha(view: titleContentView, alpha: isActiveWithText ? 0.0 : 1.0)
}
if let image = self.iconView.image {
let iconFrame = CGRect(origin: CGPoint(x: titleContentFrame.minX - image.size.width - 3.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
var iconTransition = transition
if titleSizeChanged {
iconTransition = .immediate
}
iconTransition.setFrame(view: self.iconView, frame: iconFrame)
transition.setAlpha(view: self.iconView, alpha: isActiveWithText || !isSecure ? 0.0 : 1.0)
}
if let image = self.clearIconView.image {
let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.width - image.size.width - 4.0, y: floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
transition.setFrame(view: self.clearIconView, frame: iconFrame)
transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0))
transition.setAlpha(view: self.clearIconView, alpha: isActiveWithText ? 1.0 : 0.0)
self.clearIconButton.isUserInteractionEnabled = isActiveWithText
}
if let cancelButtonTitleComponentView = self.cancelButtonTitle.view {
if cancelButtonTitleComponentView.superview == nil {
self.addSubview(cancelButtonTitleComponentView)
cancelButtonTitleComponentView.isUserInteractionEnabled = false
if let cancelButtonView = self.cancelButton.view {
if cancelButtonView.superview == nil {
self.addSubview(cancelButtonView)
}
transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize))
transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText && !isTablet ? 1.0 : 0.0)
transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelSize.height) / 2.0)), size: cancelSize))
transition.setAlpha(view: cancelButtonView, alpha: isActiveWithText && !isTablet ? 1.0 : 0.0)
}
let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height))
let textFieldFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: CGSize(width: backgroundFrame.width - sideInset, height: backgroundFrame.height))
let textField: TextField
if let current = self.textField {
@@ -434,7 +464,7 @@ final class AddressBarContentComponent: Component {
textField.autocorrectionType = .no
textField.keyboardType = .URL
textField.returnKeyType = .go
self.insertSubview(textField, belowSubview: self.clearIconView)
self.backgroundView.contentView.insertSubview(textField, belowSubview: self.clearIconView)
self.textField = textField
textField.delegate = self
@@ -450,9 +480,34 @@ final class AddressBarContentComponent: Component {
}
textField.textColor = theme.rootController.navigationSearchBar.inputTextColor
transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideInset, y: backgroundFrame.minY - UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideInset - 32.0, height: backgroundFrame.height)))
transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: sideInset, y: -UIScreenPixel), size: CGSize(width: backgroundFrame.width - sideInset - 32.0, height: backgroundFrame.height)))
transition.setAlpha(view: textField, alpha: isActiveWithText ? 1.0 : 0.0)
textField.isUserInteractionEnabled = isActiveWithText
let loadingProgressInset: CGFloat = 12.0
let loadingProgressSize = CGSize(width: backgroundSize.width - loadingProgressInset * 2.0, height: 2.0)
let _ = self.loadingProgress.update(
transition: transition,
component: AnyComponent(LoadingProgressComponent(
color: theme.rootController.navigationBar.accentTextColor,
height: loadingProgressSize.height,
value: params.loadingProgress ?? 0.0
)),
environment: {},
containerSize: loadingProgressSize
)
if let loadingProgressView = self.loadingProgress.view {
if loadingProgressView.superview == nil {
self.clippingView.addSubview(loadingProgressView)
}
transition.setFrame(view: loadingProgressView, frame: CGRect(origin: CGPoint(x: loadingProgressInset, y: backgroundSize.height - loadingProgressSize.height), size: loadingProgressSize))
transition.setAlpha(view: loadingProgressView, alpha: isActiveWithText ? 0.0 : 1.0)
}
self.readingProgressView.backgroundColor = theme.rootController.navigationBar.primaryTextColor.withMultipliedAlpha(0.07)
self.readingProgressView.frame = CGRect(origin: .zero, size: CGSize(width: backgroundSize.width * params.readingProgress, height: backgroundSize.height))
transition.setAlpha(view: self.readingProgressView, alpha: isActiveWithText ? 0.0 : 1.0)
}
}
@@ -19,6 +19,7 @@ final class BrowserAddressListComponent: Component {
let insets: UIEdgeInsets
let metrics: LayoutMetrics
let addressBarFrame: CGRect
let navigationBarHeight: CGFloat
let performAction: ActionSlot<BrowserScreen.Action>
let presentInGlobalOverlay: (ViewController) -> Void
@@ -29,6 +30,7 @@ final class BrowserAddressListComponent: Component {
insets: UIEdgeInsets,
metrics: LayoutMetrics,
addressBarFrame: CGRect,
navigationBarHeight: CGFloat,
performAction: ActionSlot<BrowserScreen.Action>,
presentInGlobalOverlay: @escaping (ViewController) -> Void
) {
@@ -38,6 +40,7 @@ final class BrowserAddressListComponent: Component {
self.insets = insets
self.metrics = metrics
self.addressBarFrame = addressBarFrame
self.navigationBarHeight = navigationBarHeight
self.performAction = performAction
self.presentInGlobalOverlay = presentInGlobalOverlay
}
@@ -61,6 +64,9 @@ final class BrowserAddressListComponent: Component {
if lhs.addressBarFrame != rhs.addressBarFrame {
return false
}
if lhs.navigationBarHeight != rhs.navigationBarHeight {
return false
}
return true
}
@@ -214,15 +220,16 @@ final class BrowserAddressListComponent: Component {
var validIds: [AnyHashable] = []
var validSectionHeaders: [AnyHashable] = []
var sectionOffset: CGFloat = 0.0
let headerOffset: CGFloat = self.scrollView.frame.minY
let sideInset: CGFloat = 0.0
let containerInset: CGFloat = 0.0
let containerInset: CGFloat = self.scrollView.frame.minY
for sectionIndex in 0 ..< itemLayout.sections.count {
let section = itemLayout.sections[sectionIndex]
do {
var sectionHeaderFrame = CGRect(origin: CGPoint(x: sideInset, y: sectionOffset - self.scrollView.bounds.minY), size: CGSize(width: itemLayout.containerSize.width, height: section.insets.top))
var sectionHeaderFrame = CGRect(origin: CGPoint(x: sideInset, y: headerOffset + sectionOffset - self.scrollView.bounds.minY), size: CGSize(width: itemLayout.containerSize.width, height: section.insets.top))
let sectionHeaderMinY = topOffset + containerInset
let sectionHeaderMaxY = containerInset + sectionOffset - self.scrollView.bounds.minY + section.totalHeight - 28.0
@@ -540,7 +547,7 @@ final class BrowserAddressListComponent: Component {
let containerFrame: CGRect
if component.metrics.isTablet {
let containerSize = CGSize(width: component.addressBarFrame.width + 32.0, height: 540.0)
containerFrame = CGRect(origin: CGPoint(x: floor(component.addressBarFrame.center.x - containerSize.width / 2.0), y: 72.0), size: containerSize)
containerFrame = CGRect(origin: CGPoint(x: floor(component.addressBarFrame.center.x - containerSize.width / 2.0), y: 100.0), size: containerSize)
self.backgroundView.layer.cornerRadius = 10.0
} else {
@@ -602,23 +609,28 @@ final class BrowserAddressListComponent: Component {
let itemLayout = ItemLayout(containerSize: containerFrame.size, insets: .zero, sections: sections)
self.itemLayout = itemLayout
let containerWidth = containerFrame.size.width
let scrollContentHeight = max(itemLayout.contentHeight, containerFrame.size.height)
self.ignoreScrolling = true
transition.setFrame(view: self.scrollView, frame: CGRect(origin: .zero, size: containerFrame.size))
let contentSize = CGSize(width: containerWidth, height: scrollContentHeight)
let scrollFrame: CGRect
if component.metrics.isTablet {
scrollFrame = CGRect(origin: .zero, size: containerFrame.size)
} else {
scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: component.navigationBarHeight), size: CGSize(width: containerFrame.size.width, height: containerFrame.size.height - component.navigationBarHeight))
}
transition.setFrame(view: self.scrollView, frame: scrollFrame)
let contentSize = CGSize(width: scrollFrame.width, height: scrollContentHeight)
if contentSize != self.scrollView.contentSize {
self.scrollView.contentSize = contentSize
}
if resetScrolling {
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: containerWidth, height: containerFrame.size.height))
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollFrame.size)
}
self.ignoreScrolling = false
self.updateScrolling(transition: transition)
transition.setFrame(view: self.backgroundView, frame: containerFrame)
transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: .zero, size: CGSize(width: containerWidth, height: scrollContentHeight)))
transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: .zero, size: CGSize(width: scrollFrame.width, height: scrollContentHeight)))
if component.metrics.isTablet {
transition.setFrame(view: self.shadowView, frame: containerFrame.insetBy(dx: -60.0, dy: -60.0))

Some files were not shown because too many files have changed in this diff Show More