From 5fa6fea3531fe2b37c44359c5caf33be853e4bd8 Mon Sep 17 00:00:00 2001 From: w1nst0n <55300518+0x192@users.noreply.github.com> Date: Sat, 18 Mar 2023 18:15:31 +0000 Subject: [PATCH] feat: add an recap modal window (#565) A modal window will now appear to give you a recap of the selected packages before applying the changes. A complete overhaul of the way UAD handles package selection was required to implement this feature. --- Cargo.lock | 295 +++++++++++++++-------- Cargo.toml | 1 + resources/assets/icons.ttf | Bin 1312 -> 1568 bytes src/core/utils.rs | 28 --- src/gui/mod.rs | 62 +++-- src/gui/style.rs | 32 ++- src/gui/views/list.rs | 476 ++++++++++++++++++++++++++++--------- src/gui/views/settings.rs | 2 +- src/gui/widgets/mod.rs | 1 + src/gui/widgets/modal.rs | 281 ++++++++++++++++++++++ 10 files changed, 920 insertions(+), 258 deletions(-) create mode 100644 src/gui/widgets/modal.rs diff --git a/Cargo.lock b/Cargo.lock index b97c2ef..f14a328 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5568a4aa5ba8adf5175c5c460b030e27d8893412976cc37bef0e4fbc16cfbba" +checksum = "fe21446ad43aa56417a767f3e2f3d7c4ca522904de1dd640529a76e9c5c3b33c" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -141,24 +141,24 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "bytemuck" -version = "1.12.3" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" +checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe233b960f12f8007e3db2d136e3cb1c291bfd7396e384ee76025fc1a3932b4" +checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" dependencies = [ "proc-macro2", "quote", @@ -186,9 +186,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -470,9 +470,9 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "cxx" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579" +checksum = "bc831ee6a32dd495436e317595e639a587aa9907bef96fe6e6abc290ab6204e9" dependencies = [ "cc", "cxxbridge-flags", @@ -482,9 +482,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70" +checksum = "94331d54f1b1a8895cd81049f7eaaaef9d05a7dcb4d1fd08bf3ff0806246789d" dependencies = [ "cc", "codespan-reporting", @@ -497,15 +497,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c" +checksum = "48dcd35ba14ca9b40d6e4b4b39961f23d835dbb8eed74565ded361d93e1feb8a" [[package]] name = "cxxbridge-macro" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5" +checksum = "81bbeb29798b407ccd82a3324ade1a7286e0d29851475990b612670f6f5124d2" dependencies = [ "proc-macro2", "quote", @@ -615,9 +615,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encase" @@ -708,7 +708,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -802,9 +802,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -817,9 +817,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -827,15 +827,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -845,15 +845,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -862,21 +862,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -1169,7 +1169,7 @@ dependencies = [ [[package]] name = "iced" version = "0.7.0" -source = "git+https://github.com/iced-rs/iced.git#18552f96df5d1fa72c3c87e96a5765f89c340e19" +source = "git+https://github.com/iced-rs/iced.git#98a717383acf71d7939d7cc90d350743487f0380" dependencies = [ "iced_core", "iced_futures", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.7.0" -source = "git+https://github.com/iced-rs/iced.git#18552f96df5d1fa72c3c87e96a5765f89c340e19" +source = "git+https://github.com/iced-rs/iced.git#98a717383acf71d7939d7cc90d350743487f0380" dependencies = [ "bitflags", "instant", @@ -1195,7 +1195,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.5.1" -source = "git+https://github.com/iced-rs/iced.git#18552f96df5d1fa72c3c87e96a5765f89c340e19" +source = "git+https://github.com/iced-rs/iced.git#98a717383acf71d7939d7cc90d350743487f0380" dependencies = [ "futures", "log", @@ -1206,7 +1206,7 @@ dependencies = [ [[package]] name = "iced_glow" version = "0.6.0" -source = "git+https://github.com/iced-rs/iced.git#18552f96df5d1fa72c3c87e96a5765f89c340e19" +source = "git+https://github.com/iced-rs/iced.git#98a717383acf71d7939d7cc90d350743487f0380" dependencies = [ "bytemuck", "euclid", @@ -1221,7 +1221,7 @@ dependencies = [ [[package]] name = "iced_glutin" version = "0.6.0" -source = "git+https://github.com/iced-rs/iced.git#18552f96df5d1fa72c3c87e96a5765f89c340e19" +source = "git+https://github.com/iced-rs/iced.git#98a717383acf71d7939d7cc90d350743487f0380" dependencies = [ "glutin", "iced_graphics", @@ -1233,7 +1233,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.6.0" -source = "git+https://github.com/iced-rs/iced.git#18552f96df5d1fa72c3c87e96a5765f89c340e19" +source = "git+https://github.com/iced-rs/iced.git#98a717383acf71d7939d7cc90d350743487f0380" dependencies = [ "bitflags", "bytemuck", @@ -1248,7 +1248,7 @@ dependencies = [ [[package]] name = "iced_native" version = "0.8.0" -source = "git+https://github.com/iced-rs/iced.git#18552f96df5d1fa72c3c87e96a5765f89c340e19" +source = "git+https://github.com/iced-rs/iced.git#98a717383acf71d7939d7cc90d350743487f0380" dependencies = [ "iced_core", "iced_futures", @@ -1261,7 +1261,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.6.0" -source = "git+https://github.com/iced-rs/iced.git#18552f96df5d1fa72c3c87e96a5765f89c340e19" +source = "git+https://github.com/iced-rs/iced.git#98a717383acf71d7939d7cc90d350743487f0380" dependencies = [ "iced_core", "once_cell", @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.8.0" -source = "git+https://github.com/iced-rs/iced.git#18552f96df5d1fa72c3c87e96a5765f89c340e19" +source = "git+https://github.com/iced-rs/iced.git#98a717383acf71d7939d7cc90d350743487f0380" dependencies = [ "bitflags", "bytemuck", @@ -1291,7 +1291,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.7.0" -source = "git+https://github.com/iced-rs/iced.git#18552f96df5d1fa72c3c87e96a5765f89c340e19" +source = "git+https://github.com/iced-rs/iced.git#98a717383acf71d7939d7cc90d350743487f0380" dependencies = [ "iced_futures", "iced_graphics", @@ -1356,9 +1356,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -1637,14 +1637,23 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.2" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] +[[package]] +name = "nom8" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" +dependencies = [ + "memchr", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -1676,18 +1685,18 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1760,9 +1769,9 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5f3c7ca08b6879e7965fb25e24d1f5eeb32ea73f9ad99b3854778a38c57e93" +checksum = "e25e9fb15717794fae58ab55c26e044103aad13186fbb625893f9a3bbcc24228" dependencies = [ "ttf-parser", ] @@ -1809,7 +1818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.6", + "parking_lot_core 0.9.7", ] [[package]] @@ -1828,15 +1837,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -1925,20 +1934,19 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" dependencies = [ "once_cell", - "thiserror", - "toml", + "toml_edit 0.18.1", ] [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] @@ -2034,9 +2042,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -2215,6 +2223,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + [[package]] name = "servo-fontconfig" version = "0.5.1" @@ -2397,9 +2414,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] @@ -2471,19 +2488,70 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772c1426ab886e7362aedf4abc9c0d1348a979517efedfc25862944d10137af0" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.1", + "toml_edit 0.19.1", +] + +[[package]] +name = "toml_datetime" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +dependencies = [ + "indexmap", + "nom8", + "toml_datetime 0.5.1", +] + +[[package]] +name = "toml_edit" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90a238ee2e6ede22fb95350acc78e21dc40da00bb66c0334bde83de4ed89424e" +dependencies = [ + "indexmap", + "nom8", + "serde", + "serde_spanned", + "toml_datetime 0.6.1", +] + [[package]] name = "ttf-parser" version = "0.18.1" @@ -2510,6 +2578,7 @@ dependencies = [ "fern", "flate2", "iced", + "iced_native", "log", "regex", "retry", @@ -2517,15 +2586,15 @@ dependencies = [ "serde_json", "static_init", "tar", - "toml", + "toml 0.7.1", "ureq", ] [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" @@ -2544,9 +2613,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" @@ -2568,9 +2637,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "ureq" -version = "2.6.1" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733b5ad78377302af52c0dbcb2623d78fe50e4b3bf215948ff29e9ee031d8566" +checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d" dependencies = [ "base64", "flate2", @@ -2621,9 +2690,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2631,9 +2700,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -2646,9 +2715,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if", "js-sys", @@ -2658,9 +2727,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2668,9 +2737,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -2681,9 +2750,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "wasm-timer" @@ -2785,9 +2854,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -3000,6 +3069,30 @@ dependencies = [ "windows_x86_64_msvc 0.42.1", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" @@ -3115,12 +3208,12 @@ dependencies = [ [[package]] name = "x11-dl" -version = "2.20.1" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1536d6965a5d4e573c7ef73a2c15ebcd0b2de3347bdf526c34c297c00ac40f0" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ - "lazy_static", "libc", + "once_cell", "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index ec30636..9db075f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ no-self-update = [] [dependencies] iced = { git = "https://github.com/iced-rs/iced.git" } +iced_native = { git = "https://github.com/iced-rs/iced.git"} serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" static_init = "^1.0" diff --git a/resources/assets/icons.ttf b/resources/assets/icons.ttf index 3e2f82f90bba46185b55903c54a65d0ab74b92fd..6fd82e7143de2f0368c971b024a948bbb0fdb54f 100644 GIT binary patch delta 808 zcmZWn%}*0i5TCbizi79th25=9O6>>s0~5uz+a55qJn2a)>C`Y z3yD#C^2W)l30{racu=|X2bg$*y9cAVPFo@+PV(N&?>Fx^Z|3dp+4iIc5CE`46DW-4 zXRpQN0e%6Q54e_B%Z1wUXNx@Hu!bBLP&%v`Bhco>|h z9stlo%qL4o2%qQ|09HagTq^I>9Tkj#9mVb9W_2}NAa~$15|c>umkae8HnV~F9%5gm zP%gfi>idNlFC@=v)$JW@6pe3hKA`mSD|4JVmR^&wcm^p6L|}l$seDIpELkU()jrvS zi1ppYsz0#|PTpE0vvMD;&?k_9b-Y61ueZLE;>t1^2uG^9iTp@3Y77UxQ=CJTRaGWL zR(bAbq?9tAMQjd%ib7em_iM5RvdVG!Zo)2!j3_)3?1SM*EthK-nF$Vir#XipYoti1 zkt=EmaEZUk+2X>p@Bo;QpwpWpA>GK~42n0a}s z_b7jEq22<VvSwMaSkgt%Jn43D&i}N>-53-=G zAiuZ-=ujYF1rZ7i%uL%S9Zg%m?$fHl#mmmb$(F~##V^MHA1J}f>JO3N=HX!CU@zq07FO3NaB~A0;tMi_ z4P*#oG&7^OgMGB5BnOAQxR|ULE2}8TT%hUfylj4In%<5AiZMcL?A#oTr+~(S#aaE; zG`t)I6$OF9>?}SG_EA!j9GvoE;xeABY@+-?uYsZu2sSkB-x|+v^Ob>{1 { - if add { - selection.enabled += 1 - } else if selection.enabled > 0 { - selection.enabled -= 1 - }; - } - PackageState::Disabled => { - if add { - selection.disabled += 1 - } else if selection.disabled > 0 { - selection.disabled -= 1 - }; - } - PackageState::Uninstalled => { - if add { - selection.uninstalled += 1 - } else if selection.uninstalled > 0 { - selection.uninstalled -= 1 - }; - } - PackageState::All => {} - }; -} - pub fn string_to_theme(theme: String) -> Theme { match theme.as_str() { "Dark" => Theme::Dark, diff --git a/src/gui/mod.rs b/src/gui/mod.rs index d7cb998..91ec30a 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -107,7 +107,12 @@ impl Application for UadGui { None => devices_list.first().map(|x| x.to_owned()), }; self.devices_list = devices_list; - self.update(Message::SettingsAction(SettingsMessage::LoadDeviceSettings)); + + #[allow(unused_must_use)] + { + self.update(Message::SettingsAction(SettingsMessage::LoadDeviceSettings)); + } + self.update(Message::AppsAction(AppsMessage::LoadUadList(true))) } Message::AppsPress => { @@ -149,21 +154,46 @@ impl Application for UadGui { ) .map(Message::AppsAction), Message::SettingsAction(msg) => { - if let SettingsMessage::RestoringDevice(ref output) = msg { - self.nb_running_async_adb_commands -= 1; - self.view = View::List; - self.apps_view.update( - &mut self.settings_view, - &mut self.selected_device.clone().unwrap_or_default(), - &mut self.update_state.uad_list, - AppsMessage::RestoringDevice(output.clone()), - ); + match msg { + SettingsMessage::RestoringDevice(ref output) => { + self.nb_running_async_adb_commands -= 1; + self.view = View::List; - if self.nb_running_async_adb_commands == 0 { - return self.update(Message::RefreshButtonPressed); + #[allow(unused_must_use)] + { + self.apps_view.update( + &mut self.settings_view, + &mut self.selected_device.clone().unwrap_or_default(), + &mut self.update_state.uad_list, + AppsMessage::RestoringDevice(output.clone()), + ); + } + if self.nb_running_async_adb_commands == 0 { + return self.update(Message::RefreshButtonPressed); + } } + SettingsMessage::MultiUserMode(toggled) => { + if toggled { + for user in self.apps_view.phone_packages.clone() { + for (i, _) in + user.iter().enumerate().filter(|&(_, pkg)| pkg.selected) + { + for u in self + .selected_device + .as_ref() + .unwrap() + .user_list + .iter() + .filter(|&u| !u.protected) + { + self.apps_view.phone_packages[u.index][i].selected = true; + } + } + } + } + } + _ => (), } - self.settings_view .update( &self.selected_device.clone().unwrap_or_default(), @@ -221,7 +251,11 @@ impl Application for UadGui { ); info!("{:-^65}", "-"); self.apps_view.loading_state = ListLoadingState::FindingPhones("".to_string()); - self.update(Message::SettingsAction(SettingsMessage::LoadDeviceSettings)); + + #[allow(unused_must_use)] + { + self.update(Message::SettingsAction(SettingsMessage::LoadDeviceSettings)); + } self.update(Message::AppsAction(AppsMessage::LoadPhonePackages(( self.apps_view.uad_lists.clone(), UadListState::Done, diff --git a/src/gui/style.rs b/src/gui/style.rs index 0e76a19..ef06e19 100644 --- a/src/gui/style.rs +++ b/src/gui/style.rs @@ -28,6 +28,8 @@ pub enum Container { Invisible, Frame, BorderedFrame, + Tooltip, + Background, } impl container::StyleSheet for Theme { @@ -49,6 +51,20 @@ impl container::StyleSheet for Theme { border_width: 1.0, border_color: self.palette().normal.error, }, + Container::Tooltip => container::Appearance { + background: Some(Background::Color(self.palette().base.foreground)), + text_color: Some(self.palette().bright.surface), + border_radius: 8.0, + border_width: 1.0, + border_color: self.palette().normal.primary, + }, + + Container::Background => container::Appearance { + background: Some(Background::Color(self.palette().base.background)), + text_color: Some(self.palette().bright.surface), + border_radius: 5.0, + ..container::Appearance::default() + }, } } } @@ -152,7 +168,7 @@ impl button::StyleSheet for Theme { match style { Button::RestorePackage => disabled_appearance(p.normal.primary, Some(p.bright.primary)), Button::UninstallPackage => disabled_appearance(p.bright.error, None), - Button::Primary => disabled_appearance(p.normal.primary, Some(p.bright.primary)), + Button::Primary => disabled_appearance(p.bright.primary, Some(p.bright.primary)), _ => button::Appearance { ..active }, } } @@ -176,21 +192,21 @@ impl scrollable::StyleSheet for Theme { fn active(&self, style: &Self::Style) -> scrollable::Scrollbar { let from_appearance = |c: Color| scrollable::Scrollbar { - background: Some(Background::Color(c)), + background: Some(Background::Color(Color::TRANSPARENT)), border_radius: 5.0, border_width: 0.0, border_color: Color::TRANSPARENT, scroller: scrollable::Scroller { - color: self.palette().base.foreground, + color: c, border_radius: 5.0, - border_width: 0.0, + border_width: 1.0, border_color: Color::TRANSPARENT, }, }; match style { - Scrollable::Description => from_appearance(self.palette().base.foreground), - Scrollable::Packages => from_appearance(self.palette().base.background), + Scrollable::Description => from_appearance(self.palette().normal.surface), + Scrollable::Packages => from_appearance(self.palette().base.foreground), } } @@ -386,6 +402,7 @@ impl pick_list::StyleSheet for Theme { pub enum Text { #[default] Default, + Ok, Danger, Commentary, Color(Color), @@ -403,6 +420,9 @@ impl text::StyleSheet for Theme { fn appearance(&self, style: Self::Style) -> text::Appearance { match style { Text::Default => Default::default(), + Text::Ok => text::Appearance { + color: Some(self.palette().bright.secondary), + }, Text::Danger => text::Appearance { color: Some(self.palette().bright.error), }, diff --git a/src/gui/views/list.rs b/src/gui/views/list.rs index ffa66d3..b8522d8 100644 --- a/src/gui/views/list.rs +++ b/src/gui/views/list.rs @@ -4,26 +4,21 @@ use crate::core::theme::Theme; use crate::core::uad_lists::{ load_debloat_lists, Opposite, Package, PackageState, Removal, UadList, UadListState, }; -use crate::core::utils::{fetch_packages, update_selection_count}; +use crate::core::utils::fetch_packages; use crate::gui::style; +use crate::gui::widgets::navigation_menu::ICONS; use std::collections::HashMap; use std::env; use crate::gui::views::settings::Settings; +use crate::gui::widgets::modal::Modal; use crate::gui::widgets::package_row::{Message as RowMessage, PackageRow}; use iced::widget::{ - button, column, container, pick_list, row, scrollable, text, text_input, Space, + button, column, container, horizontal_space, pick_list, radio, row, scrollable, text, + text_input, tooltip, vertical_rule, Space, }; use iced::{alignment, Alignment, Command, Element, Length, Renderer}; -#[derive(Debug, Default, Clone)] -pub struct Selection { - pub uninstalled: u16, - pub enabled: u16, - pub disabled: u16, - pub selected_packages: Vec, // phone_packages indexes (= what you've selected) -} - #[derive(Debug, Default, Clone)] pub struct PackageInfo { pub i_user: usize, @@ -53,13 +48,14 @@ pub struct List { pub uad_lists: HashMap, pub phone_packages: Vec>, // packages of all users of the phone filtered_packages: Vec, // phone_packages indexes of the selected user (= what you see on screen) - pub selection: Selection, + selected_packages: Vec<(usize, usize)>, // Vec of (user_index, pkg_index) selected_package_state: Option, selected_removal: Option, selected_list: Option, selected_user: Option, pub input_value: String, description: String, + selection_modal: bool, current_package_index: usize, } @@ -79,6 +75,9 @@ pub enum Message { List(usize, RowMessage), ChangePackageState(Result), Nothing, + ModalHide, + ModalUserSelected(User), + ModalValidate, } impl List { @@ -91,6 +90,25 @@ impl List { ) -> Command { let i_user = self.selected_user.unwrap_or_default().index; match message { + Message::ModalHide => { + self.selection_modal = false; + Command::none() + } + Message::ModalValidate => { + let mut commands = vec![]; + self.selected_packages.sort(); + self.selected_packages.dedup(); + for selection in &self.selected_packages { + commands.append(&mut build_action_pkg_commands( + &self.phone_packages, + selected_device, + &settings.device, + *selection, + )); + } + self.selection_modal = false; + Command::batch(commands) + } Message::RestoringDevice(output) => { if let Ok(res) = output { if let CommandType::PackageManager(p) = res { @@ -138,25 +156,14 @@ impl List { Command::none() } Message::ToggleAllSelected(selected) => { + #[allow(unused_must_use)] for i in self.filtered_packages.clone() { - self.phone_packages[i_user][i].selected = selected; - - if !selected { - if self.selection.selected_packages.contains(&i) { - update_selection_count( - &mut self.selection, - self.phone_packages[i_user][i].state, - false, - ); - - self.selection.selected_packages.retain(|&s_i| s_i != i); - } - } else if !self.selection.selected_packages.contains(&i) { - self.selection.selected_packages.push(i); - update_selection_count( - &mut self.selection, - self.phone_packages[i_user][i].state, - true, + if self.phone_packages[i_user][i].selected != selected { + self.update( + settings, + selected_device, + list_update_state, + Message::List(i, RowMessage::ToggleSelection(selected)), ); } } @@ -183,9 +190,12 @@ impl List { Command::none() } Message::List(i_package, row_message) => { - self.phone_packages[i_user][i_package] - .update(row_message.clone()) - .map(move |row_message| Message::List(i_package, row_message)); + #[allow(unused_must_use)] + { + self.phone_packages[i_user][i_package] + .update(row_message.clone()) + .map(move |row_message| Message::List(i_package, row_message)); + } let package = &mut self.phone_packages[i_user][i_package]; @@ -193,30 +203,35 @@ impl List { RowMessage::ToggleSelection(toggle) => { if package.removal == Removal::Unsafe && !settings.general.expert_mode { package.selected = false; + return Command::none(); + } + + if settings.device.multi_user_mode { + for u in selected_device.user_list.iter().filter(|&u| !u.protected) { + self.phone_packages[u.index][i_package].selected = toggle; + if toggle { + self.selected_packages.push((u.index, i_package)); + } + } + if !toggle { + self.selected_packages.retain(|&x| x.1 != i_package); + } } else { package.selected = toggle; - - if package.selected { - self.selection.selected_packages.push(i_package); + if toggle { + self.selected_packages.push((i_user, i_package)); } else { - self.selection - .selected_packages - .retain(|&s_i| s_i != i_package); + self.selected_packages + .retain(|&x| x.1 != i_package || x.0 != i_user); } - update_selection_count( - &mut self.selection, - package.state, - package.selected, - ); } Command::none() } RowMessage::ActionPressed => Command::batch(build_action_pkg_commands( - &self.selected_user.unwrap(), &self.phone_packages, selected_device, &settings.device, - i_package, + (i_user, i_package), )), RowMessage::PackagePressed => { self.description = package.clone().description; @@ -230,26 +245,11 @@ impl List { } } Message::ApplyActionOnSelection => { - let mut commands = vec![]; - for i in &self.selection.selected_packages { - commands.append(&mut build_action_pkg_commands( - &self.selected_user.unwrap(), - &self.phone_packages, - selected_device, - &settings.device, - *i, - )); - } - Command::batch(commands) + self.selection_modal = true; + Command::none() } Message::UserSelected(user) => { - for p in &mut self.phone_packages[i_user] { - p.selected = false; - } self.selected_user = Some(user); - for i_package in &self.selection.selected_packages { - self.phone_packages[user.index][*i_package].selected = true; - } self.filtered_packages = (0..self.phone_packages[user.index].len()).collect(); Self::filter_package_lists(self); Command::none() @@ -258,15 +258,22 @@ impl List { if let Ok(CommandType::PackageManager(p)) = res { let package = &mut self.phone_packages[p.i_user][p.index]; package.state = package.state.opposite(settings.device.disable_mode); - - update_selection_count(&mut self.selection, package.state, false); - self.selection - .selected_packages - .retain(|&s_i| s_i != p.index); + package.selected = false; + self.selected_packages + .retain(|&x| x.1 != p.index && x.0 != p.i_user); Self::filter_package_lists(self); } Command::none() } + Message::ModalUserSelected(user) => { + self.selected_user = Some(user); + self.update( + settings, + selected_device, + list_update_state, + Message::UserSelected(user), + ) + } Message::Nothing => Command::none(), } } @@ -305,8 +312,6 @@ impl List { ) .padding(5); - // let package_amount = text(format!("{} packages found", packages.len())); - let user_picklist = pick_list( selected_device.user_list.clone(), self.selected_user, @@ -358,8 +363,6 @@ impl List { .height(Length::FillPortion(6)) .style(style::Scrollable::Packages); - // let mut packages_v: Vec<&str> = self.packages.lines().collect(); - let description_scroll = scrollable(text(&self.description)).style(style::Scrollable::Description); @@ -368,31 +371,21 @@ impl List { .width(Length::Fill) .style(style::Container::Frame); - let restore_action = match settings.device.disable_mode { - true => "Enable/Restore", - false => "Restore", + let review_selection = if !self.selected_packages.is_empty() { + button(text(format!( + "Review selection ({})", + self.selected_packages.len() + ))) + .on_press(Message::ApplyActionOnSelection) + .padding(5) + .style(style::Button::Primary) + } else { + button(text(format!( + "Review selection ({})", + self.selected_packages.len() + ))) + .padding(5) }; - let remove_action = match settings.device.disable_mode { - true => "Disable", - false => "Uninstall", - }; - - let apply_restore_selection = button(text(format!( - "{} selection ({})", - restore_action, - self.selection.uninstalled + self.selection.disabled - ))) - .on_press(Message::ApplyActionOnSelection) - .padding(5) - .style(style::Button::Primary); - - let apply_remove_selection = button(text(format!( - "{} selection ({})", - remove_action, self.selection.enabled - ))) - .on_press(Message::ApplyActionOnSelection) - .padding(5) - .style(style::Button::Primary); let select_all_btn = button("Select all") .padding(5) @@ -408,8 +401,7 @@ impl List { select_all_btn, unselect_all_btn, Space::new(Length::Fill, Length::Shrink), - apply_restore_selection, - apply_remove_selection, + review_selection, ] .width(Length::Fill) .spacing(10) @@ -450,11 +442,232 @@ impl List { .spacing(10) .align_items(Alignment::Center) }; - container(content).height(Length::Fill).padding(10).into() + if self.selection_modal { + Modal::new( + content.padding(10), + self.apply_selection_modal( + selected_device, + settings, + &self.phone_packages[self.selected_user.unwrap().index], + ), + ) + .on_blur(Message::ModalHide) + .into() + } else { + container(content).height(Length::Fill).padding(10).into() + } } } } + fn apply_selection_modal( + &self, + device: &Phone, + settings: &Settings, + packages: &[PackageRow], + ) -> Element> { + let mut h_recap: HashMap = HashMap::new(); + for p in packages.iter().filter(|p| p.selected) { + if p.state != PackageState::Uninstalled { + h_recap.entry(p.removal).or_insert((0, 0)).0 += 1; + } else { + h_recap.entry(p.removal).or_insert((0, 0)).1 += 1; + } + } + + let radio_btn_users = device.user_list.iter().filter(|&u| !u.protected).fold( + row![].spacing(10), + |row, user| { + row.push( + radio( + format!("{}", user.clone()), + *user, + self.selected_user, + Message::ModalUserSelected, + ) + .size(23), + ) + }, + ); + + let title_ctn = + container(row![text("Review your selection").size(25)].align_items(Alignment::Center)) + .width(Length::Fill) + .style(style::Container::Frame) + .padding([10, 0, 10, 0]) + .center_y() + .center_x(); + + let users_ctn = container(radio_btn_users) + .padding(10) + .center_x() + .style(style::Container::Frame); + + let explaination_ctn = container( + row![ + text("The action for the selected user will be applied to all other users") + .style(style::Text::Danger), + tooltip( + text("\u{EA0C}") + .font(ICONS) + .width(Length::Units(17)) + .horizontal_alignment(alignment::Horizontal::Center) + .style(style::Text::Commentary) + .size(17), + "Let's say you choose user 0. If a selected package on user 0\n\ + is set to be uninstalled and if this same package is disabled on user 10,\n\ + then the package on both users will be uninstalled.", + tooltip::Position::Top, + ) + .gap(20) + .padding(10) + .size(17) + .style(style::Container::Tooltip) + ] + .spacing(10), + ) + .center_x() + .padding(10) + .style(style::Container::BorderedFrame); + + let modal_btn_row = row![ + button(text("Cancel")).on_press(Message::ModalHide), + horizontal_space(Length::Fill), + button(text("Apply")).on_press(Message::ModalValidate), + ] + .padding([0, 15, 10, 10]); + + let recap_view = Removal::ALL + .iter() + .filter(|&&r| r != Removal::All) + .fold(column![].spacing(6).width(Length::Fill), |col, r| { + col.push(recap(settings, &mut h_recap, *r)) + }); + + let selected_pkgs_ctn = container( + container( + scrollable( + container( + if !self + .selected_packages + .iter() + .any(|s| s.0 == self.selected_user.unwrap().index) + { + column![text("No packages selected for this user")] + .align_items(Alignment::Center) + .width(Length::Fill) + } else { + self.selected_packages + .iter() + .filter(|s| s.0 == self.selected_user.unwrap().index) + .fold( + column![].spacing(6).width(Length::Fill), + |col, selection| { + col.push( + row![ + row![text( + self.phone_packages[selection.0][selection.1] + .removal + )] + .width(Length::Units(100)), + row![text( + self.phone_packages[selection.0][selection.1] + .uad_list + )] + .width(Length::Units(60)), + row![text( + self.phone_packages[selection.0][selection.1] + .name + .clone() + ),], + horizontal_space(Length::Fill), + row![match self.phone_packages[selection.0] + [selection.1] + .state + { + PackageState::Enabled => + if settings.device.disable_mode { + text("Disable") + .style(style::Text::Danger) + } else { + text("Uninstall") + .style(style::Text::Danger) + }, + PackageState::Disabled => + text("Enable").style(style::Text::Ok), + PackageState::Uninstalled => + text("Restore").style(style::Text::Ok), + _ => text("Impossible") + .style(style::Text::Danger), + },] + .width(Length::Units(60)), + ] + .width(Length::Fill) + .spacing(20), + ) + }, + ) + }, + ) + .padding(10) + .width(Length::Fill), + ) + .style(style::Scrollable::Description), + ) + .width(Length::Fill) + .style(style::Container::Frame), + ) + .width(Length::Fill) + .max_height(150) + .padding([0, 10, 0, 10]); + + container( + if device + .user_list + .iter() + .filter(|&u| !u.protected) + .collect::>() + .len() + > 1 + && settings.device.multi_user_mode + { + column![ + title_ctn, + users_ctn, + row![explaination_ctn].padding([0, 10, 0, 10]), + container(recap_view).padding(10), + selected_pkgs_ctn, + modal_btn_row, + ] + .spacing(10) + .align_items(Alignment::Center) + } else if !settings.device.multi_user_mode { + column![ + title_ctn, + users_ctn, + container(recap_view).padding(10), + selected_pkgs_ctn, + modal_btn_row, + ] + .spacing(10) + .align_items(Alignment::Center) + } else { + column![ + title_ctn, + container(recap_view).padding(10), + selected_pkgs_ctn, + modal_btn_row, + ] + .spacing(10) + .align_items(Alignment::Center) + }, + ) + .width(Length::Units(800)) + .height(Length::Shrink) + .max_height(700) + .style(style::Container::Background) + .into() + } fn filter_package_lists(&mut self) { let list_filter: UadList = self.selected_list.unwrap(); let package_filter: PackageState = self.selected_package_state.unwrap(); @@ -545,30 +758,33 @@ fn waiting_view<'a>( } fn build_action_pkg_commands( - selected_user: &User, packages: &[Vec], device: &Phone, settings: &DeviceSettings, - p_index: usize, + selection: (usize, usize), ) -> Vec> { - let pkg = &packages[selected_user.index][p_index]; + let pkg = &packages[selection.0][selection.1]; let wanted_state = pkg.state.opposite(settings.disable_mode); let mut commands = vec![]; for u in device.user_list.iter().filter(|&&u| { - !u.protected - && (u != *selected_user || settings.multi_user_mode) - && packages[u.index][p_index].state != wanted_state + !u.protected && (packages[u.index][selection.1].selected || settings.multi_user_mode) }) { - let u_pkg = packages[u.index][p_index].clone(); - let actions = apply_pkg_state_commands(u_pkg.into(), &wanted_state, u, device); + let u_pkg = packages[u.index][selection.1].clone(); + let actions = if settings.multi_user_mode { + apply_pkg_state_commands(u_pkg.into(), &wanted_state, u, device) + } else { + let wanted_state = &u_pkg.state.opposite(settings.disable_mode); + apply_pkg_state_commands(u_pkg.into(), wanted_state, u, device) + }; for (j, action) in actions.into_iter().enumerate() { let p_info = PackageInfo { i_user: u.index, - index: p_index, + index: selection.1, removal: pkg.removal.to_string(), }; - // Only the first command can change the package state + // In the end there is only one package state change + // even if we run multiple adb commands commands.push(Command::perform( perform_adb_commands(action, CommandType::PackageManager(p_info)), if j == 0 { @@ -581,3 +797,47 @@ fn build_action_pkg_commands( } commands } + +fn recap<'a>( + settings: &Settings, + recap: &mut HashMap, + removal: Removal, +) -> Element<'a, Message, Renderer> { + container( + row![ + text(removal).size(25).width(Length::FillPortion(1)), + vertical_rule(5), + row![ + if settings.device.disable_mode { + text("Disable").style(style::Text::Danger) + } else { + text("Uninstall").style(style::Text::Danger) + }, + horizontal_space(Length::Fill), + text(recap.entry(removal).or_insert((0, 0)).0.to_string()) + .style(style::Text::Danger) + ] + .width(Length::FillPortion(1)), + vertical_rule(5), + row![ + if settings.device.disable_mode { + text("Enable").style(style::Text::Ok) + } else { + text("Restore").style(style::Text::Ok) + }, + horizontal_space(Length::Fill), + text(recap.entry(removal).or_insert((0, 0)).1.to_string()).style(style::Text::Ok) + ] + .width(Length::FillPortion(1)) + ] + .spacing(20) + .padding([0, 10, 0, 0]) + .width(Length::Fill) + .align_items(Alignment::Center), + ) + .padding(10) + .width(Length::Fill) + .height(Length::Units(45)) + .style(style::Container::Frame) + .into() +} diff --git a/src/gui/views/settings.rs b/src/gui/views/settings.rs index 85c5a64..296595f 100644 --- a/src/gui/views/settings.rs +++ b/src/gui/views/settings.rs @@ -252,7 +252,7 @@ impl Settings { ]; let multi_user_mode_checkbox = checkbox( - "Affect all the users of the phone (not only the selected user)", + "Affect all the users of the device (not only the selected user)", self.device.multi_user_mode, Message::MultiUserMode, ) diff --git a/src/gui/widgets/mod.rs b/src/gui/widgets/mod.rs index e595500..5796d6f 100644 --- a/src/gui/widgets/mod.rs +++ b/src/gui/widgets/mod.rs @@ -1,2 +1,3 @@ +pub mod modal; pub mod navigation_menu; pub mod package_row; diff --git a/src/gui/widgets/modal.rs b/src/gui/widgets/modal.rs new file mode 100644 index 0000000..7d46cb5 --- /dev/null +++ b/src/gui/widgets/modal.rs @@ -0,0 +1,281 @@ +use iced_native::alignment::Alignment; +use iced_native::widget::{self, Tree}; +use iced_native::{ + event, layout, mouse, overlay, renderer, Clipboard, Color, Element, Event, Layout, Length, + Point, Rectangle, Shell, Size, Widget, +}; + +/// A widget that centers a modal element over some base element +pub struct Modal<'a, Message, Renderer> { + base: Element<'a, Message, Renderer>, + modal: Element<'a, Message, Renderer>, + on_blur: Option, +} + +impl<'a, Message, Renderer> Modal<'a, Message, Renderer> { + /// Returns a new [`Modal`] + pub fn new( + base: impl Into>, + modal: impl Into>, + ) -> Self { + Self { + base: base.into(), + modal: modal.into(), + on_blur: None, + } + } + + /// Sets the message that will be produces when the background + /// of the [`Modal`] is pressed + pub fn on_blur(self, on_blur: Message) -> Self { + Self { + on_blur: Some(on_blur), + ..self + } + } +} + +impl<'a, Message, Renderer> Widget for Modal<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, + Message: Clone, +{ + fn children(&self) -> Vec { + vec![Tree::new(&self.base), Tree::new(&self.modal)] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(&[&self.base, &self.modal]); + } + + fn width(&self) -> Length { + self.base.as_widget().width() + } + + fn height(&self) -> Length { + self.base.as_widget().height() + } + + fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + self.base.as_widget().layout(renderer, limits) + } + + fn on_event( + &mut self, + state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.base.as_widget_mut().on_event( + &mut state.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + state: &Tree, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.base.as_widget().draw( + &state.children[0], + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + state: &'b mut Tree, + layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option> { + Some(overlay::Element::new( + layout.position(), + Box::new(Overlay { + content: &mut self.modal, + tree: &mut state.children[1], + size: layout.bounds().size(), + on_blur: self.on_blur.clone(), + }), + )) + } + + fn mouse_interaction( + &self, + state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.base.as_widget().mouse_interaction( + &state.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn operate( + &self, + state: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + self.base + .as_widget() + .operate(&mut state.children[0], layout, renderer, operation); + } +} + +struct Overlay<'a, 'b, Message, Renderer> { + content: &'b mut Element<'a, Message, Renderer>, + tree: &'b mut Tree, + size: Size, + on_blur: Option, +} + +impl<'a, 'b, Message, Renderer> overlay::Overlay + for Overlay<'a, 'b, Message, Renderer> +where + Renderer: iced_native::Renderer, + Message: Clone, +{ + fn layout(&self, renderer: &Renderer, _bounds: Size, position: Point) -> layout::Node { + let limits = layout::Limits::new(Size::ZERO, self.size) + .width(Length::Fill) + .height(Length::Fill); + + let mut child = self.content.as_widget().layout(renderer, &limits); + child.align(Alignment::Center, Alignment::Center, limits.max()); + + let mut node = layout::Node::with_children(self.size, vec![child]); + node.move_to(position); + + node + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let content_bounds = layout.children().next().unwrap().bounds(); + + if let Some(message) = self.on_blur.as_ref() { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = &event { + if !content_bounds.contains(cursor_position) { + shell.publish(message.clone()); + return event::Status::Captured; + } + } + } + + self.content.as_widget_mut().on_event( + self.tree, + event, + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + renderer.fill_quad( + renderer::Quad { + bounds: layout.bounds(), + border_radius: renderer::BorderRadius::from(0.0), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Color { + a: 0.80, + ..Color::BLACK + }, + ); + + self.content.as_widget().draw( + self.tree, + renderer, + theme, + style, + layout.children().next().unwrap(), + cursor_position, + &layout.bounds(), + ); + } + + fn operate( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + self.content.as_widget().operate( + self.tree, + layout.children().next().unwrap(), + renderer, + operation, + ); + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + self.tree, + layout.children().next().unwrap(), + cursor_position, + viewport, + renderer, + ) + } +} + +impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> +where + Renderer: 'a + iced_native::Renderer, + Message: 'a + Clone, +{ + fn from(modal: Modal<'a, Message, Renderer>) -> Self { + Element::new(modal) + } +}