diff --git a/backend/Cargo.lock b/backend/Cargo.lock index d4a8c1c..b8b4b58 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" dependencies = [ "async-lock", "cfg-if", @@ -113,7 +113,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.44", + "rustix", "slab", "tracing", "windows-sys 0.59.0", @@ -132,9 +132,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" dependencies = [ "async-channel", "async-io", @@ -145,7 +145,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 0.38.44", + "rustix", "tracing", ] @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" dependencies = [ "async-io", "async-lock", @@ -172,7 +172,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.44", + "rustix", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -232,9 +232,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -265,9 +265,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -366,7 +366,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cairo-sys-rs", "glib", "libc", @@ -429,9 +429,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.21" +version = "1.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" dependencies = [ "shlex", ] @@ -483,8 +483,10 @@ checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link", ] @@ -525,9 +527,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -545,7 +547,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation", "core-graphics-types", "foreign-types", @@ -558,7 +560,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation", "libc", ] @@ -744,7 +746,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", ] @@ -773,9 +775,9 @@ dependencies = [ [[package]] name = "dlopen2_derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", @@ -820,9 +822,9 @@ checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "embed-resource" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbc6e0d8e0c03a655b53ca813f0463d2c956bc4db8138dbc89f120b066551e3" +checksum = "e8fe7d068ca6b3a5782ca5ec9afc244acd99dd441e4686a83b1c3973aba1d489" dependencies = [ "cc", "memchr", @@ -883,9 +885,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -1226,9 +1228,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -1280,7 +1282,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "futures-channel", "futures-core", "futures-executor", @@ -1416,9 +1418,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "hex" @@ -1501,17 +1503,21 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1531,7 +1537,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core", ] [[package]] @@ -1555,21 +1561,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -1578,31 +1585,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -1610,67 +1597,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1690,9 +1664,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1735,6 +1709,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-docker" version = "0.2.0" @@ -1849,7 +1833,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "serde", "unicode-segmentation", ] @@ -1919,16 +1903,10 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1937,15 +1915,15 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1967,11 +1945,15 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" name = "manhunt-app" version = "0.1.0" dependencies = [ + "chrono", + "rand 0.9.1", "serde", "serde_json", "tauri", "tauri-build", + "tauri-plugin-geolocation", "tauri-plugin-opener", + "tokio", ] [[package]] @@ -2027,13 +2009,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2063,7 +2045,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "jni-sys", "log", "ndk-sys", @@ -2095,11 +2077,11 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -2180,7 +2162,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.6.1", "libc", "objc2 0.6.1", @@ -2199,7 +2181,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-foundation 0.3.1", ] @@ -2210,7 +2192,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-foundation 0.3.1", ] @@ -2221,7 +2203,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "dispatch2", "objc2 0.6.1", ] @@ -2232,7 +2214,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "dispatch2", "objc2 0.6.1", "objc2-core-foundation", @@ -2270,7 +2252,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -2282,7 +2264,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.6.1", "libc", "objc2 0.6.1", @@ -2295,7 +2277,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-core-foundation", ] @@ -2306,7 +2288,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -2318,7 +2300,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -2331,7 +2313,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-foundation 0.3.1", ] @@ -2342,7 +2324,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-core-foundation", "objc2-foundation 0.3.1", @@ -2354,7 +2336,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.6.1", "objc2 0.6.1", "objc2-app-kit", @@ -2438,9 +2420,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -2448,9 +2430,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -2662,19 +2644,28 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 0.38.44", + "rustix", "tracing", "windows-sys 0.59.0", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2812,6 +2803,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -2832,6 +2833,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -2850,6 +2861,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -2880,7 +2900,7 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2925,9 +2945,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" dependencies = [ "base64 0.22.1", "bytes", @@ -2952,13 +2972,13 @@ dependencies = [ "tokio", "tokio-util", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "windows-registry", ] [[package]] @@ -2976,37 +2996,24 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys", "windows-sys 0.59.0", ] [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -3294,9 +3301,9 @@ checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3465,7 +3472,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation", "core-graphics", "crossbeam-channel", @@ -3492,8 +3499,8 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows 0.61.1", - "windows-core 0.61.0", + "windows", + "windows-core", "windows-version", "x11-dl", ] @@ -3563,7 +3570,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows 0.61.1", + "windows", ] [[package]] @@ -3646,11 +3653,25 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-geolocation" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70978d3dbca4d900f8708dad1e0cff32c52de578101998d0f30dc7e12104e85" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + [[package]] name = "tauri-plugin-opener" -version = "2.2.6" +version = "2.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fdc6cb608e04b7d2b6d1f21e9444ad49245f6d03465ba53323d692d1ceb1a30" +checksum = "66644b71a31ec1a8a52c4a16575edd28cf763c87cf4a7da24c884122b5c77097" dependencies = [ "dunce", "glob", @@ -3664,7 +3685,7 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.12", "url", - "windows 0.60.0", + "windows", "zbus", ] @@ -3687,7 +3708,7 @@ dependencies = [ "tauri-utils", "thiserror 2.0.12", "url", - "windows 0.61.1", + "windows", ] [[package]] @@ -3713,7 +3734,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.61.1", + "windows", "wry", ] @@ -3768,14 +3789,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", + "rustix", "windows-sys 0.59.0", ] @@ -3869,9 +3890,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -3879,9 +3900,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -3889,9 +3910,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "tokio-util" version = "0.7.15" @@ -3959,7 +3992,7 @@ dependencies = [ "serde_spanned", "toml_datetime", "toml_write", - "winnow 0.7.9", + "winnow 0.7.10", ] [[package]] @@ -3983,6 +4016,24 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc2d9e086a412a451384326f521c8123a99a466b329941a9403696bff9b0da2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -4160,12 +4211,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -4174,12 +4219,14 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] @@ -4400,9 +4447,9 @@ checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.61.1", - "windows-core 0.61.0", - "windows-implement 0.60.0", + "windows", + "windows-core", + "windows-implement", "windows-interface", ] @@ -4424,8 +4471,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295" dependencies = [ "thiserror 2.0.12", - "windows 0.61.1", - "windows-core 0.61.0", + "windows", + "windows-core", ] [[package]] @@ -4474,39 +4521,17 @@ dependencies = [ "windows-version", ] -[[package]] -name = "windows" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529" -dependencies = [ - "windows-collections 0.1.1", - "windows-core 0.60.1", - "windows-future 0.1.1", - "windows-link", - "windows-numerics 0.1.1", -] - [[package]] name = "windows" version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.0", - "windows-future 0.2.0", + "windows-collections", + "windows-core", + "windows-future", "windows-link", - "windows-numerics 0.2.0", -] - -[[package]] -name = "windows-collections" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec" -dependencies = [ - "windows-core 0.60.1", + "windows-numerics", ] [[package]] @@ -4515,64 +4540,31 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.0", -] - -[[package]] -name = "windows-core" -version = "0.60.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" -dependencies = [ - "windows-implement 0.59.0", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings 0.3.1", + "windows-core", ] [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.0", + "windows-implement", "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.0", -] - -[[package]] -name = "windows-future" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0" -dependencies = [ - "windows-core 0.60.1", - "windows-link", + "windows-strings", ] [[package]] name = "windows-future" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.61.0", + "windows-core", "windows-link", -] - -[[package]] -name = "windows-implement" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", + "windows-threading", ] [[package]] @@ -4603,60 +4595,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" -[[package]] -name = "windows-numerics" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed" -dependencies = [ - "windows-core 0.60.1", - "windows-link", -] - [[package]] name = "windows-numerics" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.0", + "windows-core", "windows-link", ] -[[package]] -name = "windows-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", -] - [[package]] name = "windows-result" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -4670,15 +4632,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -4712,21 +4665,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -4736,7 +4674,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -4744,19 +4682,12 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.53.0" +name = "windows-threading" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", ] [[package]] @@ -4774,180 +4705,90 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winnow" version = "0.5.40" @@ -4959,21 +4800,21 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.52.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -4982,20 +4823,14 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wry" @@ -5035,8 +4870,8 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.61.1", - "windows-core 0.61.0", + "windows", + "windows-core", "windows-version", "x11-dl", ] @@ -5064,9 +4899,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -5076,9 +4911,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -5088,9 +4923,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.6.0" +version = "5.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2522b82023923eecb0b366da727ec883ace092e7887b61d3da5139f26b44da58" +checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68" dependencies = [ "async-broadcast", "async-executor", @@ -5113,7 +4948,7 @@ dependencies = [ "tracing", "uds_windows", "windows-sys 0.59.0", - "winnow 0.7.9", + "winnow 0.7.10", "zbus_macros", "zbus_names", "zvariant", @@ -5121,9 +4956,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.6.0" +version = "5.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d2e12843c75108c00c618c2e8ef9675b50b6ec095b36dc965f2e5aed463c15" +checksum = "a17e7e5eec1550f747e71a058df81a9a83813ba0f6a95f39c4e218bdc7ba366a" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", @@ -5142,7 +4977,7 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" dependencies = [ "serde", "static_assertions", - "winnow 0.7.9", + "winnow 0.7.10", "zvariant", ] @@ -5187,11 +5022,22 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -5200,9 +5046,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", @@ -5211,23 +5057,23 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.5.1" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "557e89d54880377a507c94cd5452f20e35d14325faf9d2958ebeadce0966c1b2" +checksum = "9d30786f75e393ee63a21de4f9074d4c038d52c5b1bb4471f955db249f9dffb1" dependencies = [ "endi", "enumflags2", "serde", - "winnow 0.7.9", + "winnow 0.7.10", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.5.1" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "757779842a0d242061d24c28be589ce392e45350dfb9186dfd7a042a2e19870c" +checksum = "75fda702cd42d735ccd48117b1630432219c0e9616bf6cb0f8350844ee4d9580" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", @@ -5247,5 +5093,5 @@ dependencies = [ "serde", "static_assertions", "syn 2.0.101", - "winnow 0.7.9", + "winnow 0.7.10", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index e0576e5..a30f8cb 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -17,3 +17,7 @@ tauri = { version = "2", features = [] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" +chrono = { version = "0.4.41", features = ["serde", "now"] } +tokio = { version = "1.45.1", features = ["sync", "macros", "time"] } +rand = { version = "0.9.1", features = ["thread_rng"] } +tauri-plugin-geolocation = "2.2.4" diff --git a/backend/src/game.rs b/backend/src/game.rs new file mode 100644 index 0000000..6c62056 --- /dev/null +++ b/backend/src/game.rs @@ -0,0 +1,447 @@ +use std::{collections::HashMap, time::Duration}; +use tauri::Runtime; +use tokio::sync::RwLock; + +use crate::{ + powerup::{PowerUpType, PowerUpUsage}, + state::{GameEvent, GameState, Location, PlayerId, DT}, +}; + +type EventMessage = (PlayerId, DT, GameEvent); + +/// Struct representing an ongoing game, handles communication with +/// other clients via [Transport] and provides high-level methods for +/// taking actions in the game. +struct Game { + id: PlayerId, + is_host: bool, + state: RwLock>, + transport: T, + interval: Duration, +} + +pub trait Transport { + async fn receive_message(&self) -> Option; + async fn send_event_to(&self, id: PlayerId, event: GameEvent); + async fn send_event_host(&self, event: GameEvent); + async fn send_event_multiple(&self, ids: Vec, event: GameEvent); + async fn send_event_all(&self, event: GameEvent); +} + +pub trait LocationService { + fn get_loc(&self) -> Location; +} + +impl Game { + pub fn new(id: PlayerId, game_state: GameState, transport: T, interval: Duration) -> Self { + Self { + id, + is_host: game_state.host.is_some(), + state: RwLock::new(game_state), + transport, + interval, + } + } + + /// Mark yourself as caught, sends out a message to all other players + async fn mark_caught(&self) { + self.transport + .send_event_all(GameEvent::HiderCaught(self.id)) + .await; + } + + /// Get the active powerup, to be called when the user is in range of the powerup + async fn get_powerup(&self) { + let mut state = self.state.write().await; + if let Some(powerup) = state.public.available_powerup.take() { + state.player.held_powerup = Some(powerup.typ); + drop(state); + self.transport + .send_event_all(GameEvent::PowerUpDespawn) + .await; + } + } + + async fn use_powerup(&self) { + let mut state = self.state.write().await; + if let Some(powerup) = state.player.held_powerup.take() { + drop(state); + match powerup { + PowerUpType::PingSeeker => { + let e = GameEvent::PowerUpActivate(PowerUpUsage::PingSeeker); + self.transport.send_event_host(e).await; + } + PowerUpType::PingAllSeekers => { + let e = GameEvent::PingReq(None); + let state = self.state.read().await; + let seekers = state.public.iter_seekers().collect::>(); + drop(state); + let host_log = GameEvent::PowerUpActivate(PowerUpUsage::PingAllSeekers); + self.transport.send_event_host(host_log).await; + self.transport.send_event_multiple(seekers, e).await; + } + PowerUpType::ForcePingOther => { + let e = GameEvent::PingReq(None); + let mut state = self.state.write().await; + if let Some(target) = state.random_other_hider() { + let host_log = + GameEvent::PowerUpActivate(PowerUpUsage::ForcePingOther(target)); + self.transport.send_event_host(host_log).await; + self.transport.send_event_to(target, e).await; + } + } + } + } + } + + /// Start main loop of the game, this should ideally be put into its own thread via + /// [tokio::spawn]. + async fn main_loop( + &self, + ) -> ( + HashMap>, + Vec<(PlayerId, DT, GameEvent)>, + ) { + let interval = tokio::time::interval(self.interval); + tokio::pin!(interval); + + let mut ended = false; + + while !ended { + tokio::select! { + _ = interval.tick() => { + let mut state = self.state.write().await; + let messages = state.tick(); + drop(state); + for (player, event) in messages { + if let Some(player) = player { + self.transport.send_event_to(player, event).await; + } else { + self.transport.send_event_all(event).await; + } + } + } + + Some((player, time_sent, event)) = self.transport.receive_message() => { + if let GameEvent::GameEnd(dt) = event { + ended = true; + + } else { + let mut state = self.state.write().await; + let new_event = state.consume_event(time_sent, event, player); + drop(state); + if let Some(event) = new_event { + self.transport.send_event_all(event).await; + } + } + } + } + } + + let state = self.state.read().await; + let locations = state.player.locations.clone(); + + if self.is_host { + let player_count = state.public.caught_state.len(); + let mut player_location_history = + HashMap::>::with_capacity(player_count); + player_location_history.insert(self.id, locations); + while player_location_history.len() != player_count { + // TODO: Join with a timeout, etc + if let Some((id, _, GameEvent::PostGameSync(player_locations))) = + self.transport.receive_message().await + { + player_location_history.insert(id, player_locations); + } + } + let history = ( + player_location_history, + state.host.as_ref().unwrap().event_history.clone(), + ); + let ev = GameEvent::HostHistorySync(history.clone()); + self.transport.send_event_all(ev).await; + history + } else { + self.transport + .send_event_host(GameEvent::PostGameSync(locations)) + .await; + loop { + if let Some((_, _, GameEvent::HostHistorySync(history))) = + self.transport.receive_message().await + { + break history; + } + } + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::sync::Arc; + + use crate::state::{GameSettings, HostState, PingStartCondition}; + + use super::*; + use tokio::sync::mpsc::{Receiver, Sender}; + use tokio::sync::Mutex; + use tokio::task::yield_now; + use tokio::test; + + type EventRx = Receiver; + type EventTx = Sender; + + struct MockTransport { + player_id: PlayerId, + rx: Mutex, + txs: HashMap, + } + + impl MockTransport { + fn new(player_id: PlayerId) -> (Self, EventTx) { + let (tx, rx) = tokio::sync::mpsc::channel(5); + let trans = Self { + player_id, + rx: Mutex::new(rx), + txs: HashMap::new(), + }; + (trans, tx) + } + + fn set_txs(&mut self, txs: HashMap) { + self.txs = txs; + } + + fn make_msg(&self, e: GameEvent) -> EventMessage { + (self.player_id, chrono::Utc::now(), e) + } + } + + impl Transport for MockTransport { + async fn receive_message(&self) -> Option { + let mut rx = self.rx.lock().await; + rx.recv().await + } + + async fn send_event_to(&self, id: PlayerId, event: GameEvent) { + if let Some(tx) = self.txs.get(&id) { + if let Err(why) = tx.send(self.make_msg(event)).await { + eprintln!("Error sending msg to {id}: {why}"); + } + } + } + + async fn send_event_host(&self, event: GameEvent) { + // While testing, host is always player 0 + self.send_event_to(0, event).await; + } + + async fn send_event_multiple(&self, ids: Vec, event: GameEvent) { + for id in ids { + self.send_event_to(id, event.clone()).await; + } + } + + async fn send_event_all(&self, event: GameEvent) { + for id in self.txs.keys() { + self.send_event_to(*id, event.clone()).await; + } + } + } + + struct MockLocation; + + impl LocationService for MockLocation { + fn get_loc(&self) -> Location { + Location { + lat: 0.0, + long: 0.0, + heading: None, + } + } + } + + type MockGame = Game; + + struct TestMatch { + games: HashMap>, + } + + impl TestMatch { + /// New test match + /// player_count: number of players + /// num_seekers: number of seekers + /// host_seeker: whether to mark the host as a seeker + pub fn new( + player_count: u32, + num_seekers: u32, + host_seeker: bool, + settings: GameSettings, + ) -> Self { + let caught_state = + HashMap::::from_iter((0..player_count).into_iter().map(|id| { + let should_seeker = + (if id == 0 || host_seeker { id } else { id - 1 }) < num_seekers; + (id, should_seeker && (host_seeker || id != 0)) + })); + + let mut txs = HashMap::::with_capacity(player_count as usize); + let mut games = HashMap::::with_capacity(player_count as usize); + + for id in 0..player_count { + let (transport, tx) = MockTransport::new(id); + let state = GameState::new( + id == 0, + id, + caught_state.clone(), + settings.clone(), + MockLocation, + ); + txs.insert(id, tx); + let game = MockGame::new(id, state, transport, Duration::from_secs(1)); + games.insert(id, game); + } + + for game in games.values_mut() { + game.transport.set_txs(txs.clone()); + } + + Self { + games: games.into_iter().map(|(k, v)| (k, Arc::new(v))).collect(), + } + } + + pub fn start(&self) { + for game in self.games.values() { + let game = game.clone(); + tokio::spawn(async move { game.main_loop().await }); + } + } + + pub fn host(&self) -> Arc { + self.game(0) + } + + pub fn game(&self, id: PlayerId) -> Arc { + self.games.get(&id).unwrap().clone() + } + + pub async fn wait_tick(&self) { + tokio::time::sleep(Duration::from_secs(1)).await; + yield_now().await; + } + + pub async fn wait_assert_seekers_released(&self) { + tokio::time::sleep(Duration::from_secs(1)).await; + yield_now().await; + + self.assert_all_player_states(|state| { + assert!(state.public.seekers_started.is_some()); + }); + } + + /// Assert a condition on the host state + pub async fn assert_host_state(&self, f: F) { + let host = self.host(); + let state = host.state.read().await; + f(state.host.as_ref().unwrap()); + } + + /// Assert a condition on all player states + pub async fn assert_all_player_states)>(&self, f: F) { + for game in self.games.values() { + let state = game.state.read().await; + f(&state); + } + } + } + + const TEST_LOC: Location = Location { + lat: 0.0, + long: 0.0, + heading: None, + }; + + #[test] + async fn test_game() { + let settings = GameSettings { + hiding_time_seconds: 1, + ping_start: PingStartCondition::Players(3), + ping_minutes_interval: 0, + powerup_start: PingStartCondition::Players(3), + powerup_chance: 0, + powerup_minutes_cooldown: 1, + powerup_locations: vec![TEST_LOC.clone()], + }; + + // A test match with 5 players, player 0 (host) is a hider, players 1 and 2 are seekers. + let test_match = TestMatch::new(5, 2, false, settings); + + let correct_caught_state = HashMap::::from_iter([ + (0, false), + (1, true), + (2, true), + (3, false), + (4, false), + ]); + + // Let's make sure our initial `caught_state` is correct + test_match + .assert_all_player_states(|s| assert_eq!(s.public.caught_state, correct_caught_state)) + .await; + + test_match.start(); + + // Wait for seekers to be released, and then assert all player states properly reflect this + test_match.wait_assert_seekers_released().await; + + test_match.wait_tick().await; + + // After a tick, all players should have at least one location in [PlayerState::locations] + test_match + .assert_all_player_states(|s| assert!(!s.player.locations.is_empty())) + .await; + + // Now, let's see if we can mark player 3 as caught + let player_3 = test_match.game(3); + player_3.mark_caught().await; + yield_now().await; + + // All states should be updated to reflect this + test_match + .assert_all_player_states(|s| { + assert_eq!(s.public.caught_state.get(&3).copied(), Some(true)) + }) + .await; + + test_match.wait_tick().await; + + // And now, 3 players have been caught, meaning our [PingStartCondition] has been met, + // let's check the host state to make sure it's starting to perform pings + test_match + .assert_host_state(|h| assert!(h.last_ping.is_some())) + .await; + + test_match.wait_tick().await; + + // Value represents if the [Option] should be [Option::Some] + let correct_pings = HashMap::::from_iter([ + (0, true), + (1, false), + (2, false), + (3, false), + (4, true), + ]); + + // Now let's make sure the hiders are being pinged (3 was just caught, triggering pings. + // Therefore, 3 should not be pinged) + test_match + .assert_all_player_states(|s| { + for (k, v) in s.public.pings.iter() { + assert_eq!(v.is_some(), correct_pings[k]); + } + }) + .await; + } +} diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 4a277ef..1b263f4 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,14 +1,13 @@ -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} +#[allow(unused)] +mod game; +mod powerup; +mod state; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![greet]) + .invoke_handler(tauri::generate_handler![]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/backend/src/powerup.rs b/backend/src/powerup.rs new file mode 100644 index 0000000..ad10168 --- /dev/null +++ b/backend/src/powerup.rs @@ -0,0 +1,65 @@ +use crate::state::{Location, PlayerId}; + +#[derive(Clone, Copy)] +/// Type of powerup +pub enum PowerUpType { + /// Ping a random seeker instead of a hider + PingSeeker, + + /// Pings all seekers locations on the map for hiders + PingAllSeekers, + + /// Ping another random hider instantly + ForcePingOther, +} + +impl PowerUpType { + pub const ALL_TYPES: [Self; 3] = [ + PowerUpType::ForcePingOther, + PowerUpType::PingAllSeekers, + PowerUpType::PingSeeker, + ]; +} + +#[derive(Clone)] +/// Usage of a powerup as reported to the host +pub enum PowerUpUsage { + /// The hider will have their location replaced with a random seeker's + PingSeeker, + /// No additional args + PingAllSeekers, + /// Instantly ping another random hider, contains the unlucky person that is being pinged + ForcePingOther(PlayerId), +} + +#[derive(Clone, PartialEq, Eq)] +/// When a plugin is used +pub enum PowerUpTiming { + /// Used the second it's activated + Instant, + /// Used during the next global ping + NextPing, +} + +impl PowerUpUsage { + pub fn timing(&self) -> PowerUpTiming { + match self { + PowerUpUsage::PingSeeker => PowerUpTiming::NextPing, + PowerUpUsage::ForcePingOther(_) => PowerUpTiming::Instant, + PowerUpUsage::PingAllSeekers => PowerUpTiming::Instant, + } + } +} + +#[derive(Clone)] +/// An on-map powerup that can be picked up by hiders +pub struct PowerUp { + loc: Location, + pub typ: PowerUpType, +} + +impl PowerUp { + pub fn new(loc: Location, typ: PowerUpType) -> Self { + Self { loc, typ } + } +} diff --git a/backend/src/state.rs b/backend/src/state.rs new file mode 100644 index 0000000..331473c --- /dev/null +++ b/backend/src/state.rs @@ -0,0 +1,475 @@ +use std::collections::HashMap; + +use chrono::{DateTime, Utc}; + +use crate::{ + game::LocationService, + powerup::{PowerUp, PowerUpTiming, PowerUpType, PowerUpUsage}, +}; + +/// UTC DateTime; +pub type DT = DateTime; + +/// Type used to uniquely identify players in the game +pub type PlayerId = u32; + +/// Type used for latitude and longitude +pub type LocationComponent = f64; + +#[derive(Debug, Clone)] +/// The starting condition for global pings to begin +pub enum PingStartCondition { + /// Wait For X players to be caught before beginning global pings + Players(u32), + /// Wait for X minutes after game start to begin global pings + Minutes(u32), + /// Don't wait at all, ping location after seekers are released + Instant, +} + +#[derive(Debug, Clone)] +/// Settings for the game, host is the only person able to change these +pub struct GameSettings { + /// The number of seconds to wait before seekers are allowed to go + pub hiding_time_seconds: u64, + /// Condition to wait for global pings to begin + pub ping_start: PingStartCondition, + /// Time between pings after the condition is met (first ping is either after the interval or + /// instantly after the condition is met depending on the condition) + pub ping_minutes_interval: u64, + /// Condition for powerups to start spawning + pub powerup_start: PingStartCondition, + /// Chance (after cooldown) each minute of a powerup spawning, out of 100 + pub powerup_chance: u32, + /// Hard cooldown between powerups spawning + pub powerup_minutes_cooldown: u32, + /// Locations that powerups may spawn at + pub powerup_locations: Vec, +} + +#[derive(Debug, Clone, Copy)] +/// Some location in the world as gotten from the Geolocation API +pub struct Location { + /// Latitude + pub lat: LocationComponent, + /// Longitude + pub long: LocationComponent, + /// The bearing (float normalized from 0 to 1) optional as GPS can't always determine + pub heading: Option, +} + +/// State for each player during the game, the host also has this +pub struct PlayerState { + /// The id of this player in this game + pub id: PlayerId, + /// All previous locations of this player, used in replay screen and when a ping happens + pub locations: Vec, + /// Whether the local player is a seeker + pub seeker: bool, + /// The powerup the player is currently holding + pub held_powerup: Option, +} + +/// Host state that determines when "privileged" events happen +pub struct HostState { + /// The last time a location global ping occurred. If this is [Option::None] it means we're not + /// pinging yet + pub last_ping: Option
, + + /// The last time a power-up has spawned. + pub last_powerup: Option
, + + /// Last time a roll was done for a powerup to spawn, if this is [Option::None] it means we're + /// not spawning powerups yet. + pub last_powerup_proc: Option
, + + /// Set of users that will not be pinged / ping someone else next ping + pub ping_power_usages: HashMap, + + /// A list of all events that happened in this game, and their times + pub event_history: Vec<(PlayerId, DT, GameEvent)>, +} + +impl HostState { + pub fn new() -> Self { + Self { + last_ping: None, + last_powerup: None, + last_powerup_proc: None, + ping_power_usages: HashMap::with_capacity(4), + event_history: Vec::with_capacity(50), + } + } +} + +#[derive(Clone)] +/// An on-map ping of a player +pub struct PlayerPing { + /// Location of the ping + loc: Location, + /// Time the ping happened + time: DT, + /// The player to display who initialized this ping + player: PlayerId, + /// The actual player that initialized this ping + real_player: PlayerId, +} + +impl PlayerPing { + pub fn new(loc: Location, player: PlayerId, real_player: PlayerId) -> Self { + Self { + loc, + player, + real_player, + time: Utc::now(), + } + } +} + +/// State meant to be updated and synced +pub struct PublicState { + /// When the game started + pub game_started: DT, + + /// When seekers were allowed to begin + pub seekers_started: Option
, + + /// Hashmap tracking if a player is a seeker (true) or a hider (false) + pub caught_state: HashMap, + + /// A map of the latest global ping results for each player + pub pings: HashMap>, + + /// Powerup on the map that players can grab. Only one at a time + pub available_powerup: Option, +} + +impl PublicState { + pub fn new(players: HashMap) -> Self { + Self { + game_started: Utc::now(), + seekers_started: None, + pings: HashMap::from_iter(players.keys().map(|id| (*id, None))), + caught_state: players, + available_powerup: None, + } + } + + pub fn iter_seekers(&self) -> impl Iterator + use<'_> { + self.caught_state + .iter() + .filter_map(|(k, v)| if *v { Some(*k) } else { None }) + } + + pub fn iter_hiders(&self) -> impl Iterator + use<'_> { + self.caught_state + .iter() + .filter_map(|(k, v)| if !*v { Some(*k) } else { None }) + } +} + +impl PlayerState { + pub fn new(id: PlayerId, seeker: bool) -> Self { + Self { + id, + locations: Vec::with_capacity(20), + seeker, + held_powerup: None, + } + } + + /// Create a [PlayerPing] with the latest location saved for the player + pub fn create_self_ping(&self) -> Option { + self.create_ping(self.id) + } + + /// Create a [PlayerPing] with the latest location as another player, used when powerups are + /// active + pub fn create_ping(&self, id: PlayerId) -> Option { + self.get_loc() + .map(|loc| PlayerPing::new(loc.clone(), id, self.id)) + } + + /// Push a new player location + pub fn push_loc(&mut self, loc: Location) { + self.locations.push(loc); + } + + /// Get the latest player location + pub fn get_loc(&self) -> Option<&Location> { + self.locations.last() + } +} + +/// Central struct for managing the entire game's state +pub struct GameState { + /// Player state, different for each player + pub player: PlayerState, + /// Public state, kept in sync via events + pub public: PublicState, + /// Host state, only for host, this being [Option::None] implies not being host + pub host: Option, + /// The settings for the current game, read only + pub settings: GameSettings, + loc: L, +} + +#[derive(Clone)] +/// Enum representing all events that can be published, some are host only although +/// implicit trust is given to all players because uh who cares. +pub enum GameEvent { + /// Seekers are now active and can see the map + SeekersReleased(DT), + /// (Host) A request for a given player to ping, optionally includes another player to ping + /// *as* (e.g. when [PowerUpType::PingSeeker] is used) + PingReq(Option), + /// A [PlayerPing] was published to [PublicState] + Ping(PlayerPing), + /// The given hider has been caught + HiderCaught(PlayerId), + /// (Host) The powerup has spawned and is available to grab + PowerUpSpawn(PowerUp), + /// The powerup has despawned (Was grabbed or timed out) + PowerUpDespawn, + /// A player has activated a powerup, some powerups will be published globally and some will be + /// handled only by the host (such as [PowerUpType::PingSeeker]) + PowerUpActivate(PowerUpUsage), + /// (Host) The game has ended (all players were caught or the host cancelled the game) + GameEnd(DT), + /// (Players) After the game has ended, players send this as the final game message + /// to the host with their entire location history + PostGameSync(Vec), + /// (Host) After the game has ended and all players have sent their location histories to the + /// host, the host will send this back to all players. Contains the entire history of the game + /// to be saved and replayed. + HostHistorySync( + ( + HashMap>, + Vec<(PlayerId, DT, GameEvent)>, + ), + ), +} + +impl GameState { + /// Create a new game state (starting a game). Needs the ID of the current player and a HashMap + /// of other player ids to their caught state (whether they start out as seeker). + pub fn new( + host: bool, + id: PlayerId, + players: HashMap, + settings: GameSettings, + loc: L, + ) -> Self { + let is_seeker = players.get(&id).copied().unwrap_or_default(); + Self { + player: PlayerState::new(id, is_seeker), + public: PublicState::new(players), + host: if host { Some(HostState::new()) } else { None }, + settings, + loc, + } + } + + pub fn random_other_hider(&mut self) -> Option { + let hiders = self + .public + .iter_hiders() + .filter(|i| *i != self.player.id) + .collect::>(); + let choice = rand::random_range(0..hiders.len()); + hiders.get(choice).copied() + } + + fn host_tick(&mut self, events: &mut Vec<(Option, GameEvent)>) { + if let Some(host) = self.host.as_mut() { + let now = Utc::now(); + + // Do seekers need to be released? + if self.public.seekers_started.is_none() + && (now - self.public.game_started) + .num_seconds() + .unsigned_abs() + >= self.settings.hiding_time_seconds + { + events.push((None, GameEvent::SeekersReleased(now))); + } + + // Do we need to start doing global pings? + if host.last_ping.is_none() { + let should_start = match self.settings.ping_start { + PingStartCondition::Players(players) => { + self.public.caught_state.values().filter(|v| **v).count() + >= (players as usize) + } + PingStartCondition::Minutes(min) => { + let delta = now - self.public.game_started; + delta.num_minutes() >= (min as i64) + } + PingStartCondition::Instant => true, + }; + if should_start { + host.last_ping = Some(now); + } + } + + // Do we need to do a global ping? + if let Some(last_ping) = host.last_ping.as_mut() { + if (now - *last_ping).num_minutes().unsigned_abs() + >= self.settings.ping_minutes_interval + { + events.extend(self.public.caught_state.iter().filter_map( + |(player, caught)| { + // If caught, don't send a ping request + if *caught { + None + } else { + // If the player is pinging as someone else, do that here. + if let Some(PowerUpUsage::PingSeeker) = + host.ping_power_usages.get(player) + { + host.ping_power_usages.remove(player); + let seekers = self.public.iter_seekers().collect::>(); + let choice = rand::random_range(0..seekers.len()); + let seeker = seekers[choice]; + return Some((Some(seeker), GameEvent::PingReq(Some(*player)))); + } + Some((Some(*player), GameEvent::PingReq(None))) + } + }, + )); + + *last_ping = now; + } + } + + // Do we need to start rolling for powerups? + if host.last_powerup_proc.is_none() { + let should_start = match self.settings.ping_start { + PingStartCondition::Players(players) => { + self.public.caught_state.values().filter(|v| **v).count() + >= (players as usize) + } + PingStartCondition::Minutes(min) => { + let delta = now - self.public.game_started; + delta.num_minutes() >= (min as i64) + } + PingStartCondition::Instant => true, + }; + if should_start { + host.last_powerup_proc = Some(now); + } + } + + // Should we roll for a powerup? + if let Some(last_powerup_proc) = host.last_powerup_proc.as_mut() { + if (now - *last_powerup_proc).num_minutes() >= 1 { + // A minute has passed, roll to see if we should spawn a powerup + let cooldown_over = host.last_powerup.is_none_or(|d| { + (now - d).num_minutes() >= (self.settings.powerup_minutes_cooldown as i64) + }); + let roll = rand::random_ratio(self.settings.powerup_chance, 100); + + if cooldown_over && roll { + // Cooldown is over and we rolled positive, choose and send out a powerup. + let typ_choice = rand::random_range(0..PowerUpType::ALL_TYPES.len()); + let loc_choice = + rand::random_range(0..self.settings.powerup_locations.len()); + let powerup = PowerUp::new( + self.settings.powerup_locations[loc_choice], + PowerUpType::ALL_TYPES[typ_choice], + ); + + events.push((None, GameEvent::PowerUpSpawn(powerup))); + + host.last_powerup = Some(now); + } + + *last_powerup_proc = now; + } + } + } + } + + fn update_loc(&mut self) { + let loc = self.loc.get_loc(); + self.player.push_loc(loc); + } + + /// Run a single game tick, returns any messages that need to be sent + pub fn tick(&mut self) -> Vec<(Option, GameEvent)> { + let mut events = Vec::with_capacity(5); + + self.host_tick(&mut events); + self.update_loc(); + + events + } + + /// Consume an event, optionally returns events to re-broadcast + pub fn consume_event( + &mut self, + time_sent: DT, + event: GameEvent, + player_id: PlayerId, + ) -> Option { + if let Some(host) = self.host.as_mut() { + host.event_history + .push((player_id, time_sent, event.clone())); + } + + match event { + GameEvent::SeekersReleased(time) => { + self.public.seekers_started = Some(time); + } + GameEvent::PingReq(fake_player) => { + let ping = if let Some(fake_player) = fake_player { + self.player.create_ping(fake_player) + } else { + self.player.create_self_ping() + }; + + return ping.map(|p| GameEvent::Ping(p)); + } + GameEvent::Ping(ping) => { + if let Some(current) = self.public.pings.get_mut(&ping.player) { + *current = Some(ping); + } + } + GameEvent::HiderCaught(id) => { + if id == self.player.id { + self.player.seeker = true; + } + if let Some(state) = self.public.caught_state.get_mut(&id) { + *state = true; + } + if self.host.is_some() && self.public.caught_state.iter().all(|(_, k)| *k) { + return Some(GameEvent::GameEnd(Utc::now())); + } + } + GameEvent::GameEnd(_dt) => { + // [Game] handles this case, do nothing if we get here. + } + GameEvent::PowerUpSpawn(power_up) => { + self.public.available_powerup = Some(power_up); + } + GameEvent::PowerUpDespawn => { + self.public.available_powerup = None; + } + GameEvent::PowerUpActivate(usage) => { + if usage.timing() == PowerUpTiming::NextPing { + if let Some(host) = self.host.as_mut() { + if let Some(old_usage) = host.ping_power_usages.get_mut(&player_id) { + *old_usage = usage; + } else { + host.ping_power_usages.insert(player_id, usage); + } + } + } + } + GameEvent::PostGameSync(_) | GameEvent::HostHistorySync(_) => { + // Handled by [Game] + } + } + None + } +} diff --git a/flake.lock b/flake.lock index 5db3bee..e892b0d 100644 --- a/flake.lock +++ b/flake.lock @@ -1,81 +1,83 @@ { - "nodes": { - "flakelight": { - "inputs": { - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1746449252, - "narHash": "sha256-dFGAaYhaaDTfh08wetspJXt6KedYZwsfFpxZ68k9Qds=", - "owner": "nix-community", - "repo": "flakelight", - "rev": "d156dc871b3cdbe0b52169c4bc5171377a9bd6e0", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "flakelight", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1746328495, - "narHash": "sha256-uKCfuDs7ZM3QpCE/jnfubTg459CnKnJG/LwqEVEdEiw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "979daf34c8cacebcd917d540070b52a3c2b9b16e", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1746397377, - "narHash": "sha256-5oLdRa3vWSRbuqPIFFmQBGGUqaYZBxX+GGtN9f/n4lU=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "ed30f8aba41605e3ab46421e3dcb4510ec560ff8", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flakelight": "flakelight", - "nixpkgs": "nixpkgs_2", - "rust-overlay": "rust-overlay" - } - }, - "rust-overlay": { - "inputs": { - "nixpkgs": ["nixpkgs"] - }, - "locked": { - "lastModified": 1746498961, - "narHash": "sha256-rp+oh/N88JKHu7ySPuGiA3lBUVIsrOtHbN2eWJdYCgk=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "24b00064cdd1d7ba25200c4a8565dc455dc732ba", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - } + "nodes": { + "flakelight": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1748868585, + "narHash": "sha256-DrrbahOQAwvNM8l5EuGxxkVS7X5/S59zcG0N9ZWQFhk=", + "owner": "nix-community", + "repo": "flakelight", + "rev": "dfbecd12d99c1bf82906521a6a7d5b75d2aa1ca2", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "flakelight", + "type": "github" + } }, - "root": "root", - "version": 7 + "nixpkgs": { + "locked": { + "lastModified": 1748693115, + "narHash": "sha256-StSrWhklmDuXT93yc3GrTlb0cKSS0agTAxMGjLKAsY8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "910796cabe436259a29a72e8d3f5e180fc6dfacc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1748792178, + "narHash": "sha256-BHmgfHlCJVNisJShVaEmfDIr/Ip58i/4oFGlD1iK6lk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5929de975bcf4c7c8d8b5ca65c8cd9ef9e44523e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flakelight": "flakelight", + "nixpkgs": "nixpkgs_2", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1748832016, + "narHash": "sha256-TQSaFa1wWJr6GOs+K8lecK4AKKr8k6mwxHIPCOmVkgs=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "7ec2ea005b600dac9436a7c5c6b66d960cbfcea2", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 } diff --git a/flake.nix b/flake.nix index d157b87..659a2a4 100644 --- a/flake.nix +++ b/flake.nix @@ -5,83 +5,78 @@ rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; }; - outputs = { flakelight, ... } @ inputs: + outputs = {flakelight, ...} @ inputs: flakelight ./. { inherit inputs; - withOverlays = [ inputs.rust-overlay.overlays.default ]; + withOverlays = [inputs.rust-overlay.overlays.default]; nixpkgs.config = { allowUnfree = true; android_sdk.accept_license = true; }; - formatters = - let - forAllTypes = cmd: types: - builtins.listToAttrs (builtins.map - (t: { - name = "*.${t}"; - value = cmd; - }) - types); - in + formatters = let + forAllTypes = cmd: types: + builtins.listToAttrs (builtins.map + (t: { + name = "*.${t}"; + value = cmd; + }) + types); + in { "*.rs" = "cd backend; cargo fmt"; } - // (forAllTypes "prettier --write ." [ "ts" "tsx" "md" "json" ]); - devShell = pkgs: - let - buildToolsVersion = "34.0.0"; - androidComposition = pkgs.androidenv.composeAndroidPackages { - platformVersions = [ - "34" - "latest" - ]; - systemImageTypes = [ "google_apis_playstore" ]; - buildToolsVersions = [ buildToolsVersion ]; - abiVersions = [ - "armeabi-v7a" - "arm64-v8a" - "x86_64" - ]; - includeNDK = true; - includeExtras = [ - "extras;google;auto" - ]; - }; - in - { - shellHook = - let - ANDROID_HOME = "${androidComposition.androidsdk}/libexec/android-sdk"; - in - '' - export XDG_DATA_DIRS="$GSETTINGS_SCHEMAS_PATH" - export GIO_EXTRA_MODULES="${pkgs.dconf.lib}/lib/gio/modules:${pkgs.glib-networking}/lib/gio/modules" - export ANDROID_HOME=${ANDROID_HOME} - export NDK_HOME="${androidComposition.androidsdk}/libexec/android-sdk/ndk/${builtins.head (pkgs.lib.lists.reverseList (builtins.split "-" "${androidComposition.ndk-bundle}"))}" - export GRADLE_OPTS="-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/${buildToolsVersion}/aapt2" - ''; - packages = with pkgs; [ - at-spi2-atk - atkmm - cairo - gdk-pixbuf - glib - gtk3 - harfbuzz - librsvg - libsoup_3 - pango - webkitgtk_4_1 - openssl - pkg-config - gobject-introspection - nodePackages.prettier - (rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override { targets = [ "aarch64-linux-android" "armv7-linux-androideabi" "i686-linux-android" "x86_64-linux-android" ]; })) - cargo-tauri - nodejs - (android-studio.withSdk androidComposition.androidsdk) + // (forAllTypes "prettier --write ." ["ts" "tsx" "md" "json"]); + devShell = pkgs: let + buildToolsVersion = "34.0.0"; + androidComposition = pkgs.androidenv.composeAndroidPackages { + platformVersions = [ + "34" + "latest" + ]; + systemImageTypes = ["google_apis_playstore"]; + buildToolsVersions = [buildToolsVersion]; + abiVersions = [ + "armeabi-v7a" + "arm64-v8a" + "x86_64" + ]; + includeNDK = true; + includeExtras = [ + "extras;google;auto" ]; }; + in { + shellHook = let + ANDROID_HOME = "${androidComposition.androidsdk}/libexec/android-sdk"; + in '' + export XDG_DATA_DIRS="$GSETTINGS_SCHEMAS_PATH" + export GIO_EXTRA_MODULES="${pkgs.dconf.lib}/lib/gio/modules:${pkgs.glib-networking}/lib/gio/modules" + export ANDROID_HOME=${ANDROID_HOME} + export NDK_HOME="${androidComposition.androidsdk}/libexec/android-sdk/ndk/${builtins.head (pkgs.lib.lists.reverseList (builtins.split "-" "${androidComposition.ndk-bundle}"))}" + export GRADLE_OPTS="-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/${buildToolsVersion}/aapt2" + ''; + packages = with pkgs; [ + at-spi2-atk + atkmm + cairo + gdk-pixbuf + glib + gtk3 + harfbuzz + librsvg + libsoup_3 + pango + webkitgtk_4_1 + openssl + pkg-config + gobject-introspection + nodePackages.prettier + (rust-bin.stable.latest.default.override {targets = ["aarch64-linux-android" "armv7-linux-androideabi" "i686-linux-android" "x86_64-linux-android"];}) + cargo-tauri + nodejs + (android-studio.withSdk androidComposition.androidsdk) + ]; + }; }; }