From f201814d544dec9c9e42cc6d16f3a26a6d21fc88 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Fri, 31 Jul 2026 09:52:24 +0200 Subject: [PATCH] Replace Slint UI with UIKit and add milestones Fixes #2.\nFixes #9. --- AGENTS.md | 6 + Cargo.lock | 4874 +---------------- Cargo.toml | 1 - README.md | 37 +- TESTING.md | 187 + crates/app/Cargo.toml | 13 +- crates/app/build.rs | 3 - crates/app/src/api.rs | 178 +- crates/app/src/domain.rs | 47 +- crates/app/src/lib.rs | 355 ++ crates/app/src/main.rs | 1232 ----- crates/app/src/markdown.rs | 312 -- crates/app/src/presentation.rs | 800 +++ crates/app/src/storage.rs | 21 - crates/app/src/ui.rs | 1 - crates/app/src/view.rs | 627 --- crates/app/ui/app.slint | 614 --- crates/app/ui/components.slint | 318 -- crates/app/ui/types.slint | 24 - ios/Generated/gotcha_core.modulemap | 7 + ios/Generated/gotcha_core.swift | 2858 ++++++++++ ios/Generated/gotcha_coreFFI.h | 786 +++ ios/Gotcha-Bridging-Header.h | 1 + ios/Gotcha.xcodeproj/project.pbxproj | 78 +- ios/Sources/AppContext.swift | 134 + ios/Sources/AppDelegate.swift | 19 + ios/Sources/ContentScreens.swift | 654 +++ ios/Sources/DetailScreens.swift | 453 ++ ios/Sources/ListScreens.swift | 454 ++ ios/Sources/SettingsViewController.swift | 59 + ios/Sources/Support.swift | 140 + ...s_with_cargo.bash => build_rust_core.bash} | 23 +- ios/project.yml | 17 +- 33 files changed, 7455 insertions(+), 7878 deletions(-) create mode 100644 TESTING.md delete mode 100644 crates/app/build.rs create mode 100644 crates/app/src/lib.rs delete mode 100644 crates/app/src/main.rs delete mode 100644 crates/app/src/markdown.rs create mode 100644 crates/app/src/presentation.rs delete mode 100644 crates/app/src/ui.rs delete mode 100644 crates/app/src/view.rs delete mode 100644 crates/app/ui/app.slint delete mode 100644 crates/app/ui/components.slint delete mode 100644 crates/app/ui/types.slint create mode 100644 ios/Generated/gotcha_core.modulemap create mode 100644 ios/Generated/gotcha_core.swift create mode 100644 ios/Generated/gotcha_coreFFI.h create mode 100644 ios/Gotcha-Bridging-Header.h create mode 100644 ios/Sources/AppContext.swift create mode 100644 ios/Sources/AppDelegate.swift create mode 100644 ios/Sources/ContentScreens.swift create mode 100644 ios/Sources/DetailScreens.swift create mode 100644 ios/Sources/ListScreens.swift create mode 100644 ios/Sources/SettingsViewController.swift create mode 100644 ios/Sources/Support.swift rename ios/{build_for_ios_with_cargo.bash => build_rust_core.bash} (52%) diff --git a/AGENTS.md b/AGENTS.md index 2b2ddc2..6536343 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,11 @@ # Repository instructions +## Gitea issue workflow + +Use the `tea` command-line tool as the preferred way to list, inspect, create, +and update Gitea issues. Run it from the repository root so it can use the +configured remote, for example `tea issues` or `tea issues 1`. + ## Required pre-commit gates Run every gate below from the repository root before committing. Every command diff --git a/Cargo.lock b/Cargo.lock index 5865228..256839e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,125 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "ab_glyph" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.4", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "aligned" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" -dependencies = [ - "as-slice", -] - -[[package]] -name = "aligned-vec" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" -dependencies = [ - "equator", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android-activity" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" -dependencies = [ - "android-properties", - "bitflags 2.13.1", - "cc", - "jni", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "num_enum", - "thiserror 2.0.19", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "annotate-snippets" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" -dependencies = [ - "anstyle", - "memchr", - "unicode-width", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - [[package]] name = "anyhow" version = "1.0.104" @@ -128,178 +9,69 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] -name = "arbitrary" -version = "1.4.2" +name = "askama" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +checksum = "f1bf825125edd887a019d0a3a837dcc5499a68b0d034cc3eb594070c3e18addc" +dependencies = [ + "askama_macros", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] [[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" +name = "askama_derive" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +checksum = "e1c7065972a130eafa84215f21352ae15b4a7393da48c1f5e103904490736738" dependencies = [ + "askama_parser", + "basic-toml", + "glob", + "memchr", "proc-macro2", "quote", + "rustc-hash", + "serde", + "serde_derive", "syn 2.0.119", ] [[package]] -name = "arrayref" -version = "0.3.9" +name = "askama_macros" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" - -[[package]] -name = "as-slice" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +checksum = "0e23b1d2c4bd39a41971f6124cef4cc6fd0540913ecb90919b69ab3bbe44ae1a" dependencies = [ - "stable_deref_trait", + "askama_derive", ] [[package]] -name = "async-broadcast" -version = "0.7.2" +name = "askama_parser" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +checksum = "7db09fde9143e7ac4513358fb32ee32847125b63b18ea715afd487956da715da" dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", + "rustc-hash", + "serde", + "serde_derive", + "unicode-ident", + "winnow", ] [[package]] -name = "async-channel" -version = "2.5.0" +name = "async-compat" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix 1.1.4", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix 1.1.4", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "async-signal" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", "futures-core", "futures-io", - "rustix 1.1.4", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", + "once_cell", + "pin-project-lite", + "tokio", ] [[package]] @@ -308,67 +80,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "auto_enums" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3091d68264354f211516b91dce6f71046e444fab1867716035f736667243affb" -dependencies = [ - "derive_utils", - "proc-macro2", - "quote", - "syn 3.0.3", -] - [[package]] name = "autocfg" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" -[[package]] -name = "av-scenechange" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" -dependencies = [ - "aligned", - "anyhow", - "arg_enum_proc_macro", - "arrayvec", - "log", - "num-rational", - "num-traits", - "pastey", - "rayon", - "thiserror 2.0.19", - "v_frame", - "y4m", -] - -[[package]] -name = "av1-grain" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom 8.0.0", - "num-rational", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" -dependencies = [ - "arrayvec", -] - [[package]] name = "aws-lc-rs" version = "1.17.3" @@ -399,111 +116,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "bincode" -version = "2.0.1" +name = "basic-toml" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ "serde", - "unty", ] -[[package]] -name = "bindgen" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags 2.13.1", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 2.1.3", - "shlex 1.3.0", - "syn 2.0.119", -] - -[[package]] -name = "bit_field" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" -dependencies = [ - "serde_core", -] - -[[package]] -name = "bitstream-io" -version = "4.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" -dependencies = [ - "no_std_io2", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2 0.6.4", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "borsh" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" -dependencies = [ - "bytes", - "cfg_aliases", -] - -[[package]] -name = "built" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" [[package]] name = "bumpalo" @@ -511,38 +136,6 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "by_address" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" - -[[package]] -name = "bytemuck" -version = "1.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - [[package]] name = "bytes" version = "1.12.1" @@ -550,54 +143,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] -name = "calloop" -version = "0.13.0" +name = "camino" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" dependencies = [ - "bitflags 2.13.1", - "log", - "polling", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", + "serde_core", ] [[package]] -name = "calloop" -version = "0.14.4" +name = "cargo-platform" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" dependencies = [ - "bitflags 2.13.1", - "polling", - "rustix 1.1.4", - "slab", - "tracing", + "serde", + "serde_core", ] [[package]] -name = "calloop-wayland-source" -version = "0.3.0" +name = "cargo_metadata" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ - "calloop 0.13.0", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" -dependencies = [ - "calloop 0.14.4", - "rustix 1.1.4", - "wayland-backend", - "wayland-client", + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", ] [[package]] @@ -609,16 +184,7 @@ dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex 2.0.1", -] - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom 7.1.3", + "shlex", ] [[package]] @@ -633,15 +199,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" -[[package]] -name = "cgl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" -dependencies = [ - "libc", -] - [[package]] name = "chacha20" version = "0.10.1" @@ -650,49 +207,7 @@ checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures", - "rand_core 0.10.1", -] - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "clang-sys" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clipboard-win" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" -dependencies = [ - "error-code", -] - -[[package]] -name = "clru" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" -dependencies = [ - "hashbrown 0.16.1", + "rand_core", ] [[package]] @@ -704,12 +219,6 @@ dependencies = [ "cc", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "combine" version = "4.6.7" @@ -720,69 +229,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-field-offset" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9713489fbc45a2d113f58d17448c73115d533ed27fd2f2d0e5e5c69663eaf19b" -dependencies = [ - "const-field-offset-macro", - "field-offset", -] - -[[package]] -name = "const-field-offset-macro" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b5dc851b7ea80cc4f69aa28632385ffd3e0f88014d53db3f2e5a781896ca783" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "copypasta" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e6811e17f81fe246ef2bc553f76b6ee6ab41a694845df1d37e52a92b7bbd38a" -dependencies = [ - "clipboard-win", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "smithay-clipboard", - "x11-clipboard", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -799,45 +245,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - -[[package]] -name = "core_maths" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" -dependencies = [ - "libm", -] - -[[package]] -name = "countme" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" - [[package]] name = "cpufeatures" version = "0.3.0" @@ -847,132 +254,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crossbeam-channel" -version = "0.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "ctor" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83cf0d42651b16c6dfe68685716d18480d18a9c39c62d76e8cf3eb6ed5d8bcbf" -dependencies = [ - "dtor", -] - -[[package]] -name = "cursor-icon" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" - -[[package]] -name = "data-url" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.119", - "unicode-xid", -] - -[[package]] -name = "derive_utils" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc05a5d33db20c784f873e84934ad94bb209a090987ac5f62fede2c178234f23" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", -] - [[package]] name = "displaydoc" version = "0.2.7" @@ -984,132 +265,12 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "dlib" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" -dependencies = [ - "libloading", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" - -[[package]] -name = "drm" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" -dependencies = [ - "bitflags 2.13.1", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "libc", - "rustix 0.38.44", -] - -[[package]] -name = "drm-ffi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a91c9b32ac4e8105dec255e849e0d66e27d7c34d184364fb93e469db08f690" -dependencies = [ - "drm-sys", - "rustix 1.1.4", -] - -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" - -[[package]] -name = "drm-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8e1361066d91f5ffccff060a3c3be9c3ecde15be2959c1937595f7a82a9f8" -dependencies = [ - "libc", - "linux-raw-sys 0.9.4", -] - -[[package]] -name = "dtor" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf234dd1594d6dd434a8fb8cada51ddbbc593e40e4a01556a0b31c62da2775b" - [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "either" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" - -[[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "equator" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" -dependencies = [ - "equator-macro", -] - -[[package]] -name = "equator-macro" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1126,217 +287,18 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "error-code" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" - -[[package]] -name = "euclid" -version = "0.22.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" -dependencies = [ - "num-traits", -] - -[[package]] -name = "event-listener" -version = "5.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" -dependencies = [ - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "exr" -version = "1.74.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" -dependencies = [ - "bit_field", - "half", - "lebe", - "miniz_oxide", - "num-complex", - "pulp", - "rayon-core", - "smallvec", - "zune-inflate", -] - [[package]] name = "fastrand" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" -[[package]] -name = "fax" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "filetime" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" -dependencies = [ - "cfg-if", - "libc", -] - [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "fixed_decimal" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c3c892f121fff406e5dd6b28c1b30096b95111c30701a899d4f2b18da6d1bd" -dependencies = [ - "displaydoc", - "smallvec", - "writeable", -] - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "font-types" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "font-types" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad67eced03f5504d9cbd3a879b5958b5c54d4e5fd794361c6eb21b05fb703411" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "fontdb" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" -dependencies = [ - "log", - "slotmap", - "tinyvec", - "ttf-parser", -] - -[[package]] -name = "fontique" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "274fa4f0f0a926ae182c7c076c078cce8a38471d15e61a102a02cac984be9813" -dependencies = [ - "hashbrown 0.17.1", - "linebender_resource_handle", - "memmap2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-text", - "objc2-foundation 0.3.2", - "parlance", - "read-fonts 0.39.2", - "roxmltree", - "smallvec", - "windows", - "windows-core", - "yeslogic-fontconfig-sys", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1346,27 +308,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a" +dependencies = [ + "autocfg", +] + [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "futures" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.33" @@ -1374,7 +330,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -1383,36 +338,12 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" -[[package]] -name = "futures-executor" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.33" @@ -1442,7 +373,6 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1453,47 +383,6 @@ dependencies = [ "slab", ] -[[package]] -name = "gbm" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a" -dependencies = [ - "bitflags 2.13.1", - "drm", - "drm-fourcc", - "gbm-sys", - "libc", -] - -[[package]] -name = "gbm-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f" -dependencies = [ - "libc", -] - -[[package]] -name = "gethostname" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" -dependencies = [ - "rustix 1.1.4", - "windows-link", -] - -[[package]] -name = "getopts" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" -dependencies = [ - "unicode-width", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -1507,18 +396,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - [[package]] name = "getrandom" version = "0.4.3" @@ -1528,21 +405,11 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", + "r-efi", + "rand_core", "wasm-bindgen", ] -[[package]] -name = "gif" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gitea-client" version = "1.25.2" @@ -1558,17 +425,6 @@ dependencies = [ "url", ] -[[package]] -name = "gl_generator" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] - [[package]] name = "glob" version = "0.3.4" @@ -1576,81 +432,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] -name = "glow" -version = "0.17.0" +name = "goblin" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "glutin" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" -dependencies = [ - "bitflags 2.13.1", - "cfg_aliases", - "cgl", - "dispatch2", - "glutin_egl_sys", - "glutin_glx_sys", - "glutin_wgl_sys", - "libloading", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "once_cell", - "raw-window-handle", - "wayland-sys", - "windows-sys 0.52.0", - "x11-dl", -] - -[[package]] -name = "glutin-winit" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" -dependencies = [ - "cfg_aliases", - "glutin", - "raw-window-handle", - "winit", -] - -[[package]] -name = "glutin_egl_sys" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" -dependencies = [ - "gl_generator", - "windows-sys 0.52.0", -] - -[[package]] -name = "glutin_glx_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" -dependencies = [ - "gl_generator", - "x11-dl", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" -dependencies = [ - "gl_generator", + "log", + "plain", + "scroll", ] [[package]] @@ -1658,13 +447,12 @@ name = "gotcha-app" version = "0.1.0" dependencies = [ "gotcha_gitea", - "pulldown-cmark", "security-framework", "serde", "serde_json", - "slint", - "slint-build", + "thiserror", "tokio", + "uniffi", ] [[package]] @@ -1689,60 +477,11 @@ dependencies = [ "serde_json", ] -[[package]] -name = "grid" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5" - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - -[[package]] -name = "harfrust" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d12c7c642d4ce8c2e784b4751a6634bd89583912265add4a679a8882d123fbcd" -dependencies = [ - "bitflags 2.13.1", - "bytemuck", - "read-fonts 0.39.2", - "smallvec", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -dependencies = [ - "foldhash", -] [[package]] name = "heck" @@ -1750,30 +489,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "htmlparser" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ce8546b993eaf241d69ded33b1be6d205dd9857ec879d9d18bd05d3676e144" - [[package]] name = "http" version = "1.5.0" @@ -1871,290 +586,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "i-slint-backend-linuxkms" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e76f2b13846ca54bba6b8eb3b5454b26879ad987396d0a2d8abedc31a6c469" -dependencies = [ - "calloop 0.14.4", - "cfg_aliases", - "drm", - "gbm", - "glutin", - "i-slint-common", - "i-slint-core", - "i-slint-renderer-skia", - "input", - "memmap2", - "nix", - "raw-window-handle", - "xkbcommon", -] - -[[package]] -name = "i-slint-backend-selector" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2247a4590a02a466b2f4916cc789dfe66247f6af826f00eb84a2685259a45c1c" -dependencies = [ - "cfg-if", - "cfg_aliases", - "i-slint-backend-linuxkms", - "i-slint-backend-testing", - "i-slint-backend-winit", - "i-slint-common", - "i-slint-core", - "i-slint-core-macros", - "i-slint-renderer-skia", -] - -[[package]] -name = "i-slint-backend-testing" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e901e3d47ab829c0ef500c63155776208707cd93259e6a7803ed627fa2786" -dependencies = [ - "cfg_aliases", - "i-slint-common", - "i-slint-core", - "i-slint-renderer-skia", - "vtable", -] - -[[package]] -name = "i-slint-backend-winit" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27c7e8f53418d3017362465367b6612b6e439a68097d627d1b91c94b885f0a6" -dependencies = [ - "block2 0.6.2", - "cfg-if", - "cfg_aliases", - "copypasta", - "derive_more", - "futures", - "glutin", - "glutin-winit", - "i-slint-common", - "i-slint-core", - "i-slint-core-macros", - "i-slint-renderer-skia", - "lyon_path", - "muda", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", - "objc2-ui-kit 0.3.2", - "pin-weak", - "raw-window-handle", - "scoped-tls-hkt", - "scopeguard", - "softbuffer", - "strum", - "vtable", - "wasm-bindgen", - "web-sys", - "webbrowser", - "windows", - "winit", - "zbus", -] - -[[package]] -name = "i-slint-common" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e63f8ea55ba21e44567ae67d2b01a4f5d09bb1fab693b1e6c79d96bef9e456" -dependencies = [ - "derive_more", - "fontique", - "htmlparser", - "icu_decimal", - "icu_locale_core", - "icu_provider", - "pulldown-cmark", - "resvg", - "skrifa 0.42.1", -] - -[[package]] -name = "i-slint-compiler" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ea275b15a425c7f2f77481151e1f5f8f1ea83feae580273090ef6b9e192218" -dependencies = [ - "annotate-snippets", - "by_address", - "data-url", - "derive_more", - "i-slint-common", - "icu_normalizer", - "image", - "itertools 0.14.0", - "linked_hash_set", - "lyon_extra", - "lyon_path", - "num_enum", - "proc-macro2", - "quote", - "rayon", - "resvg", - "rowan", - "rspolib", - "skrifa 0.42.1", - "smol_str 0.3.2", - "strum", - "swash", - "typed-index-collections", - "unicode-segmentation", - "url", -] - -[[package]] -name = "i-slint-core" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e407dc2bd5385dcbdd9c1cf927213d2e4bacda667545abf8839c60f8290f754" -dependencies = [ - "auto_enums", - "bitflags 2.13.1", - "cfg-if", - "chrono", - "clru", - "const-field-offset", - "derive_more", - "euclid", - "i-slint-common", - "i-slint-core-macros", - "icu_normalizer", - "image", - "lyon_algorithms", - "lyon_extra", - "lyon_geom", - "lyon_path", - "num-traits", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "once_cell", - "parley", - "pin-project", - "pin-weak", - "portable-atomic", - "raw-window-handle", - "resvg", - "rgb", - "scopeguard", - "skrifa 0.42.1", - "slab", - "strum", - "swash", - "sys-locale", - "taffy", - "unicode-linebreak", - "unicode-script", - "unicode-segmentation", - "vtable", - "wasm-bindgen", - "web-sys", - "web-time", - "windows", -] - -[[package]] -name = "i-slint-core-macros" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a842640da4c4d0909d25cc5a51e1f9cde02bd48f7fdfa94df10d595d4e3c80a" -dependencies = [ - "quote", - "serde_json", - "syn 2.0.119", -] - -[[package]] -name = "i-slint-renderer-skia" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b6eed7f3f0a9a3d3ca6e8b9d4ca233371d989351fdb2a7ab88ec368b99e7b57" -dependencies = [ - "bytemuck", - "cfg-if", - "cfg_aliases", - "clru", - "const-field-offset", - "derive_more", - "glow", - "glutin", - "i-slint-common", - "i-slint-core", - "i-slint-core-macros", - "lyon_path", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", - "objc2-quartz-core 0.3.2", - "pin-weak", - "raw-window-handle", - "raw-window-metal", - "read-fonts 0.39.2", - "scoped-tls-hkt", - "skia-safe", - "softbuffer", - "unicode-segmentation", - "vtable", - "windows", - "write-fonts", -] - -[[package]] -name = "i-slint-renderer-software" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73496f164f8e471877c35e0fadb996dcc674922712abd9c2be2cbe80c58859bd" -dependencies = [ - "bytemuck", - "clru", - "derive_more", - "euclid", - "i-slint-common", - "i-slint-core", - "integer-sqrt", - "lyon_path", - "num-traits", - "skrifa 0.42.1", - "swash", - "zeno", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "icu_collections" version = "2.1.1" @@ -2168,42 +599,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_decimal" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38c52231bc348f9b982c1868a2af3195199623007ba2c7650f432038f5b3e8e" -dependencies = [ - "fixed_decimal", - "icu_decimal_data", - "icu_locale", - "icu_locale_core", - "icu_provider", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_decimal_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2905b4044eab2dd848fe84199f9195567b63ab3a93094711501363f63546fef7" - -[[package]] -name = "icu_locale" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_locale_data", - "icu_provider", - "potential_utf", - "tinystr", - "zerovec", -] - [[package]] name = "icu_locale_core" version = "2.1.1" @@ -2212,18 +607,11 @@ checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", - "serde", "tinystr", "writeable", "zerovec", ] -[[package]] -name = "icu_locale_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831" - [[package]] name = "icu_normalizer" version = "2.1.1" @@ -2272,8 +660,6 @@ checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "serde", - "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -2281,27 +667,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_segmenter" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a807a7488f3f758629ae86d99d9d30dce24da2fb2945d74c80a4f4a62c71db73" -dependencies = [ - "icu_collections", - "icu_locale", - "icu_provider", - "icu_segmenter_data", - "potential_utf", - "utf8_iter", - "zerovec", -] - -[[package]] -name = "icu_segmenter_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebbb7321d9e21d25f5660366cb6c08201d0175898a3a6f7a41ee9685af21c80" - [[package]] name = "idna" version = "1.1.0" @@ -2323,52 +688,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "image" -version = "0.25.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" -dependencies = [ - "bytemuck", - "byteorder-lite", - "color_quant", - "exr", - "gif", - "image-webp", - "moxcms", - "num-traits", - "png", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core 0.5.1", - "zune-jpeg 0.5.15", -] - -[[package]] -name = "image-webp" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "imagesize" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" - -[[package]] -name = "imgref" -version = "1.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" - [[package]] name = "indexmap" version = "2.14.0" @@ -2376,57 +695,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", -] - -[[package]] -name = "input" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9793345a65d71317763a33066b5d8351f8760dde8d4930fe9e39b5f14a7959d" -dependencies = [ - "bitflags 2.13.1", - "input-sys", - "libc", - "log", - "udev", -] - -[[package]] -name = "input-sys" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eee07d8e02bd95bf52b2e642cf13d33701b94c6e4b04fbf1d1fb07e9cb19e7" - -[[package]] -name = "integer-sqrt" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" -dependencies = [ - "num-traits", -] - -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", + "hashbrown", + "serde", + "serde_core", ] [[package]] @@ -2435,24 +706,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.18" @@ -2468,10 +721,10 @@ dependencies = [ "cfg-if", "combine", "jni-macros", - "jni-sys 0.4.1", + "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.19", + "thiserror", "walkdir", "windows-link", ] @@ -2489,15 +742,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - [[package]] name = "jni-sys" version = "0.4.1" @@ -2538,134 +782,12 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.13.1", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "kurbo" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" -dependencies = [ - "arrayvec", - "euclid", - "polycool", - "smallvec", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "lebe" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" - [[package]] name = "libc" version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" -[[package]] -name = "libfuzzer-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" -dependencies = [ - "arbitrary", - "cc", -] - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libredox" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" -dependencies = [ - "bitflags 2.13.1", - "libc", - "plain", - "redox_syscall 0.9.1", -] - -[[package]] -name = "libudev-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "linebender_resource_handle" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "linked_hash_set" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "984fb35d06508d1e69fc91050cceba9c0b748f983e6739fa2c7a9237154c52c8" -dependencies = [ - "linked-hash-map", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2684,96 +806,18 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lyon_algorithms" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8575c0d003ae459399623c4def180c63b77f343b1a7fee64f249b349e7699a31" -dependencies = [ - "lyon_path", - "num-traits", -] - -[[package]] -name = "lyon_extra" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7755f08423275157ad1680aaecc9ccb7e0cc633da3240fea2d1522935cc15c72" -dependencies = [ - "lyon_path", - "thiserror 2.0.19", -] - -[[package]] -name = "lyon_geom" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" -dependencies = [ - "arrayvec", - "euclid", - "num-traits", -] - -[[package]] -name = "lyon_path" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" -dependencies = [ - "lyon_geom", - "num-traits", -] - -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - [[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" -[[package]] -name = "memmap2" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "mime" version = "0.3.17" @@ -2796,16 +840,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - [[package]] name = "mio" version = "1.2.2" @@ -2817,98 +851,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "moxcms" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" -dependencies = [ - "num-traits", - "pxfm", -] - -[[package]] -name = "muda" -version = "0.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" -dependencies = [ - "crossbeam-channel", - "dpi", - "keyboard-types", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "once_cell", - "png", - "thiserror 2.0.19", - "windows-sys 0.61.2", -] - -[[package]] -name = "natord" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.13.1", - "jni-sys 0.3.1", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys 0.3.1", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.31.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "no_std_io2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" -dependencies = [ - "memchr", -] - [[package]] name = "nom" version = "7.1.3" @@ -2919,488 +861,11 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "bytemuck", - "num-traits", -] - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_enum" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "libc", - "objc2 0.5.2", - "objc2-core-data 0.2.2", - "objc2-core-image 0.2.2", - "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "libc", - "objc2 0.6.4", - "objc2-cloud-kit 0.3.2", - "objc2-core-data 0.3.2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image 0.3.2", - "objc2-core-text", - "objc2-core-video", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.13.1", - "dispatch2", - "objc2 0.6.4", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.13.1", - "dispatch2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal 0.2.2", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-core-video" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-io-surface", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "dispatch", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-metal" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal 0.2.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-cloud-kit 0.2.2", - "objc2-core-data 0.2.2", - "objc2-core-image 0.2.2", - "objc2-core-location", - "objc2-foundation 0.2.2", - "objc2-link-presentation", - "objc2-quartz-core 0.2.2", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -dependencies = [ - "critical-section", - "portable-atomic", -] [[package]] name = "openssl-probe" @@ -3408,147 +873,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "orbclient" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" -dependencies = [ - "libc", - "libredox", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "owned_ttf_parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" -dependencies = [ - "ttf-parser", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parlance" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6937eda350acc1a5d05872c3cbf99fe78619c269096e2be3d4a350058639d5" - -[[package]] -name = "parley" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1cfcf399c774719fb1fa51bc6b91e86bdf003b03202a0902f1827ba6750746" -dependencies = [ - "fontique", - "harfrust", - "hashbrown 0.17.1", - "icu_normalizer", - "icu_properties", - "icu_segmenter", - "linebender_resource_handle", - "parlance", - "parley_data", - "skrifa 0.42.1", -] - -[[package]] -name = "parley_data" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d3755e2cc12c0625b0cd2f2773c6a37dd11d1531940c945ade4aeb78f2b145" -dependencies = [ - "icu_properties", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pastey" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" - [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pin-weak" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b330c9d1b92dfe68442ca20b009c717d5f0b1e3cf4965e62f704c3c6e95a1305" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - [[package]] name = "pkg-config" version = "0.3.33" @@ -3561,90 +897,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.13.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.5.2", - "pin-project-lite", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - -[[package]] -name = "polycool" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "portable-atomic" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" -dependencies = [ - "critical-section", -] - [[package]] name = "potential_utf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ - "serde_core", - "writeable", "zerovec", ] -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.119", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -3654,97 +915,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "profiling" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pulldown-cmark" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" -dependencies = [ - "bitflags 2.13.1", - "getopts", - "memchr", - "pulldown-cmark-escape", - "unicase", -] - -[[package]] -name = "pulldown-cmark-escape" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" - -[[package]] -name = "pulp" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" -dependencies = [ - "bytemuck", - "cfg-if", - "libm", - "num-complex", - "paste", - "pulp-wasm-simd-flag", - "raw-cpuid", - "reborrow", - "version_check", -] - -[[package]] -name = "pulp-wasm-simd-flag" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" - -[[package]] -name = "pxfm" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" - -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quick-xml" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" -dependencies = [ - "memchr", -] - [[package]] name = "quinn" version = "0.11.11" @@ -3756,10 +926,10 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.3", + "rustc-hash", "rustls", "socket2", - "thiserror 2.0.19", + "thiserror", "tokio", "tracing", "web-time", @@ -3775,14 +945,14 @@ dependencies = [ "bytes", "getrandom 0.4.3", "lru-slab", - "rand 0.10.2", + "rand", "rand_pcg", "ring", - "rustc-hash 2.1.3", + "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.19", + "thiserror", "tinyvec", "tracing", "web-time", @@ -3811,28 +981,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - [[package]] name = "rand" version = "0.10.2" @@ -3841,26 +995,7 @@ checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", "getrandom 0.4.3", - "rand_core 0.10.1", -] - -[[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.5", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", + "rand_core", ] [[package]] @@ -3875,189 +1010,9 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "rand_core 0.10.1", + "rand_core", ] -[[package]] -name = "rav1e" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" -dependencies = [ - "aligned-vec", - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av-scenechange", - "av1-grain", - "bitstream-io", - "built", - "cfg-if", - "interpolate_name", - "itertools 0.14.0", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "paste", - "profiling", - "rand 0.9.5", - "rand_chacha", - "simd_helpers", - "thiserror 2.0.19", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rayon", - "rgb", -] - -[[package]] -name = "raw-cpuid" -version = "11.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "raw-window-metal" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" -dependencies = [ - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", -] - -[[package]] -name = "rayon" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "read-fonts" -version = "0.39.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81" -dependencies = [ - "bytemuck", - "font-types 0.11.3", -] - -[[package]] -name = "read-fonts" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" -dependencies = [ - "bytemuck", - "font-types 0.12.1", - "once_cell", -] - -[[package]] -name = "reborrow" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "redox_syscall" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07507be7b4a5f9f26eeb41eeaebb1f5a7ff29dfb29739facc21d35bf8b11c21e" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - [[package]] name = "reqwest" version = "0.13.4" @@ -4100,32 +1055,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "resvg" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be183ad6a216aa96f33e4c8033b0988b8b3ea6fd2359d19af5bac4643fd8e81" -dependencies = [ - "gif", - "image-webp", - "log", - "pico-args", - "rgb", - "svgtypes", - "tiny-skia 0.12.0", - "usvg", - "zune-jpeg 0.5.15", -] - -[[package]] -name = "rgb" -version = "0.8.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" -dependencies = [ - "bytemuck", -] - [[package]] name = "ring" version = "0.17.14" @@ -4140,27 +1069,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rowan" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" -dependencies = [ - "countme", - "hashbrown 0.14.5", - "rustc-hash 1.1.0", - "text-size", -] - -[[package]] -name = "roxmltree" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" -dependencies = [ - "memchr", -] - [[package]] name = "rpassword" version = "7.5.4" @@ -4172,19 +1080,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rspolib" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fda9a7796aff63a7b1b39ccc93fffaaf65e20042984b4843041a49ca4677535" -dependencies = [ - "lazy_static", - "natord", - "snafu", - "unicode-linebreak", - "unicode-width", -] - [[package]] name = "rtoolbox" version = "0.0.5" @@ -4195,12 +1090,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.3" @@ -4216,29 +1105,16 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.1", + "bitflags", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -4284,7 +1160,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "jni", "log", @@ -4323,24 +1199,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" -[[package]] -name = "rustybuzz" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" -dependencies = [ - "bitflags 2.13.1", - "bytemuck", - "core_maths", - "log", - "smallvec", - "ttf-parser", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-properties", - "unicode-script", -] - [[package]] name = "ryu" version = "1.0.23" @@ -4366,34 +1224,23 @@ dependencies = [ ] [[package]] -name = "scoped-tls" -version = "1.0.1" +name = "scroll" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scoped-tls-hkt" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9603871ffe5df3ac39cb624790c296dbd47a400d202f56bf3e414045099524d" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sctk-adwaita" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" dependencies = [ - "ab_glyph", - "log", - "memmap2", - "smithay-client-toolkit 0.19.2", - "tiny-skia 0.11.4", + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -4402,8 +1249,8 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.10.1", + "bitflags", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -4424,6 +1271,10 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -4513,34 +1364,12 @@ dependencies = [ "unsafe-libyaml", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - [[package]] name = "simd_cesu8" version = "1.2.0" @@ -4551,145 +1380,24 @@ dependencies = [ "simdutf8", ] -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - [[package]] name = "simdutf8" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "simplecss" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" -dependencies = [ - "log", -] - [[package]] name = "siphasher" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" -[[package]] -name = "skia-bindings" -version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2d1c3ebd697c0cbded0145e9204a38fa6b268446051b7196d0a096414ea7f3" -dependencies = [ - "bindgen", - "cc", - "flate2", - "heck", - "pkg-config", - "regex", - "serde_json", - "tar", - "toml", -] - -[[package]] -name = "skia-safe" -version = "0.99.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f512ac418a64194842dd05566320805dad1c957c521039db2486fd6368865bc" -dependencies = [ - "bitflags 2.13.1", - "skia-bindings", - "windows", -] - -[[package]] -name = "skrifa" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564" -dependencies = [ - "bytemuck", - "read-fonts 0.39.2", -] - -[[package]] -name = "skrifa" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" -dependencies = [ - "bytemuck", - "read-fonts 0.41.0", -] - [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" -[[package]] -name = "slint" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce12038f57f8f3a423564b0c9301a2ab3fda4246e4cbadd6b97acf9124bdc315" -dependencies = [ - "const-field-offset", - "i-slint-backend-selector", - "i-slint-common", - "i-slint-core", - "i-slint-core-macros", - "i-slint-renderer-skia", - "i-slint-renderer-software", - "num-traits", - "once_cell", - "pin-weak", - "slint-macros", - "unicode-segmentation", - "vtable", -] - -[[package]] -name = "slint-build" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76684c11441f775b10bbc7b0e6b3016a39f17efdd659ffca112f17cfd6bdaed1" -dependencies = [ - "derive_more", - "fontique", - "i-slint-compiler", - "spin_on", - "toml_edit", -] - -[[package]] -name = "slint-macros" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2a763e72e1e53c496f571d36327e777da1eb0d9e58fceb83890c2a83ee7223" -dependencies = [ - "i-slint-compiler", - "proc-macro2", - "quote", - "spin_on", -] - -[[package]] -name = "slotmap" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" -dependencies = [ - "version_check", -] - [[package]] name = "smallvec" version = "1.15.2" @@ -4697,107 +1405,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] -name = "smithay-client-toolkit" -version = "0.19.2" +name = "smawk" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.13.1", - "calloop 0.13.0", - "calloop-wayland-source 0.3.0", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-client-toolkit" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" -dependencies = [ - "bitflags 2.13.1", - "calloop 0.14.4", - "calloop-wayland-source 0.4.1", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 1.1.4", - "thiserror 2.0.19", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-experimental", - "wayland-protocols-misc", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-clipboard" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" -dependencies = [ - "libc", - "smithay-client-toolkit 0.20.0", - "wayland-backend", -] - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - -[[package]] -name = "smol_str" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" -dependencies = [ - "borsh", - "serde", -] - -[[package]] -name = "snafu" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" -dependencies = [ - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" [[package]] name = "socket2" @@ -4809,47 +1420,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" -dependencies = [ - "as-raw-xcb-connection", - "bytemuck", - "drm", - "fastrand", - "js-sys", - "memmap2", - "ndk", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", - "raw-window-handle", - "redox_syscall 0.5.18", - "rustix 1.1.4", - "tiny-xlib", - "tracing", - "wasm-bindgen", - "wayland-backend", - "wayland-client", - "wayland-sys", - "web-sys", - "windows-sys 0.61.2", - "x11rb", -] - -[[package]] -name = "spin_on" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076e103ed41b9864aa838287efe5f4e3a7a0362dd00671ae62a212e5e4612da2" -dependencies = [ - "pin-utils", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4857,34 +1427,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "strict-num" -version = "0.1.1" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -dependencies = [ - "float-cmp", -] - -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" @@ -4892,27 +1438,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "svgtypes" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" -dependencies = [ - "kurbo", - "siphasher", -] - -[[package]] -name = "swash" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e" -dependencies = [ - "skrifa 0.44.0", - "yazi", - "zeno", -] - [[package]] name = "syn" version = "2.0.119" @@ -4955,41 +1480,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "sys-locale" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" -dependencies = [ - "js-sys", - "libc", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "taffy" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aea22054047c16c3f34d3ac473a2170be1424b1115b2a3adcf28cfb067c88859" -dependencies = [ - "arrayvec", - "grid", - "serde", - "slotmap", -] - -[[package]] -name = "tar" -version = "0.4.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" -dependencies = [ - "filetime", - "libc", - "xattr", -] - [[package]] name = "tempfile" version = "3.27.0" @@ -4999,23 +1489,17 @@ dependencies = [ "fastrand", "getrandom 0.4.3", "once_cell", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] [[package]] -name = "text-size" -version = "1.1.1" +name = "textwrap" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ - "thiserror-impl 1.0.69", + "smawk", ] [[package]] @@ -5024,18 +1508,7 @@ version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.19", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror-impl", ] [[package]] @@ -5049,84 +1522,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "tiff" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" -dependencies = [ - "fax", - "flate2", - "half", - "quick-error", - "weezl", - "zune-jpeg 0.4.21", -] - -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "tiny-skia-path 0.11.4", -] - -[[package]] -name = "tiny-skia" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "png", - "tiny-skia-path 0.12.0", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tiny-skia-path" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tiny-xlib" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4" -dependencies = [ - "as-raw-xcb-connection", - "ctor", - "libloading", - "pkg-config", - "tracing", -] - [[package]] name = "tinystr" version = "0.8.3" @@ -5134,7 +1529,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", - "serde_core", "zerovec", ] @@ -5227,19 +1621,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.25.13+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", -] - [[package]] name = "toml_parser" version = "1.1.3+spec-1.1.0" @@ -5276,7 +1657,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.1", + "bitflags", "bytes", "futures-util", "http", @@ -5306,23 +1687,10 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "tracing-core" version = "0.1.36" @@ -5338,72 +1706,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -dependencies = [ - "core_maths", -] - -[[package]] -name = "typed-index-collections" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd393dbd1e7b23e0cab7396570309b4068aa504e9dac2cd41d827583b4e9ab7" -dependencies = [ - "bincode", - "serde", -] - -[[package]] -name = "udev" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4e37e9ea4401fc841ff54b9ddfc9be1079b1e89434c1a6a865dd68980f7e9f" -dependencies = [ - "io-lifetimes", - "libc", - "libudev-sys", - "pkg-config", -] - -[[package]] -name = "uds_windows" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" -dependencies = [ - "memoffset", - "tempfile", - "windows-sys 0.61.2", -] - [[package]] name = "unicase" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-bidi-mirroring" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" - -[[package]] -name = "unicode-ccc" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" - [[package]] name = "unicode-ident" version = "1.0.24" @@ -5411,46 +1719,124 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] -name = "unicode-linebreak" -version = "0.1.5" +name = "uniffi" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +checksum = "a782a48d72cfd7a2d65cfc7c691dbf5375c43104b3c195f7eccc716dcc3540c8" +dependencies = [ + "anyhow", + "cargo_metadata", + "uniffi_bindgen", + "uniffi_core", + "uniffi_macros", + "uniffi_pipeline", +] [[package]] -name = "unicode-properties" -version = "0.1.4" +name = "uniffi_bindgen" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +checksum = "533b0312c73e3b54eb78a4b257ceae390962dd4767995778309a74644643f9ac" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "fs-err", + "glob", + "goblin", + "heck", + "indexmap", + "once_cell", + "serde", + "tempfile", + "textwrap", + "toml", + "uniffi_internal_macros", + "uniffi_meta", + "uniffi_pipeline", + "uniffi_udl", +] [[package]] -name = "unicode-script" -version = "0.5.8" +name = "uniffi_core" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" +checksum = "8e32e261c5b0dfaba6488f536e71957dddd6b1a498ac7eb791bee56b60a086be" +dependencies = [ + "anyhow", + "async-compat", + "bytes", + "once_cell", + "static_assertions", +] [[package]] -name = "unicode-segmentation" -version = "1.13.3" +name = "uniffi_internal_macros" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +checksum = "84ae78069a5e6772ef694fd5bdb628532c88d2c2f0e7142bf6a384636eadb1af" +dependencies = [ + "anyhow", + "indexmap", + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "unicode-vo" -version = "0.1.0" +name = "uniffi_macros" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" +checksum = "330be6770532e86320df31f54c70bb0be67588594e8e77fa56e9083a3fed5d0d" +dependencies = [ + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.119", + "toml", + "uniffi_meta", +] [[package]] -name = "unicode-width" -version = "0.2.2" +name = "uniffi_meta" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +checksum = "78de021f5547e56ab16c665a49d67d4fd3d31e77422f7739a2e9359d328cd9e7" +dependencies = [ + "anyhow", + "siphasher", + "uniffi_internal_macros", + "uniffi_pipeline", +] [[package]] -name = "unicode-xid" -version = "0.2.6" +name = "uniffi_pipeline" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "3f8201bb1907ed8a42d80e11cbc25c8a033e7a31c3cff1d911f56eedb81d4948" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "tempfile", + "uniffi_internal_macros", +] + +[[package]] +name = "uniffi_udl" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e57996bc58009cc29bf04845d627ae313c2547b87171c1c349d6c51a1656c0" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "weedle2", +] [[package]] name = "unsafe-libyaml" @@ -5464,12 +1850,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "unty" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" - [[package]] name = "url" version = "2.5.8" @@ -5482,91 +1862,12 @@ dependencies = [ "serde", ] -[[package]] -name = "usvg" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d46cf96c5f498d36b7a9693bc6a7075c0bb9303189d61b2249b0dc3d309c07de" -dependencies = [ - "base64", - "data-url", - "flate2", - "fontdb", - "imagesize", - "kurbo", - "log", - "pico-args", - "roxmltree", - "rustybuzz", - "simplecss", - "siphasher", - "strict-num", - "svgtypes", - "tiny-skia-path 0.12.0", - "ttf-parser", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", -] - [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "uuid" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" -dependencies = [ - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "v_frame" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" -dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vtable" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea953f65593feb5287b022b83c35046d1ff49c7417b6f5851a079d8a231fe31" -dependencies = [ - "const-field-offset", - "portable-atomic", - "stable_deref_trait", - "vtable-macro", -] - -[[package]] -name = "vtable-macro" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "482a37f9777480673d3a99c80ac2edc69f92382230440946855fa27ebc47cba6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -5592,15 +1893,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" version = "0.2.126" @@ -5669,141 +1961,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wayland-backend" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016ccf01d1c58b6f8999612813e17c9b2390f7d70671428869913310f83f54b8" -dependencies = [ - "cc", - "downcast-rs", - "rustix 1.1.4", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" -dependencies = [ - "bitflags 2.13.1", - "rustix 1.1.4", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-csd-frame" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" -dependencies = [ - "bitflags 2.13.1", - "cursor-icon", - "wayland-backend", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" -dependencies = [ - "rustix 1.1.4", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" -dependencies = [ - "bitflags 2.13.1", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-experimental" -version = "20250721.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" -dependencies = [ - "bitflags 2.13.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-misc" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" -dependencies = [ - "bitflags 2.13.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-plasma" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" -dependencies = [ - "bitflags 2.13.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" -dependencies = [ - "bitflags 2.13.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] - [[package]] name = "web-sys" version = "0.3.103" @@ -5824,22 +1981,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webbrowser" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef62a3d5f7b2411119a11b6f62570dbff91d7105e011a20fb83fbf8f5761c40f" -dependencies = [ - "jni", - "log", - "ndk-context", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "url", - "web-sys", -] - [[package]] name = "webpki-root-certs" version = "1.0.9" @@ -5850,10 +1991,13 @@ dependencies = [ ] [[package]] -name = "weezl" -version = "0.1.12" +name = "weedle2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] [[package]] name = "winapi-util" @@ -5864,123 +2008,19 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core", - "windows-link", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core", - "windows-link", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -5989,7 +2029,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -6001,76 +2041,34 @@ dependencies = [ "windows-link", ] -[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link", -] - -[[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_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_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" @@ -6083,106 +2081,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[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_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_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_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 = "winit" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" -dependencies = [ - "ahash", - "android-activity", - "atomic-waker", - "bitflags 2.13.1", - "block2 0.5.1", - "bytemuck", - "calloop 0.13.0", - "cfg_aliases", - "concurrent-queue", - "core-foundation 0.9.4", - "core-graphics", - "cursor-icon", - "dpi", - "js-sys", - "libc", - "memmap2", - "ndk", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit 0.2.2", - "orbclient", - "percent-encoding", - "pin-project", - "raw-window-handle", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "sctk-adwaita", - "smithay-client-toolkit 0.19.2", - "smol_str 0.2.2", - "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-plasma", - "web-sys", - "web-time", - "windows-sys 0.52.0", - "x11-dl", - "x11rb", - "xkbcommon-dl", -] - [[package]] name = "winnow" version = "1.0.4" @@ -6192,154 +2114,12 @@ dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "write-fonts" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb731d4c4d93eacc69a1ad2f270f905788a98e4a3438267bcafbe08d3431c8d8" -dependencies = [ - "font-types 0.11.3", - "indexmap", - "kurbo", - "log", - "read-fonts 0.39.2", -] - [[package]] name = "writeable" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" -[[package]] -name = "x11-clipboard" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" -dependencies = [ - "libc", - "x11rb", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" -dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "libloading", - "once_cell", - "rustix 1.1.4", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" - -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix 1.1.4", -] - -[[package]] -name = "xcursor" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" - -[[package]] -name = "xkbcommon" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a974f48060a14e95705c01f24ad9c3345022f4d97441b8a36beb7ed5c4a02d" -dependencies = [ - "libc", - "memmap2", - "xkeysym", -] - -[[package]] -name = "xkbcommon-dl" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" -dependencies = [ - "bitflags 2.13.1", - "dlib", - "log", - "once_cell", - "xkeysym", -] - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "xml-rs" -version = "0.8.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" - -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - -[[package]] -name = "y4m" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" - -[[package]] -name = "yazi" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" - -[[package]] -name = "yeslogic-fontconfig-sys" -version = "6.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" -dependencies = [ - "dlib", - "once_cell", - "pkg-config", -] - [[package]] name = "yoke" version = "0.8.3" @@ -6363,93 +2143,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zbus" -version = "5.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe18fb60dc696039e738717b76eaea21e7a4489bbb1885020b43c94236d7e98a" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "libc", - "ordered-stream", - "rustix 1.1.4", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys 0.61.2", - "winnow", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" -dependencies = [ - "serde", - "winnow", - "zvariant", -] - -[[package]] -name = "zeno" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" - -[[package]] -name = "zerocopy" -version = "0.8.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "zerofrom" version = "0.1.8" @@ -6486,7 +2179,6 @@ dependencies = [ "displaydoc", "yoke", "zerofrom", - "zerovec", ] [[package]] @@ -6495,7 +2187,6 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ - "serde", "yoke", "zerofrom", "zerovec-derive", @@ -6517,82 +2208,3 @@ name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "zune-jpeg" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" -dependencies = [ - "zune-core 0.4.12", -] - -[[package]] -name = "zune-jpeg" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" -dependencies = [ - "zune-core 0.5.1", -] - -[[package]] -name = "zvariant" -version = "5.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee2a0bcd2a907786a456fff45aaaaf54c9ba5f50b71ae9ec1a4edd200c94911" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.119", - "winnow", -] diff --git a/Cargo.toml b/Cargo.toml index 090e52e..5da1254 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,3 @@ serde_json = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } rpassword = "7" serde_yaml = "0.9" -slint = { version = "=1.17.1", default-features = false, features = ["std", "backend-winit", "renderer-skia", "compat-1-2"] } diff --git a/README.md b/README.md index 4a0cc9a..19f4045 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@ # Gotcha -Gotcha is a lightweight Gitea client built in Rust. Development starts with a -reusable API crate and a CLI test bed; the same crate will back the Slint iOS -application. +Gotcha is a lightweight Gitea client with a reusable Rust core, a CLI, and a +native iOS application. The iOS interface is UIKit/Swift; UniFFI exposes the +Rust application logic to Swift. ## Workspace - `gotcha_gitea`: reusable asynchronous Gitea API client with the complete typed Gitea 1.25 API and model surface - `gotcha`: CLI for typed common operations and arbitrary API requests -- `gotcha-app`: Slint iOS client for servers, owned repositories, favorites, - open issues, and issue details; bundle identifier `de.rfc1437.gotcha` +- `gotcha-app`: Rust application core and UniFFI API used by iOS +- `ios`: native UIKit application for activity, repositories, favorites, + issues, milestones, pull requests, commit history, changed files, and diffs; bundle + identifier `de.rfc1437.gotcha` The generated API modules and models cover every Gitea 1.25 operation. The low-level request API remains available for newer instance-specific endpoints; @@ -53,19 +55,23 @@ derives the `owner/repository` scope. Use `--server gitea.example.com` when sele ambiguous, or `--url`/`GITEA_URL` for an unconfigured server. Tokens are never accepted as command-line arguments or environment variables. -## Direction +## Architecture -1. Exercise and type API areas in the CLI: repositories, issues and pull - requests, Actions, notifications, organizations, packages, administration. -2. Add the Slint shell and move proven read workflows into touch-first screens. -3. Add write workflows and iOS integrations such as sharing, notifications, - and background refresh. +The Rust core owns Gitea access, validation, presentation records, preferences, +favorites, and Keychain-backed credentials. UniFFI generates the Swift bridge +in `ios/Generated`. UIKit owns navigation, lists, text input, menus, scrolling, +pull-to-refresh, appearance, and other platform behavior. + +New API workflows should first be exercised and typed in the CLI, then exposed +through the Rust core and presented with native UIKit controls. Future work +includes write workflows and iOS integrations such as sharing, notifications, +and background refresh. ## iOS app -Server tokens are kept in the Apple Keychain; the JSON preferences contain only -server metadata and favorites. The app requires Xcode, XcodeGen, an installed -iOS Simulator runtime, and Rust's +Server tokens are kept in the Apple Keychain; JSON preferences contain only +server metadata, settings, and favorites. The app requires Xcode, XcodeGen, an +installed iOS Simulator runtime, and Rust's `aarch64-apple-ios` and `aarch64-apple-ios-sim` targets. Generate the project after installing those prerequisites: @@ -76,3 +82,6 @@ open Gotcha.xcodeproj ``` For a fast host-side check, run `cargo test -p gotcha-app`. + +See [TESTING.md](TESTING.md) for the complete build, simulator, gesture, and +release regression checklist. diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..61ea042 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,187 @@ +# Testing Gotcha + +Use this checklist for every iOS release. Add checks when a new screen or +interaction ships so the document remains the release regression suite. + +## Test record + +- Release/build: +- Commit: +- Date and tester: +- Xcode and iOS versions: +- Simulator/device: +- Gitea server version: +- Result and open issues: + +Use a real test account with repositories, open and closed issues, open and +closed pull requests, multiple branches, commits, comments, and file changes. +Never paste a production token into logs, screenshots, source files, or this +document. The app stores entered tokens in the Apple Keychain. + +Run both of these passes: + +- Fresh install on a disposable simulator, including Add Server. +- Upgrade/reinstall over the previous build, preserving app data and Keychain. + +## Build gates + +From the repository root, all commands must pass without warnings: + +```sh +cargo fmt --all -- --check +RUSTFLAGS="-D warnings" cargo check --workspace --all-targets +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace +``` + +Build and install the simulator app: + +```sh +cd ios +xcodegen generate + +xcodebuild \ + -project Gotcha.xcodeproj \ + -scheme Gotcha \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ + ARCHS=arm64 \ + ONLY_ACTIVE_ARCH=YES \ + build + +gotcha_build_dir="$( + xcodebuild \ + -project Gotcha.xcodeproj \ + -scheme Gotcha \ + -configuration Debug \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ + -showBuildSettings -json \ + ARCHS=arm64 \ + ONLY_ACTIVE_ARCH=YES | + plutil -extract 0.buildSettings.TARGET_BUILD_DIR raw -o - - +)" +xcrun simctl install booted "$gotcha_build_dir/Gotcha.app" +xcrun simctl launch booted de.rfc1437.gotcha +``` + +## Add Server and native text editing + +- [ ] With no configured server, Issues and Repos show the Servers screen and + its empty state. +- [ ] The add button presents Add Server; Cancel dismisses it. +- [ ] Name, Server URL, and Access token use native text fields and suitable + keyboards; Next advances between fields and Done submits. +- [ ] Long-press in every field shows the native loupe, insertion point, and + selection handles. +- [ ] The standard edit menu offers Cut, Copy, Paste, Select, and Select All as + applicable. There is no app-specific editing toolbar. +- [ ] Copy text from one field and paste it into another; verify the exact text. +- [ ] The token is obscured, remains editable, and does not trigger a password + save prompt. +- [ ] Empty or invalid values show an error without adding a server. +- [ ] Valid credentials add and select the server; all data tabs load. +- [ ] Terminate and relaunch the app; the selected server and Keychain token + still work without re-entry. +- [ ] Open the server picker from a repository list and switch between at least + two configured servers; every data tab changes to the selected server. + +## Native interaction and navigation + +- [ ] Every list scrolls with normal drag, momentum, bounce, and scroll-bar + behavior. Rows are flat, use system separators, and have no card-shaped + rounded backgrounds. +- [ ] Pull past the top of Home, repository lists, issue lists, pull lists, + commit history, changed files, issue details, pull details, and diffs. + The native refresh control appears, reloads data, and disappears. +- [ ] Tap a row, scroll its detail, and use both the navigation-bar Back button + and the left-edge interactive swipe to return. +- [ ] From a list scrolled well away from the top, open a detail and go Back. + The same rows and scroll offset remain visible. +- [ ] Switch tabs while a tab has a pushed detail and a scrolled list; returning + to the tab preserves its navigation stack and position. +- [ ] Rapidly change tabs, filters, and branches while content loads. The app + neither crashes nor replaces a newer result with a cancelled request. +- [ ] Loading and error states remain dismissible and do not leave a refresh + indicator or navigation-bar spinner running. + +## Home + +- [ ] Home shows the selected server name, contribution total, 12-month heatmap, + and recent activity. +- [ ] Activity rows have the correct icon, repository, summary, and date. +- [ ] Tap repository, issue, pull-request, and commit activity. Each opens the + matching native tab and destination. +- [ ] Non-linkable server activity does not navigate or appear tappable. + +## Issues + +- [ ] The repository list shows name, description, language, open count, update + date, and current favorite state. +- [ ] Toggle a favorite and confirm it remains after refresh and relaunch. +- [ ] Open a repository; the issue list defaults to the saved Open/Closed filter. +- [ ] Change the native filter menu between Open and Closed; the checkmark, + rows, and persisted selection update. +- [ ] Open an issue and verify title, state/author metadata, Markdown body, and + comments. Links and selectable text use normal iOS interaction. + +## Repositories and commits + +- [ ] The repository list and favorite behavior match the Issues tab. +- [ ] Open a repository; commit history initially selects **All**, visibly shows + All in the navigation bar, and includes commits from multiple branches. +- [ ] Open the branch menu; All is checked. Select a branch and verify the label, + checkmark, commits, and graph update, then return to All. +- [ ] Commit graph lanes and merge connections align with their rows while + scrolling. +- [ ] Open a commit and verify Changed Files paths and statuses. +- [ ] Open a changed file and verify the diff title, old/new line numbers, + monospaced text, addition/removal/hunk colors, vertical scrolling, and + horizontal scrolling for long lines. +- [ ] Long-press diff text and verify normal selection and copying. + +## Pull requests + +- [ ] The list defaults to the saved Open/Closed filter. +- [ ] Change the native filter menu between Open and Closed; the checkmark, + rows, and persisted selection update. +- [ ] Each row shows repository/number, title, author/update metadata, comment + count, and draft/merged state where applicable. +- [ ] Open a pull request and verify title, metadata, Markdown body, comments, + changed-file reference, file paths, and statuses. +- [ ] Open a changed file and run the same diff checks as for a commit. + +## Milestones + +- [ ] Milestones uses the server → repository → milestone flow and includes + both open and closed milestones. +- [ ] Each milestone shows its title, description, state, due date, issue + counts, and a green/amber closed/open progress bar. +- [ ] Open a milestone and verify every assigned issue is listed; tapping an + issue opens its normal issue detail. +- [ ] Repository issue lists and issue details show the assigned milestone with + a flag icon instead of a text label. +- [ ] Repositories without milestones show the native empty state. + +## Settings, accessibility, and lifecycle + +- [ ] Settings opens from the gear button on Home and is not a tab. +- [ ] Appearance changes between Auto, Light, and Dark immediately; Auto follows + the simulator system appearance. +- [ ] Icon and appearance settings remain selected after relaunch. +- [ ] Test Light and Dark appearances for readable text, separators, graph lines, + diff colors, menus, selection, loading, empty, and error states. +- [ ] Test at the default and at least one larger Dynamic Type size. Text remains + readable without hiding required controls. +- [ ] VoiceOver announces tabs, navigation controls, filters, favorites, rows, + fields, and changed-file statuses meaningfully and in a usable order. +- [ ] Rotate, background/foreground, terminate/relaunch, and temporarily disable + networking. The app recovers without losing preferences or credentials. + +## Release sign-off + +- [ ] All build gates passed. +- [ ] Fresh-install and upgrade passes completed. +- [ ] Every supported iOS version and required device class completed. +- [ ] Failures are linked in the test record; release blockers are resolved. diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index 8fd655e..c5727b7 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -6,18 +6,15 @@ edition.workspace = true license.workspace = true rust-version.workspace = true -[[bin]] -name = "gotcha-app" -path = "src/main.rs" +[lib] +name = "gotcha_core" +crate-type = ["lib", "staticlib"] [dependencies] gotcha_gitea = { path = "../gitea" } -pulldown-cmark = "0.13" serde.workspace = true serde_json.workspace = true security-framework = "3" -slint.workspace = true +thiserror = "2" tokio.workspace = true - -[build-dependencies] -slint-build = "=1.17.1" +uniffi = { version = "0.32", features = ["tokio"] } diff --git a/crates/app/build.rs b/crates/app/build.rs deleted file mode 100644 index c19a7b8..0000000 --- a/crates/app/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - slint_build::compile("ui/app.slint").unwrap(); -} diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index b20db62..a540d9e 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -7,8 +7,12 @@ use gotcha_gitea::{Client, apis, models}; use tokio::task::JoinSet; use crate::{ - domain::{HistoryCommit, HomeData, RepositoryData, Server}, - view::compact_date, + diff, + domain::{ + HistoryCommit, HomeData, IssueDetails, MilestoneDetails, PullDetails, RepositoryData, + Server, + }, + presentation::compact_date, }; pub async fn load_repositories(server: &Server) -> Result, String> { @@ -81,6 +85,71 @@ pub async fn load_issues( .map_err(|error| error.to_string()) } +pub async fn load_milestones( + server: &Server, + owner: &str, + repository: &str, +) -> Result, String> { + let client = + Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; + let configuration = client.configuration(); + let mut milestones = Vec::new(); + for page in 1.. { + let batch = apis::issue_api::issue_get_milestones_list( + &configuration, + owner, + repository, + Some("all"), + None, + Some(page), + Some(100), + ) + .await + .map_err(|error| error.to_string())?; + let done = batch.len() < 100; + milestones.extend(batch); + if done { + break; + } + } + Ok(milestones) +} + +pub async fn load_milestone( + server: &Server, + owner: &str, + repository: &str, + id: i64, +) -> Result { + let client = + Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; + let configuration = client.configuration(); + let milestone = + apis::issue_api::issue_get_milestone(&configuration, owner, repository, &id.to_string()) + .await + .map_err(|error| error.to_string())?; + let issues = apis::issue_api::issue_list_issues( + &configuration, + owner, + repository, + Some("all"), + None, + None, + Some("issues"), + milestone.title.as_deref(), + None, + None, + None, + None, + None, + Some(1), + Some(100), + ) + .await + .map_err(|error| error.to_string())?; + Ok(MilestoneDetails { milestone, issues }) +} + pub async fn load_pulls(server: &Server, status: &str) -> Result, String> { let client = Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; @@ -233,6 +302,111 @@ pub async fn load_pull_files( Ok(files) } +pub async fn load_issue( + server: &Server, + owner: &str, + repository: &str, + number: i64, +) -> Result { + let client = + Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; + let configuration = client.configuration(); + let (issue, comments) = tokio::join!( + apis::issue_api::issue_get_issue(&configuration, owner, repository, number), + apis::issue_api::issue_get_comments(&configuration, owner, repository, number, None, None,), + ); + Ok(IssueDetails { + issue: issue.map_err(|error| error.to_string())?, + comments: comments.map_err(|error| error.to_string())?, + }) +} + +pub async fn load_pull( + server: &Server, + owner: &str, + repository: &str, + number: i64, +) -> Result { + let client = + Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; + let configuration = client.configuration(); + let (pull, comments, files) = tokio::join!( + apis::repository_api::repo_get_pull_request(&configuration, owner, repository, number,), + apis::issue_api::issue_get_comments(&configuration, owner, repository, number, None, None,), + load_pull_files(&configuration, owner, repository, number), + ); + Ok(PullDetails { + pull: pull.map_err(|error| error.to_string())?, + comments: comments.map_err(|error| error.to_string())?, + files: files.map_err(|error| error.to_string())?, + }) +} + +pub async fn load_commit_files( + server: &Server, + owner: &str, + repository: &str, + sha: &str, +) -> Result, String> { + let client = + Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; + apis::repository_api::repo_get_single_commit( + &client.configuration(), + owner, + repository, + sha, + Some(true), + None, + Some(true), + ) + .await + .map(|commit| commit.files.unwrap_or_default()) + .map_err(|error| error.to_string()) +} + +pub async fn load_commit_diff( + server: &Server, + owner: &str, + repository: &str, + sha: &str, + path: &str, +) -> Result { + let client = + Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; + apis::repository_api::repo_download_commit_diff_or_patch( + &client.configuration(), + owner, + repository, + sha, + "diff", + ) + .await + .map(|text| diff::parse_file(&text, path)) + .map_err(|error| error.to_string()) +} + +pub async fn load_pull_diff( + server: &Server, + owner: &str, + repository: &str, + number: i64, + path: &str, +) -> Result { + let client = + Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; + apis::repository_api::repo_download_pull_diff_or_patch( + &client.configuration(), + owner, + repository, + number, + "diff", + Some(false), + ) + .await + .map(|text| diff::parse_file(&text, path)) + .map_err(|error| error.to_string()) +} + async fn load_pull_refs( server: &Server, owner: &str, diff --git a/crates/app/src/domain.rs b/crates/app/src/domain.rs index ca36b5f..3021db2 100644 --- a/crates/app/src/domain.rs +++ b/crates/app/src/domain.rs @@ -3,35 +3,6 @@ use std::collections::BTreeSet; use gotcha_gitea::models; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum IconStyle { - Duotone, - Bold, - #[default] - #[serde(other)] - Outline, -} - -impl IconStyle { - pub fn index(self) -> i32 { - match self { - Self::Outline => 0, - Self::Duotone => 1, - Self::Bold => 2, - } - } - - pub fn from_index(index: i32) -> Option { - match index { - 0 => Some(Self::Outline), - 1 => Some(Self::Duotone), - 2 => Some(Self::Bold), - _ => None, - } - } -} - #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "lowercase")] pub enum AppearanceMode { @@ -82,8 +53,6 @@ pub struct Preferences { #[serde(default = "open_status")] pub pull_status: String, #[serde(default)] - pub icon_style: IconStyle, - #[serde(default)] pub appearance: AppearanceMode, } @@ -95,7 +64,6 @@ impl Default for Preferences { last_server: None, issue_status: open_status(), pull_status: open_status(), - icon_style: IconStyle::default(), appearance: AppearanceMode::default(), } } @@ -126,17 +94,7 @@ pub struct HistoryCommit { pub struct State { pub preferences: Preferences, pub active_server: Option, - pub active_repository: Option<(String, String)>, - pub active_issue: Option, - pub active_pull: Option<(String, String, i64)>, - pub active_commit: Option, - pub opened_from_home: bool, pub repositories: Vec, - pub issues: Vec, - pub pulls: Vec, - pub branches: Vec, - pub active_branch: Option, - pub commits: Vec, } pub struct HomeData { @@ -149,6 +107,11 @@ pub struct IssueDetails { pub comments: Vec, } +pub struct MilestoneDetails { + pub milestone: models::Milestone, + pub issues: Vec, +} + pub struct PullDetails { pub pull: models::PullRequest, pub comments: Vec, diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs new file mode 100644 index 0000000..e9a6c3d --- /dev/null +++ b/crates/app/src/lib.rs @@ -0,0 +1,355 @@ +use std::sync::{Arc, Mutex}; + +use gotcha_gitea::Client; +use thiserror::Error; + +mod activity; +mod api; +mod diff; +mod domain; +mod presentation; +mod storage; + +use api::*; +use domain::*; +pub use presentation::*; +use storage::*; + +uniffi::setup_scaffolding!(); + +#[derive(Debug, Error, uniffi::Error)] +pub enum GotchaError { + #[error("{message}")] + Message { message: String }, +} + +impl From for GotchaError { + fn from(message: String) -> Self { + Self::Message { message } + } +} + +impl From<&str> for GotchaError { + fn from(message: &str) -> Self { + message.to_string().into() + } +} + +#[derive(Clone, uniffi::Record)] +pub struct Settings { + pub issue_status: String, + pub pull_status: String, + pub appearance: u32, +} + +#[derive(uniffi::Object)] +pub struct GotchaCore { + state: Mutex, + startup_error: Option, +} + +#[uniffi::export(async_runtime = "tokio")] +impl GotchaCore { + #[uniffi::constructor] + pub fn new() -> Arc { + let (preferences, startup_error) = match load_preferences() { + Ok(preferences) => (preferences, None), + Err(error) => (Preferences::default(), Some(error)), + }; + let active_server = preferences + .last_server + .filter(|index| *index < preferences.servers.len()) + .or_else(|| (preferences.servers.len() == 1).then_some(0)); + Arc::new(Self { + state: Mutex::new(State { + preferences, + active_server, + ..Default::default() + }), + startup_error, + }) + } + + pub fn startup_error(&self) -> Option { + self.startup_error.clone() + } + + pub fn servers(&self) -> Vec { + self.state + .lock() + .unwrap() + .preferences + .servers + .iter() + .map(|server| ServerRow { + name: server.name.clone(), + url: server.url.clone(), + }) + .collect() + } + + pub fn active_server_index(&self) -> Option { + self.state + .lock() + .unwrap() + .active_server + .map(|index| index as u32) + } + + pub fn active_server_name(&self) -> Option { + let state = self.state.lock().unwrap(); + state + .active_server + .and_then(|index| state.preferences.servers.get(index)) + .map(|server| server.name.clone()) + } + + pub fn select_server(&self, index: u32) -> Result<(), GotchaError> { + let mut state = self.state.lock().unwrap(); + let index = index as usize; + if index >= state.preferences.servers.len() { + return Err("That server no longer exists.".into()); + } + state.active_server = Some(index); + state.preferences.last_server = Some(index); + state.repositories.clear(); + save_preferences(&state.preferences).map_err(Into::into) + } + + pub async fn add_server( + &self, + name: String, + url: String, + token: String, + ) -> Result { + let server = validate_server(&name, &url, &token)?; + Client::new(&server.url, Some(&server.token)) + .map_err(|error| error.to_string())? + .current_user() + .await + .map_err(|error| error.to_string())?; + save_server_token(&server)?; + let mut state = self.state.lock().unwrap(); + state.preferences.servers.push(server); + let index = state.preferences.servers.len() - 1; + state.preferences.last_server = Some(index); + state.active_server = Some(index); + save_preferences(&state.preferences)?; + Ok(index as u32) + } + + pub fn settings(&self) -> Settings { + let state = self.state.lock().unwrap(); + Settings { + issue_status: state.preferences.issue_status.clone(), + pull_status: state.preferences.pull_status.clone(), + appearance: state.preferences.appearance.index() as u32, + } + } + + pub fn set_issue_status(&self, status: String) -> Result<(), GotchaError> { + if !matches!(status.as_str(), "open" | "closed") { + return Err("Unsupported issue status.".into()); + } + let mut state = self.state.lock().unwrap(); + state.preferences.issue_status = status; + save_preferences(&state.preferences).map_err(Into::into) + } + + pub fn set_pull_status(&self, status: String) -> Result<(), GotchaError> { + if !matches!(status.as_str(), "open" | "closed") { + return Err("Unsupported pull-request status.".into()); + } + let mut state = self.state.lock().unwrap(); + state.preferences.pull_status = status; + save_preferences(&state.preferences).map_err(Into::into) + } + + pub fn set_appearance(&self, index: u32) -> Result<(), GotchaError> { + let appearance = + AppearanceMode::from_index(index as i32).ok_or("Unsupported appearance.")?; + let mut state = self.state.lock().unwrap(); + state.preferences.appearance = appearance; + save_preferences(&state.preferences).map_err(Into::into) + } + + pub async fn home(&self) -> Result { + let server = self.server()?; + let name = server.name.clone(); + Ok(home_page(name, load_home(&server).await?)) + } + + pub async fn repositories(&self) -> Result, GotchaError> { + let server = self.server()?; + let repositories = load_repositories(&server).await?; + let mut state = self.state.lock().unwrap(); + state.repositories = repositories; + Ok(self.repository_rows(&state)) + } + + pub fn toggle_favorite( + &self, + owner: String, + repository: String, + ) -> Result, GotchaError> { + let mut state = self.state.lock().unwrap(); + let server = state + .active_server + .and_then(|index| state.preferences.servers.get(index)) + .ok_or("Select a server first.")?; + let key = favorite_key(&server.url, &owner, &repository); + if !state.preferences.favorites.remove(&key) { + state.preferences.favorites.insert(key); + } + save_preferences(&state.preferences)?; + Ok(self.repository_rows(&state)) + } + + pub async fn issues( + &self, + owner: String, + repository: String, + ) -> Result, GotchaError> { + let server = self.server()?; + let status = self.state.lock().unwrap().preferences.issue_status.clone(); + Ok(issue_rows( + &load_issues(&server, &owner, &repository, &status).await?, + )) + } + + pub async fn issue( + &self, + owner: String, + repository: String, + number: i64, + ) -> Result { + Ok(issue_page( + load_issue(&self.server()?, &owner, &repository, number).await?, + )) + } + + pub async fn milestones( + &self, + owner: String, + repository: String, + ) -> Result, GotchaError> { + Ok(milestone_rows( + &load_milestones(&self.server()?, &owner, &repository).await?, + )) + } + + pub async fn milestone( + &self, + owner: String, + repository: String, + id: i64, + ) -> Result { + Ok(milestone_page( + load_milestone(&self.server()?, &owner, &repository, id).await?, + )) + } + + pub async fn pulls(&self) -> Result, GotchaError> { + let server = self.server()?; + let status = self.state.lock().unwrap().preferences.pull_status.clone(); + Ok(pull_rows(&load_pulls(&server, &status).await?)) + } + + pub async fn pull( + &self, + owner: String, + repository: String, + number: i64, + ) -> Result { + Ok(pull_page( + load_pull(&self.server()?, &owner, &repository, number).await?, + )) + } + + pub async fn commits( + &self, + owner: String, + repository: String, + branch: Option, + ) -> Result { + let server = self.server()?; + let default_branch = self + .state + .lock() + .unwrap() + .repositories + .iter() + .find(|candidate| candidate.owner == owner && candidate.name == repository) + .map(|candidate| candidate.default_branch.clone()) + .unwrap_or_else(|| "main".into()); + let branches = load_branches(&server, &owner, &repository, &default_branch).await?; + let commits = match branch.as_deref() { + Some(branch) => load_branch_commits(&server, &owner, &repository, branch).await?, + None => load_all_commits(&server, &owner, &repository, &branches).await?, + }; + Ok(commit_page(branches, &commits, branch.is_none())) + } + + pub async fn commit_files( + &self, + owner: String, + repository: String, + sha: String, + ) -> Result, GotchaError> { + Ok(commit_file_rows( + load_commit_files(&self.server()?, &owner, &repository, &sha).await?, + )) + } + + pub async fn commit_diff( + &self, + owner: String, + repository: String, + sha: String, + path: String, + ) -> Result { + Ok(diff_page( + &path, + load_commit_diff(&self.server()?, &owner, &repository, &sha, &path).await?, + )) + } + + pub async fn pull_diff( + &self, + owner: String, + repository: String, + number: i64, + path: String, + ) -> Result { + Ok(diff_page( + &path, + load_pull_diff(&self.server()?, &owner, &repository, number, &path).await?, + )) + } +} + +impl GotchaCore { + fn server(&self) -> Result { + let state = self.state.lock().unwrap(); + state + .active_server + .and_then(|index| state.preferences.servers.get(index)) + .cloned() + .ok_or_else(|| "Select a server first.".to_string().into()) + } + + fn repository_rows(&self, state: &State) -> Vec { + let server_url = state + .active_server + .and_then(|index| state.preferences.servers.get(index)) + .map(|server| server.url.as_str()) + .unwrap_or_default(); + repository_rows(&state.repositories, |repository| { + state.preferences.favorites.contains(&favorite_key( + server_url, + &repository.owner, + &repository.name, + )) + }) + } +} diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs deleted file mode 100644 index 4ba6f09..0000000 --- a/crates/app/src/main.rs +++ /dev/null @@ -1,1232 +0,0 @@ -use std::{ - future::Future, - sync::{Arc, Mutex, OnceLock}, -}; - -use gotcha_gitea::{Client, apis}; -use slint::{ModelRc, VecModel}; - -mod activity; -mod api; -mod diff; -mod domain; -mod markdown; -mod storage; -mod ui; -mod view; - -use api::*; -use domain::*; -use storage::*; -use ui::*; -use view::*; - -static RUNTIME: OnceLock = OnceLock::new(); - -fn main() -> Result<(), Box> { - let ui = AppWindow::new()?; - let (preferences, startup_error) = match load_preferences() { - Ok(preferences) => (preferences, None), - Err(error) => (Preferences::default(), Some(error)), - }; - let state = Arc::new(Mutex::new(State { - preferences, - ..Default::default() - })); - - refresh_servers(&ui, &state.lock().unwrap().preferences); - { - let state = state.lock().unwrap(); - ui.set_issue_filter(state.preferences.issue_status.clone().into()); - ui.set_pull_filter(state.preferences.pull_status.clone().into()); - ui.global::() - .set_style(state.preferences.icon_style.index()); - ui.global::() - .set_mode(state.preferences.appearance.index()); - } - if let Some(error) = startup_error { - ui.set_error(error.into()); - } - wire_callbacks(&ui, state.clone()); - - let startup_server = { - let state = state.lock().unwrap(); - state - .preferences - .last_server - .filter(|index| *index < state.preferences.servers.len()) - .or_else(|| (state.preferences.servers.len() == 1).then_some(0)) - }; - if let Some(index) = startup_server { - open_server(&ui, state.clone(), index); - open_home(&ui, state); - } - - ui.run()?; - Ok(()) -} - -fn wire_callbacks(ui: &AppWindow, state: Arc>) { - ui.on_select_tab({ - let weak = ui.as_weak(); - let state = state.clone(); - move |tab| { - let Some(ui) = weak.upgrade() else { return }; - let changed = ui.get_tab() != tab; - if changed { - state.lock().unwrap().opened_from_home = false; - } - ui.set_error("".into()); - ui.set_tab(tab.clone()); - if changed { - match tab.as_str() { - "home" => open_home(&ui, state.clone()), - "pulls" => open_pulls(&ui, state.clone()), - "issues" | "repositories" => { - ui.set_page(if state.lock().unwrap().active_server.is_some() { - "repositories".into() - } else { - "servers".into() - }); - } - _ => {} - } - } - } - }); - - ui.on_refresh_home({ - let weak = ui.as_weak(); - let state = state.clone(); - move || { - if let Some(ui) = weak.upgrade() { - open_home(&ui, state.clone()); - } - } - }); - - ui.on_refresh_repositories({ - let weak = ui.as_weak(); - let state = state.clone(); - move || { - let server = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - }; - if let (Some(ui), Some(server)) = (weak.upgrade(), server) { - fetch_repositories(&ui, state.clone(), server, true); - } - } - }); - - ui.on_refresh_issues({ - let weak = ui.as_weak(); - let state = state.clone(); - move || { - let target = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .zip(state.active_repository.clone()) - }; - if let (Some(ui), Some((server, (owner, repository)))) = (weak.upgrade(), target) { - fetch_issues(&ui, state.clone(), server, owner, repository, true); - } - } - }); - - ui.on_refresh_issue({ - let weak = ui.as_weak(); - let state = state.clone(); - move || { - let target = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .zip(state.active_repository.clone()) - .zip(state.active_issue) - }; - if let (Some(ui), Some(((server, (owner, repository)), number))) = - (weak.upgrade(), target) - { - fetch_issue(&ui, server, owner, repository, number, true); - } - } - }); - - ui.on_refresh_pulls({ - let weak = ui.as_weak(); - let state = state.clone(); - move || { - if let Some(ui) = weak.upgrade() { - open_pulls_with_refresh(&ui, state.clone(), true); - } - } - }); - - ui.on_refresh_pull({ - let weak = ui.as_weak(); - let state = state.clone(); - move || { - let target = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .zip(state.active_pull.clone()) - }; - if let (Some(ui), Some((server, (owner, repository, number)))) = - (weak.upgrade(), target) - { - fetch_pull(&ui, server, owner, repository, number, true); - } - } - }); - - ui.on_refresh_commits({ - let weak = ui.as_weak(); - let state = state.clone(); - move || { - let target = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .zip(state.active_repository.clone()) - }; - if let (Some(ui), Some((server, (owner, repository)))) = (weak.upgrade(), target) { - fetch_commits(&ui, state.clone(), server, owner, repository, true); - } - } - }); - - ui.on_choose_icon_style({ - let weak = ui.as_weak(); - let state = state.clone(); - move |index| { - let Some(style) = IconStyle::from_index(index) else { - return; - }; - let result = { - let mut state = state.lock().unwrap(); - let previous = state.preferences.icon_style; - state.preferences.icon_style = style; - save_preferences(&state.preferences).inspect_err(|_| { - state.preferences.icon_style = previous; - }) - }; - if let Some(ui) = weak.upgrade() { - match result { - Ok(()) => ui.global::().set_style(index), - Err(error) => ui.set_error(error.into()), - } - } - } - }); - - ui.on_choose_appearance({ - let weak = ui.as_weak(); - let state = state.clone(); - move |index| { - let Some(appearance) = AppearanceMode::from_index(index) else { - return; - }; - let result = { - let mut state = state.lock().unwrap(); - let previous = state.preferences.appearance; - state.preferences.appearance = appearance; - save_preferences(&state.preferences).inspect_err(|_| { - state.preferences.appearance = previous; - }) - }; - if let Some(ui) = weak.upgrade() { - match result { - Ok(()) => ui.global::().set_mode(index), - Err(error) => ui.set_error(error.into()), - } - } - } - }); - - ui.on_show_add_server({ - let weak = ui.as_weak(); - move || { - if let Some(ui) = weak.upgrade() { - ui.set_error("".into()); - ui.set_page("add".into()); - } - } - }); - - ui.on_save_server({ - let weak = ui.as_weak(); - let state = state.clone(); - move |name, url, token| { - let server = match validate_server(name.as_str(), url.as_str(), token.as_str()) { - Ok(server) => server, - Err(error) => { - if let Some(ui) = weak.upgrade() { - ui.set_error(error.into()); - } - return; - } - }; - if let Some(ui) = weak.upgrade() { - ui.set_error("".into()); - ui.set_loading(true); - } - let weak = weak.clone(); - let state = state.clone(); - spawn(async move { - let result = match Client::new(&server.url, Some(&server.token)) { - Ok(client) => client - .current_user() - .await - .map_err(|error| error.to_string()), - Err(error) => Err(error.to_string()), - }; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - ui.set_loading(false); - match result { - Ok(_) => { - if let Err(error) = save_server_token(&server) { - ui.set_error(error.into()); - return; - } - let index = { - let mut state = state.lock().unwrap(); - state.preferences.servers.push(server); - let index = state.preferences.servers.len() - 1; - state.preferences.last_server = Some(index); - if let Err(error) = save_preferences(&state.preferences) { - ui.set_error(error.into()); - return; - } - refresh_servers(&ui, &state.preferences); - index - }; - open_server(&ui, state, index); - } - Err(error) => ui.set_error(error.into()), - } - }); - }); - } - }); - - ui.on_select_server({ - let weak = ui.as_weak(); - let state = state.clone(); - move |index| { - if let Some(ui) = weak.upgrade() { - open_server(&ui, state.clone(), index as usize); - } - } - }); - - ui.on_select_repository({ - let weak = ui.as_weak(); - let state = state.clone(); - move |owner, name| { - if let Some(ui) = weak.upgrade() { - if ui.get_tab().as_str() == "repositories" { - open_commit_history(&ui, state.clone(), owner.to_string(), name.to_string()); - } else { - open_repository(&ui, state.clone(), owner.to_string(), name.to_string()); - } - } - } - }); - - ui.on_toggle_favorite({ - let weak = ui.as_weak(); - let state = state.clone(); - move |owner, name| { - let mut state = state.lock().unwrap(); - let Some(server) = state - .active_server - .and_then(|i| state.preferences.servers.get(i)) - else { - return; - }; - let key = favorite_key(&server.url, owner.as_str(), name.as_str()); - if !state.preferences.favorites.remove(&key) { - state.preferences.favorites.insert(key); - } - if let Err(error) = save_preferences(&state.preferences) { - if let Some(ui) = weak.upgrade() { - ui.set_error(error.into()); - } - return; - } - if let Some(ui) = weak.upgrade() { - refresh_repositories(&ui, &state); - } - } - }); - - ui.on_select_issue({ - let weak = ui.as_weak(); - let state = state.clone(); - move |number| { - let target = { - let mut state = state.lock().unwrap(); - state.opened_from_home = false; - state.active_issue = Some(number as i64); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .zip(state.active_repository.clone()) - }; - if let (Some(ui), Some((server, (owner, repository)))) = (weak.upgrade(), target) { - fetch_issue(&ui, server, owner, repository, number as i64, false); - } - } - }); - - ui.on_select_pull({ - let weak = ui.as_weak(); - let state = state.clone(); - move |owner, repository, number| { - let server = { - let mut state = state.lock().unwrap(); - state.opened_from_home = false; - state.active_pull = - Some((owner.to_string(), repository.to_string(), number as i64)); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - }; - if let (Some(ui), Some(server)) = (weak.upgrade(), server) { - fetch_pull( - &ui, - server, - owner.to_string(), - repository.to_string(), - number as i64, - false, - ); - } - } - }); - - ui.on_select_commit({ - let weak = ui.as_weak(); - let state = state.clone(); - move |sha| { - let target = { - let mut state = state.lock().unwrap(); - state.opened_from_home = false; - state.active_commit = Some(sha.to_string()); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .zip(state.active_repository.clone()) - }; - if let (Some(ui), Some((server, (owner, repository)))) = (weak.upgrade(), target) { - fetch_commit(&ui, server, owner, repository, sha.to_string()); - } - } - }); - - ui.on_select_branch({ - let weak = ui.as_weak(); - let state = state.clone(); - move |selection| { - let target = { - let mut state = state.lock().unwrap(); - let branch = (selection.as_str() != "All").then(|| selection.to_string()); - if branch - .as_ref() - .is_some_and(|branch| !state.branches.contains(branch)) - { - return; - } - state.active_branch = branch; - state.commits.clear(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .zip(state.active_repository.clone()) - }; - if let (Some(ui), Some((server, (owner, repository)))) = (weak.upgrade(), target) { - ui.set_commits(empty_model()); - fetch_commits(&ui, state.clone(), server, owner, repository, false); - } - } - }); - - ui.on_select_file({ - let weak = ui.as_weak(); - let state = state.clone(); - move |path| { - let target = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .zip(state.active_repository.clone()) - .zip(state.active_commit.clone()) - }; - if let (Some(ui), Some(((server, (owner, repository)), sha))) = (weak.upgrade(), target) - { - fetch_file_diff(&ui, server, owner, repository, sha, path.to_string()); - } - } - }); - - ui.on_select_pull_file({ - let weak = ui.as_weak(); - let state = state.clone(); - move |path| { - let target = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .zip(state.active_pull.clone()) - }; - if let (Some(ui), Some((server, (owner, repository, number)))) = - (weak.upgrade(), target) - { - fetch_pull_file_diff(&ui, server, owner, repository, number, path.to_string()); - } - } - }); - - ui.on_select_activity({ - let weak = ui.as_weak(); - let state = state.clone(); - move |target, owner, repository, number, sha| { - let server = { - let mut state = state.lock().unwrap(); - state.opened_from_home = true; - state.active_repository = Some((owner.to_string(), repository.to_string())); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - }; - let (Some(ui), Some(server)) = (weak.upgrade(), server) else { - return; - }; - match target.as_str() { - "repository" => { - ui.set_tab("repositories".into()); - open_commit_history( - &ui, - state.clone(), - owner.to_string(), - repository.to_string(), - ); - } - "issue" => { - state.lock().unwrap().active_issue = Some(number as i64); - ui.set_tab("issues".into()); - fetch_issue( - &ui, - server, - owner.to_string(), - repository.to_string(), - number as i64, - false, - ); - } - "pull" => { - state.lock().unwrap().active_pull = - Some((owner.to_string(), repository.to_string(), number as i64)); - ui.set_tab("pulls".into()); - fetch_pull( - &ui, - server, - owner.to_string(), - repository.to_string(), - number as i64, - false, - ); - } - "commit" => { - state.lock().unwrap().active_commit = Some(sha.to_string()); - ui.set_tab("repositories".into()); - ui.set_repository_title(repository.clone()); - fetch_commit( - &ui, - server, - owner.to_string(), - repository.to_string(), - sha.to_string(), - ); - } - _ => {} - } - } - }); - - ui.on_choose_filter({ - let weak = ui.as_weak(); - let state = state.clone(); - move |status| { - if !matches!(status.as_str(), "open" | "closed") { - return; - } - let Some(ui) = weak.upgrade() else { return }; - { - let mut state = state.lock().unwrap(); - if ui.get_tab().as_str() == "pulls" { - state.preferences.pull_status = status.to_string(); - ui.set_pull_filter(status.clone()); - } else { - state.preferences.issue_status = status.to_string(); - ui.set_issue_filter(status.clone()); - } - if let Err(error) = save_preferences(&state.preferences) { - ui.set_error(error.into()); - return; - } - } - if ui.get_tab().as_str() == "pulls" { - open_pulls(&ui, state.clone()); - } else { - let target = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .zip(state.active_repository.clone()) - }; - if let Some((server, (owner, repository))) = target { - fetch_issues(&ui, state.clone(), server, owner, repository, false); - } - } - } - }); - - ui.on_back({ - let weak = ui.as_weak(); - let state = state.clone(); - move || { - let Some(ui) = weak.upgrade() else { return }; - ui.set_error("".into()); - if state.lock().unwrap().opened_from_home - && matches!(ui.get_page().as_str(), "issue" | "pull" | "files") - { - state.lock().unwrap().opened_from_home = false; - ui.set_tab("home".into()); - open_home(&ui, state.clone()); - return; - } - ui.set_page(match (ui.get_tab().as_str(), ui.get_page().as_str()) { - ("pulls", "pull") => "pulls".into(), - ("pulls", "diff") => "pull".into(), - ("repositories", "diff") => "files".into(), - ("repositories", "files") => "commits".into(), - ("repositories", "commits") => "repositories".into(), - (_, "issue") => "issues".into(), - (_, "issues") => "repositories".into(), - (_, "repositories" | "add") => "servers".into(), - _ => "servers".into(), - }); - } - }); -} - -fn open_server(ui: &AppWindow, state: Arc>, index: usize) { - let server = { - let mut state = state.lock().unwrap(); - let Some(server) = state.preferences.servers.get(index).cloned() else { - return; - }; - state.active_server = Some(index); - state.preferences.last_server = Some(index); - state.repositories.clear(); - if let Err(error) = save_preferences(&state.preferences) { - ui.set_error(error.into()); - } - server - }; - ui.set_has_active_server(true); - ui.set_home_server(server.name.clone().into()); - ui.set_server_title(server.name.clone().into()); - ui.set_repositories(empty_model()); - ui.set_page("repositories".into()); - ui.set_error("".into()); - fetch_repositories(ui, state, server, false); -} - -fn fetch_repositories(ui: &AppWindow, state: Arc>, server: Server, refreshing: bool) { - if refreshing { - ui.set_refreshing(true); - } else { - ui.set_loading(true); - } - let weak = ui.as_weak(); - spawn(async move { - let result = load_repositories(&server).await; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - if refreshing { - ui.set_refreshing(false); - } else { - ui.set_loading(false); - } - match result { - Ok(repositories) => { - let mut state = state.lock().unwrap(); - state.repositories = repositories; - refresh_repositories(&ui, &state); - } - Err(error) => ui.set_error(error.into()), - } - }); - }); -} - -fn open_home(ui: &AppWindow, state: Arc>) { - let server = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - }; - let Some(server) = server else { - ui.set_has_active_server(false); - ui.set_activities(empty_model()); - ui.set_heat_cells(empty_model()); - ui.set_contribution_count(0); - return; - }; - - ui.set_has_active_server(true); - ui.set_home_server(server.name.clone().into()); - ui.set_home_loading(true); - let server_url = server.url.clone(); - let weak = ui.as_weak(); - spawn(async move { - let result = load_home(&server).await; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - let is_current = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .is_some_and(|server| server.url == server_url) - }; - if !is_current { - return; - } - ui.set_home_loading(false); - match result { - Ok(home) => refresh_home(&ui, home), - Err(error) => ui.set_error(error.into()), - } - }); - }); -} - -fn open_repository(ui: &AppWindow, state: Arc>, owner: String, name: String) { - let server = { - let mut state = state.lock().unwrap(); - state.active_repository = Some((owner.clone(), name.clone())); - state.issues.clear(); - state - .active_server - .and_then(|i| state.preferences.servers.get(i)) - .cloned() - }; - let Some(server) = server else { return }; - ui.set_repository_title(name.clone().into()); - ui.set_issues(empty_model()); - ui.set_page("issues".into()); - ui.set_error("".into()); - fetch_issues(ui, state, server, owner, name, false); -} - -fn fetch_issues( - ui: &AppWindow, - state: Arc>, - server: Server, - owner: String, - repository: String, - refreshing: bool, -) { - if refreshing { - ui.set_refreshing(true); - } else { - ui.set_loading(true); - } - let weak = ui.as_weak(); - let status = state.lock().unwrap().preferences.issue_status.clone(); - spawn(async move { - let result = load_issues(&server, &owner, &repository, &status).await; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - if refreshing { - ui.set_refreshing(false); - } else { - ui.set_loading(false); - } - match result { - Ok(issues) => { - let mut state = state.lock().unwrap(); - state.issues = issues; - refresh_issues(&ui, &state.issues); - } - Err(error) => ui.set_error(error.into()), - } - }); - }); -} - -fn fetch_issue( - ui: &AppWindow, - server: Server, - owner: String, - repository: String, - number: i64, - refreshing: bool, -) { - if refreshing { - ui.set_refreshing(true); - } else { - ui.set_loading(true); - } - let weak = ui.as_weak(); - spawn(async move { - let result = async { - let client = - Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; - let configuration = client.configuration(); - let (issue, comments) = tokio::join!( - apis::issue_api::issue_get_issue(&configuration, &owner, &repository, number), - apis::issue_api::issue_get_comments( - &configuration, - &owner, - &repository, - number, - None, - None, - ), - ); - Ok::<_, String>(IssueDetails { - issue: issue.map_err(|error| error.to_string())?, - comments: comments.map_err(|error| error.to_string())?, - }) - } - .await; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - if refreshing { - ui.set_refreshing(false); - } else { - ui.set_loading(false); - } - match result { - Ok(details) => show_issue(&ui, &details), - Err(error) => ui.set_error(error.into()), - } - }); - }); -} - -fn open_pulls(ui: &AppWindow, state: Arc>) { - open_pulls_with_refresh(ui, state, false); -} - -fn open_pulls_with_refresh(ui: &AppWindow, state: Arc>, refreshing: bool) { - let target = { - let state = state.lock().unwrap(); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - .map(|server| (server, state.preferences.pull_status.clone())) - }; - let Some((server, status)) = target else { - ui.set_has_active_server(false); - ui.set_pulls(empty_model()); - return; - }; - ui.set_has_active_server(true); - ui.set_page("pulls".into()); - if refreshing { - ui.set_refreshing(true); - } else { - ui.set_loading(true); - } - let weak = ui.as_weak(); - spawn(async move { - let result = load_pulls(&server, &status).await; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - if refreshing { - ui.set_refreshing(false); - } else { - ui.set_loading(false); - } - match result { - Ok(pulls) => { - state.lock().unwrap().pulls = pulls; - refresh_pulls(&ui, &state.lock().unwrap().pulls); - } - Err(error) => ui.set_error(error.into()), - } - }); - }); -} - -fn fetch_pull( - ui: &AppWindow, - server: Server, - owner: String, - repository: String, - number: i64, - refreshing: bool, -) { - if refreshing { - ui.set_refreshing(true); - } else { - ui.set_loading(true); - } - let weak = ui.as_weak(); - spawn(async move { - let result = async { - let client = - Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; - let configuration = client.configuration(); - let (pull, comments, files) = tokio::join!( - apis::repository_api::repo_get_pull_request( - &configuration, - &owner, - &repository, - number, - ), - apis::issue_api::issue_get_comments( - &configuration, - &owner, - &repository, - number, - None, - None, - ), - load_pull_files(&configuration, &owner, &repository, number,), - ); - Ok::<_, String>(PullDetails { - pull: pull.map_err(|error| error.to_string())?, - comments: comments.map_err(|error| error.to_string())?, - files: files.map_err(|error| error.to_string())?, - }) - } - .await; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - if refreshing { - ui.set_refreshing(false); - } else { - ui.set_loading(false); - } - match result { - Ok(details) => show_pull(&ui, &details), - Err(error) => ui.set_error(error.into()), - } - }); - }); -} - -fn open_commit_history( - ui: &AppWindow, - state: Arc>, - owner: String, - repository: String, -) { - let server = { - let mut state = state.lock().unwrap(); - let default_branch = state - .repositories - .iter() - .find(|candidate| candidate.owner == owner && candidate.name == repository) - .map(|candidate| candidate.default_branch.clone()) - .unwrap_or_else(|| "main".into()); - state.active_repository = Some((owner.clone(), repository.clone())); - state.active_branch = Some(default_branch.clone()); - state.branches.clear(); - state.commits.clear(); - ui.set_branch_choices(model(vec!["All".into(), default_branch.into()])); - ui.set_branch_index(1); - state - .active_server - .and_then(|index| state.preferences.servers.get(index)) - .cloned() - }; - let Some(server) = server else { return }; - ui.set_repository_title(repository.clone().into()); - ui.set_commits(empty_model()); - ui.set_page("commits".into()); - ui.set_error("".into()); - fetch_commits(ui, state, server, owner, repository, false); -} - -fn fetch_commits( - ui: &AppWindow, - state: Arc>, - server: Server, - owner: String, - repository: String, - refreshing: bool, -) { - if refreshing { - ui.set_refreshing(true); - } else { - ui.set_loading(true); - } - let weak = ui.as_weak(); - let (active_branch, branches) = { - let state = state.lock().unwrap(); - (state.active_branch.clone(), state.branches.clone()) - }; - spawn(async move { - let requested_branch = active_branch.clone(); - let result = async { - let default_branch = active_branch - .as_deref() - .or(branches.first().map(String::as_str)) - .unwrap_or("main"); - let branches = if branches.is_empty() { - load_branches(&server, &owner, &repository, default_branch).await? - } else { - branches - }; - let commits = match active_branch.as_deref() { - Some(branch) => load_branch_commits(&server, &owner, &repository, branch).await?, - None => load_all_commits(&server, &owner, &repository, &branches).await?, - }; - Ok::<_, String>((requested_branch, branches, commits)) - } - .await; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - if refreshing { - ui.set_refreshing(false); - } else { - ui.set_loading(false); - } - match result { - Ok((requested_branch, branches, commits)) => { - let mut state = state.lock().unwrap(); - if state.active_branch != requested_branch { - return; - } - state.branches = branches; - state.commits = commits; - let mut choices: Vec = vec!["All".into()]; - choices.extend(state.branches.iter().map(Into::into)); - ui.set_branch_choices(model(choices)); - ui.set_branch_index( - state - .active_branch - .as_ref() - .and_then(|active| { - state.branches.iter().position(|branch| branch == active) - }) - .map_or(0, |index| index as i32 + 1), - ); - let lane_count = if state.active_branch.is_none() { - state - .commits - .iter() - .flat_map(|commit| { - commit - .top_lanes - .iter() - .chain(commit.bottom_lanes.iter()) - .chain(commit.connections.iter()) - .chain(commit.node_lane.iter()) - }) - .max() - .map_or(0, |lane| lane + 1) - } else { - 0 - }; - refresh_commits(&ui, &state.commits, lane_count); - } - Err(error) => ui.set_error(error.into()), - } - }); - }); -} - -fn fetch_commit(ui: &AppWindow, server: Server, owner: String, repository: String, sha: String) { - ui.set_loading(true); - let weak = ui.as_weak(); - spawn(async move { - let result = async { - let client = - Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; - apis::repository_api::repo_get_single_commit( - &client.configuration(), - &owner, - &repository, - &sha, - Some(true), - None, - Some(true), - ) - .await - .map_err(|error| error.to_string()) - } - .await; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - ui.set_loading(false); - match result { - Ok(commit) => { - ui.set_files(model( - commit - .files - .unwrap_or_default() - .into_iter() - .filter_map(|file| { - Some(FileRow { - path: file.filename?.into(), - status: file.status.unwrap_or_else(|| "modified".into()).into(), - }) - }) - .collect(), - )); - ui.set_page("files".into()); - } - Err(error) => ui.set_error(error.into()), - } - }); - }); -} - -fn fetch_file_diff( - ui: &AppWindow, - server: Server, - owner: String, - repository: String, - sha: String, - path: String, -) { - ui.set_loading(true); - let weak = ui.as_weak(); - spawn(async move { - let result = async { - let client = - Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; - apis::repository_api::repo_download_commit_diff_or_patch( - &client.configuration(), - &owner, - &repository, - &sha, - "diff", - ) - .await - .map(|text| diff::parse_file(&text, &path)) - .map_err(|error| error.to_string()) - } - .await; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - ui.set_loading(false); - match result { - Ok(diff) => show_file_diff(&ui, &path, diff), - Err(error) => ui.set_error(error.into()), - } - }); - }); -} - -fn fetch_pull_file_diff( - ui: &AppWindow, - server: Server, - owner: String, - repository: String, - number: i64, - path: String, -) { - ui.set_loading(true); - let weak = ui.as_weak(); - spawn(async move { - let result = async { - let client = - Client::new(&server.url, Some(&server.token)).map_err(|error| error.to_string())?; - apis::repository_api::repo_download_pull_diff_or_patch( - &client.configuration(), - &owner, - &repository, - number, - "diff", - Some(false), - ) - .await - .map(|text| diff::parse_file(&text, &path)) - .map_err(|error| error.to_string()) - } - .await; - post(move || { - let Some(ui) = weak.upgrade() else { return }; - ui.set_loading(false); - match result { - Ok(diff) => show_file_diff(&ui, &path, diff), - Err(error) => ui.set_error(error.into()), - } - }); - }); -} - -fn show_file_diff(ui: &AppWindow, path: &str, diff: diff::Parsed) { - ui.set_detail_title(path.rsplit('/').next().unwrap_or(path).into()); - ui.set_diff_columns(diff.columns.min(i32::MAX as usize) as i32); - ui.set_diff_lines(model( - diff.lines - .into_iter() - .map(|line| DiffLine { - old_number: line.old_number.into(), - new_number: line.new_number.into(), - text: line.text.into(), - kind: line.kind.into(), - }) - .collect(), - )); - ui.set_page("diff".into()); -} - -fn runtime() -> &'static tokio::runtime::Runtime { - RUNTIME.get_or_init(|| tokio::runtime::Runtime::new().expect("Tokio runtime")) -} - -fn spawn(future: impl Future + Send + 'static) { - runtime().spawn(future); -} - -fn post(callback: impl FnOnce() + Send + 'static) { - let _ = slint::invoke_from_event_loop(callback); -} - -fn model(rows: Vec) -> ModelRc { - ModelRc::new(VecModel::from(rows)) -} - -fn empty_model() -> ModelRc { - model(Vec::new()) -} diff --git a/crates/app/src/markdown.rs b/crates/app/src/markdown.rs deleted file mode 100644 index 381d0a0..0000000 --- a/crates/app/src/markdown.rs +++ /dev/null @@ -1,312 +0,0 @@ -use pulldown_cmark::{Event, HeadingLevel, Options, Parser, Tag, TagEnd}; -use slint::StyledText; - -use crate::ui::MarkdownBlock; - -struct Draft { - kind: &'static str, - source: String, - plain: bool, -} - -#[derive(Default)] -struct Renderer { - blocks: Vec, - current: Option, - lists: Vec>, - links: Vec<(String, bool, bool)>, - quote_depth: usize, -} - -pub fn render(source: &str) -> Vec { - let mut renderer = Renderer::default(); - for event in Parser::new_ext(source, Options::all()) { - renderer.event(event); - } - renderer.finish(); - renderer.blocks -} - -impl Renderer { - fn event(&mut self, event: Event<'_>) { - match event { - Event::Start(tag) => self.start(tag), - Event::End(tag) => self.end(tag), - Event::Text(text) => self.text(&text), - Event::Code(text) | Event::InlineMath(text) | Event::DisplayMath(text) => { - if self.is_plain() { - self.raw(&text); - } else { - self.raw(&code_span(&text)); - } - } - Event::Html(html) | Event::InlineHtml(html) => self.text(&html), - Event::FootnoteReference(label) => self.text(&format!("[{label}]")), - Event::SoftBreak | Event::HardBreak => self.raw("\n"), - Event::Rule => { - self.begin("rule", true); - self.finish(); - } - Event::TaskListMarker(checked) => self.raw(if checked { "☑ " } else { "☐ " }), - } - } - - fn start(&mut self, tag: Tag<'_>) { - match tag { - Tag::Paragraph => { - if self.current.is_none() { - self.begin( - if self.quote_depth > 0 { - "quote" - } else { - "paragraph" - }, - false, - ); - } else if self - .current - .as_ref() - .is_some_and(|draft| !draft.source.is_empty() && !draft.source.ends_with(' ')) - { - self.raw("\n"); - } - } - Tag::Heading { level, .. } => { - self.begin(heading_kind(level), false); - self.raw("**"); - } - Tag::BlockQuote(_) => { - self.quote_depth += 1; - if self.current.is_none() { - self.begin("quote", false); - } else { - self.raw("› "); - } - } - Tag::CodeBlock(_) => self.begin("code", true), - Tag::HtmlBlock | Tag::MetadataBlock(_) => self.begin("code", true), - Tag::List(start) => self.lists.push(start), - Tag::Item => { - self.begin("list", false); - let indent = "\u{00a0}\u{00a0}".repeat(self.lists.len().saturating_sub(1)); - self.raw(&indent); - let prefix = match self.lists.last_mut() { - Some(Some(number)) => { - let prefix = format!("{number}\\. "); - *number += 1; - prefix - } - _ => "• ".into(), - }; - self.raw(&prefix); - } - Tag::FootnoteDefinition(label) => { - self.begin("footnote", false); - self.text(&format!("[{label}] ")); - } - Tag::DefinitionList => {} - Tag::DefinitionListTitle => { - self.begin("paragraph", false); - self.raw("**"); - } - Tag::DefinitionListDefinition => { - self.begin("paragraph", false); - self.raw("↳ "); - } - Tag::Table(_) => self.begin("table", true), - Tag::TableHead | Tag::TableRow | Tag::TableCell => {} - Tag::Emphasis => self.marker("*"), - Tag::Strong => self.marker("**"), - Tag::Strikethrough => self.marker("~~"), - Tag::Superscript => self.raw("\\^"), - Tag::Subscript => self.raw("\\~"), - Tag::Link { dest_url, .. } => self.start_link(dest_url.into_string(), false), - Tag::Image { dest_url, .. } => self.start_link(dest_url.into_string(), true), - } - } - - fn end(&mut self, tag: TagEnd) { - match tag { - TagEnd::Paragraph => { - if self.quote_depth == 0 - && !self - .current - .as_ref() - .is_some_and(|draft| draft.kind == "list") - { - self.finish(); - } - } - TagEnd::Heading(_) => { - self.raw("**"); - self.finish(); - } - TagEnd::BlockQuote(_) => { - self.quote_depth = self.quote_depth.saturating_sub(1); - if self.quote_depth == 0 { - self.finish(); - } - } - TagEnd::CodeBlock | TagEnd::HtmlBlock | TagEnd::MetadataBlock(_) => self.finish(), - TagEnd::List(_) => { - self.lists.pop(); - } - TagEnd::Item | TagEnd::FootnoteDefinition => self.finish(), - TagEnd::DefinitionList => {} - TagEnd::DefinitionListTitle => { - self.raw("**"); - self.finish(); - } - TagEnd::DefinitionListDefinition => self.finish(), - TagEnd::Table => self.finish(), - TagEnd::TableHead => self.raw("────────\n"), - TagEnd::TableRow => { - if let Some(draft) = self.current.as_mut() - && draft.source.ends_with(" │ ") - { - draft.source.truncate(draft.source.len() - " │ ".len()); - } - self.raw("\n"); - } - TagEnd::TableCell => self.raw(" │ "), - TagEnd::Emphasis => self.marker("*"), - TagEnd::Strong => self.marker("**"), - TagEnd::Strikethrough => self.marker("~~"), - TagEnd::Superscript => self.raw("\\^"), - TagEnd::Subscript => self.raw("\\~"), - TagEnd::Link | TagEnd::Image => self.end_link(), - } - } - - fn begin(&mut self, kind: &'static str, plain: bool) { - self.finish(); - self.current = Some(Draft { - kind, - source: String::new(), - plain, - }); - } - - fn finish(&mut self) { - let Some(draft) = self.current.take() else { - return; - }; - let source = draft.source.trim_matches('\n'); - if source.is_empty() && draft.kind != "rule" { - return; - } - let text = if draft.plain { - StyledText::from_plain_text(source) - } else { - StyledText::from_markdown(source) - .unwrap_or_else(|_| StyledText::from_plain_text(source)) - }; - self.blocks.push(MarkdownBlock { - kind: draft.kind.into(), - text, - }); - } - - fn text(&mut self, text: &str) { - if self.current.is_none() { - self.begin( - if self.quote_depth > 0 { - "quote" - } else { - "paragraph" - }, - false, - ); - } - if self.is_plain() { - self.raw(text); - return; - } - let mut escaped = String::with_capacity(text.len()); - for character in text.chars() { - if character.is_ascii_punctuation() { - escaped.push('\\'); - } - escaped.push(character); - } - self.raw(&escaped); - } - - fn raw(&mut self, text: &str) { - if self.current.is_none() { - self.begin("paragraph", false); - } - self.current.as_mut().unwrap().source.push_str(text); - } - - fn marker(&mut self, marker: &str) { - if !self.is_plain() { - self.raw(marker); - } - } - - fn is_plain(&self) -> bool { - self.current.as_ref().is_some_and(|draft| draft.plain) - } - - fn start_link(&mut self, url: String, image: bool) { - let styled = !self.is_plain(); - if styled { - self.raw(if image { "[🖼 " } else { "[" }); - } else if image { - self.raw("🖼 "); - } - self.links.push((url, image, styled)); - } - - fn end_link(&mut self) { - let Some((url, _, styled)) = self.links.pop() else { - return; - }; - if styled { - self.raw(&format!("](<{}>)", url.replace('>', "%3E"))); - } else { - self.raw(&format!(" ({url})")); - } - } -} - -fn heading_kind(level: HeadingLevel) -> &'static str { - match level { - HeadingLevel::H1 => "heading1", - HeadingLevel::H2 => "heading2", - HeadingLevel::H3 => "heading3", - HeadingLevel::H4 => "heading4", - HeadingLevel::H5 => "heading5", - HeadingLevel::H6 => "heading6", - } -} - -fn code_span(text: &str) -> String { - let longest = text - .split(|character| character != '`') - .map(str::len) - .max() - .unwrap_or(0); - let fence = "`".repeat(longest + 1); - format!("{fence} {text} {fence}") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn renders_extended_markdown_as_safe_styled_blocks() { - let blocks = render( - "# Title\n\n- [x] **done** with [link](https://example.com)\n\n> quoted\n\n```rust\nlet x = 1;\n```\n\n| A | B |\n| - | - |\n| 1 | 2 |", - ); - let kinds: Vec<_> = blocks.iter().map(|block| block.kind.as_str()).collect(); - assert_eq!(kinds, ["heading1", "list", "quote", "code", "table"]); - assert_eq!( - blocks[0].text, - StyledText::from_markdown("**Title**").unwrap() - ); - assert_eq!(blocks[3].text, StyledText::from_plain_text("let x = 1;")); - } -} diff --git a/crates/app/src/presentation.rs b/crates/app/src/presentation.rs new file mode 100644 index 0000000..20de04f --- /dev/null +++ b/crates/app/src/presentation.rs @@ -0,0 +1,800 @@ +use gotcha_gitea::models; + +use crate::{ + activity, + domain::{ + HistoryCommit, HomeData, IssueDetails, MilestoneDetails, PullDetails, RepositoryData, + }, +}; + +#[derive(Clone, uniffi::Record)] +pub struct ServerRow { + pub name: String, + pub url: String, +} + +#[derive(Clone, uniffi::Record)] +pub struct RepositoryRow { + pub name: String, + pub owner: String, + pub description: String, + pub meta: String, + pub favorite: bool, +} + +#[derive(Clone, uniffi::Record)] +pub struct LabelRow { + pub name: String, + pub color: String, + pub light: bool, +} + +#[derive(Clone, uniffi::Record)] +pub struct IssueRow { + pub number: i64, + pub title: String, + pub summary: String, + pub meta: String, + pub milestone: String, + pub labels: Vec, +} + +#[derive(Clone, uniffi::Record)] +pub struct PullRow { + pub number: i64, + pub owner: String, + pub repository: String, + pub title: String, + pub summary: String, + pub meta: String, +} + +#[derive(Clone, uniffi::Record)] +pub struct CommentRow { + pub author: String, + pub body: String, + pub meta: String, +} + +#[derive(Clone, uniffi::Record)] +pub struct IssuePage { + pub title: String, + pub meta: String, + pub milestone: String, + pub body: String, + pub comments: Vec, +} + +#[derive(Clone, uniffi::Record)] +pub struct MilestoneRow { + pub id: i64, + pub title: String, + pub description: String, + pub meta: String, + pub open_issues: i64, + pub closed_issues: i64, +} + +#[derive(Clone, uniffi::Record)] +pub struct MilestonePage { + pub milestone: MilestoneRow, + pub issues: Vec, +} + +#[derive(Clone, uniffi::Record)] +pub struct PullPage { + pub title: String, + pub meta: String, + pub body: String, + pub files_ref: String, + pub files: Vec, + pub comments: Vec, +} + +#[derive(Clone, uniffi::Record)] +pub struct CommitRow { + pub sha: String, + pub title: String, + pub meta: String, + pub refs: String, + pub top_lanes: Vec, + pub bottom_lanes: Vec, + pub node_lane: Option, + pub connections: Vec, +} + +#[derive(Clone, uniffi::Record)] +pub struct CommitPage { + pub branches: Vec, + pub commits: Vec, + pub lane_count: u32, +} + +#[derive(Clone, uniffi::Record)] +pub struct FileRow { + pub path: String, + pub status: String, +} + +#[derive(Clone, uniffi::Record)] +pub struct DiffLine { + pub old_number: String, + pub new_number: String, + pub text: String, + pub kind: String, +} + +#[derive(Clone, uniffi::Record)] +pub struct DiffPage { + pub title: String, + pub columns: u32, + pub lines: Vec, +} + +#[derive(Clone, uniffi::Record)] +pub struct ActivityRow { + pub icon: String, + pub title: String, + pub detail: String, + pub meta: String, + pub target: String, + pub owner: String, + pub repository: String, + pub number: i64, + pub sha: String, +} + +#[derive(Clone, uniffi::Record)] +pub struct HeatCell { + pub week: u32, + pub day: u32, + pub level: u32, +} + +#[derive(Clone, uniffi::Record)] +pub struct HomePage { + pub server_name: String, + pub activities: Vec, + pub heat_cells: Vec, + pub contribution_count: i64, +} + +pub fn repository_rows( + repositories: &[RepositoryData], + is_favorite: impl Fn(&RepositoryData) -> bool, +) -> Vec { + let mut repositories = repositories.to_vec(); + repositories + .sort_by_key(|repository| (!is_favorite(repository), repository.name.to_lowercase())); + repositories + .into_iter() + .map(|repository| RepositoryRow { + favorite: is_favorite(&repository), + meta: format!( + "{} · {} open · {}", + repository.language, repository.open_issues, repository.updated + ), + name: repository.name, + owner: repository.owner, + description: repository.description, + }) + .collect() +} + +pub fn issue_rows(issues: &[models::Issue]) -> Vec { + issues + .iter() + .map(|issue| IssueRow { + number: issue.number.unwrap_or_default(), + title: issue + .title + .clone() + .unwrap_or_else(|| "Untitled issue".into()), + summary: issue + .body + .as_deref() + .map(summary) + .unwrap_or_else(|| "No description".into()), + meta: issue_meta(issue), + milestone: issue_milestone(issue), + labels: issue + .labels + .as_deref() + .unwrap_or_default() + .iter() + .filter_map(label_row) + .collect(), + }) + .collect() +} + +pub fn milestone_rows(milestones: &[models::Milestone]) -> Vec { + let mut milestones = milestones.to_vec(); + milestones.sort_by_key(|milestone| { + ( + milestone.state.as_deref() == Some("closed"), + milestone + .title + .as_deref() + .unwrap_or_default() + .to_lowercase(), + ) + }); + milestones.iter().map(milestone_row).collect() +} + +pub fn milestone_page(details: MilestoneDetails) -> MilestonePage { + MilestonePage { + milestone: milestone_row(&details.milestone), + issues: issue_rows(&details.issues), + } +} + +pub fn pull_rows(pulls: &[models::Issue]) -> Vec { + pulls + .iter() + .filter_map(|pull| { + let repository = pull.repository.as_ref()?; + let author = pull + .user + .as_ref() + .and_then(|user| user.login.as_deref()) + .unwrap_or("unknown"); + let kind = if pull + .pull_request + .as_ref() + .and_then(|pull| pull.draft) + .unwrap_or(false) + { + "Draft" + } else { + pull.state.as_deref().unwrap_or("unknown") + }; + Some(PullRow { + number: pull.number.unwrap_or_default(), + owner: repository.owner.clone()?, + repository: repository.name.clone()?, + title: pull + .title + .clone() + .unwrap_or_else(|| "Untitled pull request".into()), + summary: pull + .body + .as_deref() + .map(summary) + .filter(|body| !body.is_empty()) + .unwrap_or_else(|| "No description".into()), + meta: format!( + "{kind} · {author} · {} · {} comments", + compact_date(pull.updated_at.as_deref()), + pull.comments.unwrap_or_default() + ), + }) + }) + .collect() +} + +pub fn commit_page( + branches: Vec, + commits: &[HistoryCommit], + show_graph: bool, +) -> CommitPage { + let lane_count = if show_graph { + commits + .iter() + .flat_map(|commit| { + commit + .top_lanes + .iter() + .chain(commit.bottom_lanes.iter()) + .chain(commit.connections.iter()) + .chain(commit.node_lane.iter()) + }) + .max() + .map_or(0, |lane| lane + 1) + } else { + 0 + }; + let commits = commits + .iter() + .filter_map(|row| { + let commit = &row.commit; + let sha = commit.sha.as_deref()?; + let details = commit.commit.as_ref(); + let author = details + .and_then(|commit| commit.author.as_ref()) + .and_then(|author| author.name.as_deref()) + .or_else(|| { + commit + .author + .as_ref() + .and_then(|author| author.login.as_deref()) + }) + .unwrap_or("unknown"); + let date = details + .and_then(|commit| commit.author.as_ref()) + .and_then(|author| author.date.as_deref()) + .or(commit.created.as_deref()); + Some(CommitRow { + sha: sha.into(), + title: details + .and_then(|commit| commit.message.as_deref()) + .map(|message| message.lines().next().unwrap_or(message)) + .unwrap_or("Commit") + .into(), + meta: format!("{author} · {}", compact_date(date)), + refs: row.refs.join(" · "), + top_lanes: indices(&row.top_lanes), + bottom_lanes: indices(&row.bottom_lanes), + node_lane: row.node_lane.map(|lane| lane as u32), + connections: indices(&row.connections), + }) + }) + .collect(); + CommitPage { + branches, + commits, + lane_count: lane_count as u32, + } +} + +pub fn issue_page(details: IssueDetails) -> IssuePage { + IssuePage { + title: details + .issue + .title + .clone() + .unwrap_or_else(|| "Untitled issue".into()), + meta: issue_meta(&details.issue), + milestone: issue_milestone(&details.issue), + body: details + .issue + .body + .filter(|body| !body.is_empty()) + .unwrap_or_else(|| "No description provided.".into()), + comments: details.comments.iter().map(comment_row).collect(), + } +} + +pub fn pull_page(details: PullDetails) -> PullPage { + let pull = &details.pull; + let author = pull + .user + .as_ref() + .and_then(|user| user.login.as_deref()) + .unwrap_or("unknown"); + let head = pull + .head + .as_ref() + .and_then(|branch| branch.label.as_deref().or(branch.r#ref.as_deref())) + .unwrap_or("head"); + let base = pull + .base + .as_ref() + .and_then(|branch| branch.label.as_deref().or(branch.r#ref.as_deref())) + .unwrap_or("base"); + let state = if pull.merged.unwrap_or(false) { + "merged" + } else if pull.draft.unwrap_or(false) { + "draft" + } else { + pull.state.as_deref().unwrap_or("unknown") + }; + PullPage { + title: pull + .title + .clone() + .unwrap_or_else(|| "Untitled pull request".into()), + meta: format!( + "#{} · {state} · {author} · {head} → {base}\n{} files · +{} −{} · {} comments", + pull.number.unwrap_or_default(), + pull.changed_files.unwrap_or_default(), + pull.additions.unwrap_or_default(), + pull.deletions.unwrap_or_default(), + pull.comments.unwrap_or_default(), + ), + body: pull + .body + .clone() + .filter(|body| !body.is_empty()) + .unwrap_or_else(|| "No description provided.".into()), + files_ref: pull_files_ref(pull), + files: details.files.iter().filter_map(file_row).collect(), + comments: details.comments.iter().map(comment_row).collect(), + } +} + +pub fn file_rows(files: Vec) -> Vec { + files.iter().filter_map(file_row).collect() +} + +pub fn commit_file_rows(files: Vec) -> Vec { + files + .into_iter() + .filter_map(|file| { + Some(FileRow { + path: file.filename?, + status: file.status.unwrap_or_else(|| "modified".into()), + }) + }) + .collect() +} + +pub fn diff_page(path: &str, diff: crate::diff::Parsed) -> DiffPage { + DiffPage { + title: path.rsplit('/').next().unwrap_or(path).into(), + columns: diff.columns.min(u32::MAX as usize) as u32, + lines: diff + .lines + .into_iter() + .map(|line| DiffLine { + old_number: line.old_number, + new_number: line.new_number, + text: line.text, + kind: line.kind.into(), + }) + .collect(), + } +} + +pub fn home_page(server_name: String, home: HomeData) -> HomePage { + let (heat_cells, contribution_count) = heat_cells(&home.heatmap); + HomePage { + server_name, + activities: home.activities.iter().map(activity_row).collect(), + heat_cells, + contribution_count, + } +} + +fn activity_row(activity: &models::Activity) -> ActivityRow { + use models::activity::OpType; + + let repository = activity + .repo + .as_ref() + .and_then(|repo| repo.full_name.as_deref()) + .unwrap_or("repository"); + let branch = activity + .ref_name + .as_deref() + .and_then(|name| name.strip_prefix("refs/heads/").or(Some(name))) + .unwrap_or("default branch"); + let (icon, title) = match activity.op_type { + Some(OpType::CommitRepo) if activity.content.as_deref().unwrap_or("").is_empty() => ( + "branch-create", + format!("Created branch {branch} in {repository}"), + ), + Some(OpType::CommitRepo | OpType::MirrorSyncPush) => { + ("push", format!("Pushed to {branch} in {repository}")) + } + Some(OpType::CreateRepo) => ("repo-create", format!("Created {repository}")), + Some(OpType::RenameRepo) => ("repo-rename", format!("Renamed {repository}")), + Some(OpType::StarRepo) => ("repo-star", format!("Starred {repository}")), + Some(OpType::WatchRepo) => ("repo-watch", format!("Started watching {repository}")), + Some(OpType::CreateIssue) => ("issue-open", format!("Opened an issue in {repository}")), + Some(OpType::CloseIssue) => ("issue-close", format!("Closed an issue in {repository}")), + Some(OpType::ReopenIssue) => ("issue-reopen", format!("Reopened an issue in {repository}")), + Some(OpType::CommentIssue) => ( + "issue-comment", + format!("Commented on an issue in {repository}"), + ), + Some(OpType::CreatePullRequest) => ( + "pull-open", + format!("Opened a pull request in {repository}"), + ), + Some(OpType::MergePullRequest | OpType::AutoMergePullRequest) => ( + "pull-merge", + format!("Merged a pull request in {repository}"), + ), + Some(OpType::ClosePullRequest) => ( + "pull-close", + format!("Closed a pull request in {repository}"), + ), + Some(OpType::ReopenPullRequest) => ( + "pull-reopen", + format!("Reopened a pull request in {repository}"), + ), + Some(OpType::CommentPull) => ( + "pull-comment", + format!("Commented on a pull request in {repository}"), + ), + Some(OpType::ApprovePullRequest) => ( + "pull-approve", + format!("Approved a pull request in {repository}"), + ), + Some(OpType::RejectPullRequest) => { + ("pull-reject", format!("Requested changes in {repository}")) + } + Some(OpType::PushTag) => ("tag-push", format!("Pushed tag {branch} in {repository}")), + Some(OpType::DeleteTag) => ( + "tag-delete", + format!("Deleted tag {branch} in {repository}"), + ), + Some(OpType::DeleteBranch) => ( + "branch-delete", + format!("Deleted branch {branch} in {repository}"), + ), + Some(OpType::PublishRelease) => ("release", format!("Published a release in {repository}")), + Some(_) | None => ("update", format!("Updated {repository}")), + }; + let target = activity::target(activity); + let (target, owner, repository, number, sha) = match target { + Some(activity::Target::Repository { owner, repository }) => { + ("repository", owner, repository, 0, String::new()) + } + Some(activity::Target::Issue { + owner, + repository, + number, + }) => ("issue", owner, repository, number, String::new()), + Some(activity::Target::Pull { + owner, + repository, + number, + }) => ("pull", owner, repository, number, String::new()), + Some(activity::Target::Commit { + owner, + repository, + sha, + }) => ("commit", owner, repository, 0, sha), + None => ("", String::new(), String::new(), 0, String::new()), + }; + ActivityRow { + icon: icon.into(), + title, + detail: activity_detail(activity), + meta: compact_date(activity.created.as_deref()), + target: target.into(), + owner, + repository, + number, + sha, + } +} + +fn activity_detail(activity: &models::Activity) -> String { + let text = activity + .comment + .as_ref() + .and_then(|comment| comment.body.as_deref()) + .or(activity.content.as_deref()) + .unwrap_or(""); + if let Ok(payload) = serde_json::from_str::(text) { + let commits = payload.get("Len").and_then(|value| value.as_i64()); + let message = payload + .get("HeadCommit") + .and_then(|commit| commit.get("Message")) + .and_then(|message| message.as_str()) + .map(summary) + .unwrap_or_default(); + if !message.is_empty() { + return match commits { + Some(1) => format!("1 commit · {message}"), + Some(count) => format!("{count} commits · {message}"), + None => message, + }; + } + } + let text = summary(text); + if text.is_empty() { + "Server activity".into() + } else { + text + } +} + +fn heat_cells(data: &[models::UserHeatmapData]) -> (Vec, i64) { + const DAYS: i64 = 52 * 7; + let latest = data + .iter() + .filter_map(|entry| entry.timestamp) + .max() + .unwrap_or(0) + / 86_400; + let start = latest - DAYS + 1; + let mut counts = vec![0_i64; DAYS as usize]; + for entry in data { + let day = entry.timestamp.unwrap_or_default() / 86_400; + if (start..=latest).contains(&day) { + counts[(day - start) as usize] += entry.contributions.unwrap_or_default(); + } + } + let maximum = counts.iter().copied().max().unwrap_or_default(); + let contribution_count = counts.iter().sum(); + let cells = counts + .into_iter() + .enumerate() + .map(|(index, count)| HeatCell { + week: (index / 7) as u32, + day: (index % 7) as u32, + level: if count == 0 || maximum == 0 { + 0 + } else { + ((count * 4 + maximum - 1) / maximum).clamp(1, 4) as u32 + }, + }) + .collect(); + (cells, contribution_count) +} + +fn label_row(label: &models::Label) -> Option { + let name = label.name.as_deref()?.trim(); + let color = label.color.as_deref()?.trim().trim_start_matches('#'); + if name.is_empty() || color.len() != 6 { + return None; + } + let value = u32::from_str_radix(color, 16).ok()?; + let red = (value >> 16) & 0xff; + let green = (value >> 8) & 0xff; + let blue = value & 0xff; + Some(LabelRow { + name: name.into(), + color: format!("#{color}"), + light: red * 299 + green * 587 + blue * 114 > 150_000, + }) +} + +fn comment_row(comment: &models::Comment) -> CommentRow { + CommentRow { + author: comment + .user + .as_ref() + .and_then(|user| user.login.as_deref()) + .or(comment.original_author.as_deref()) + .unwrap_or("unknown") + .into(), + body: comment + .body + .clone() + .filter(|body| !body.is_empty()) + .unwrap_or_else(|| "No comment text.".into()), + meta: compact_date( + comment + .updated_at + .as_deref() + .or(comment.created_at.as_deref()), + ), + } +} + +fn file_row(file: &models::ChangedFile) -> Option { + Some(FileRow { + path: file.filename.clone()?, + status: file.status.clone().unwrap_or_else(|| "modified".into()), + }) +} + +fn pull_files_ref(pull: &models::PullRequest) -> String { + if pull.state.as_deref() == Some("open") { + let branch = pull + .head + .as_ref() + .and_then(|head| head.r#ref.as_deref().or(head.label.as_deref())) + .unwrap_or("head branch"); + format!("Files on {branch}") + } else if let Some(sha) = pull.merge_commit_sha.as_deref() { + format!("Files at merge commit {}", &sha[..sha.len().min(8)]) + } else { + "Files from pull request".into() + } +} + +fn issue_meta(issue: &models::Issue) -> String { + let author = issue + .user + .as_ref() + .and_then(|user| user.login.as_deref()) + .unwrap_or("unknown"); + format!( + "#{} · {} · updated {} · {} comments", + issue.number.unwrap_or_default(), + author, + compact_date(issue.updated_at.as_deref()), + issue.comments.unwrap_or_default() + ) +} + +fn issue_milestone(issue: &models::Issue) -> String { + issue + .milestone + .as_ref() + .and_then(|milestone| milestone.title.as_deref()) + .unwrap_or_default() + .into() +} + +fn milestone_row(milestone: &models::Milestone) -> MilestoneRow { + let open = milestone.open_issues.unwrap_or_default(); + let closed = milestone.closed_issues.unwrap_or_default(); + let due = milestone + .due_on + .as_deref() + .map(|date| format!("due {}", compact_date(Some(date)))) + .unwrap_or_else(|| "no due date".into()); + MilestoneRow { + id: milestone.id.unwrap_or_default(), + title: milestone + .title + .clone() + .unwrap_or_else(|| "Untitled milestone".into()), + description: milestone + .description + .clone() + .filter(|description| !description.is_empty()) + .unwrap_or_else(|| "No description".into()), + meta: format!( + "{} · {closed} of {} closed · {due}", + milestone.state.as_deref().unwrap_or("unknown"), + open + closed + ), + open_issues: open, + closed_issues: closed, + } +} + +fn summary(body: &str) -> String { + body.split_whitespace() + .take(24) + .collect::>() + .join(" ") +} + +fn indices(values: &[usize]) -> Vec { + values.iter().map(|value| *value as u32).collect() +} + +pub fn compact_date(date: Option<&str>) -> String { + date.and_then(|date| date.get(..10)) + .unwrap_or("unknown") + .to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn maps_heat_levels_and_labels() { + let heatmap = vec![ + models::UserHeatmapData { + timestamp: Some(100 * 86_400), + contributions: Some(1), + }, + models::UserHeatmapData { + timestamp: Some(101 * 86_400), + contributions: Some(4), + }, + ]; + let (cells, total) = heat_cells(&heatmap); + assert_eq!((cells.len(), total), (364, 5)); + assert_eq!((cells[362].level, cells[363].level), (1, 4)); + + let label = models::Label { + name: Some("bug".into()), + color: Some("d73a4a".into()), + ..Default::default() + }; + assert!(label_row(&label).is_some()); + assert!(label_row(&models::Label::default()).is_none()); + + let milestone = models::Milestone { + id: Some(1), + title: Some("Version 1".into()), + open_issues: Some(3), + closed_issues: Some(2), + state: Some("open".into()), + ..Default::default() + }; + let milestone_row = milestone_rows(std::slice::from_ref(&milestone)).remove(0); + assert_eq!( + (milestone_row.open_issues, milestone_row.closed_issues), + (3, 2) + ); + let issue = models::Issue { + milestone: Some(Box::new(milestone)), + ..Default::default() + }; + assert_eq!(issue_rows(&[issue])[0].milestone, "Version 1"); + } +} diff --git a/crates/app/src/storage.rs b/crates/app/src/storage.rs index 52a7d19..e4525c7 100644 --- a/crates/app/src/storage.rs +++ b/crates/app/src/storage.rs @@ -96,31 +96,10 @@ mod tests { "https://gitea.example.com|octo/demo" ); assert_eq!(Preferences::default().pull_status, "open"); - assert_eq!( - Preferences::default().icon_style, - crate::domain::IconStyle::Outline - ); assert_eq!( Preferences::default().appearance, crate::domain::AppearanceMode::Auto ); - assert_eq!( - serde_json::from_str::(r#"{"icon_style":"duotone"}"#) - .unwrap() - .icon_style, - crate::domain::IconStyle::Duotone - ); - assert_eq!( - serde_json::from_str::(r#"{"icon_style":"future-style"}"#) - .unwrap() - .icon_style, - crate::domain::IconStyle::Outline - ); - assert_eq!( - crate::domain::IconStyle::from_index(2), - Some(crate::domain::IconStyle::Bold) - ); - assert_eq!(crate::domain::IconStyle::from_index(3), None); assert_eq!( serde_json::from_str::(r#"{"appearance":"dark"}"#) .unwrap() diff --git a/crates/app/src/ui.rs b/crates/app/src/ui.rs deleted file mode 100644 index d174e0c..0000000 --- a/crates/app/src/ui.rs +++ /dev/null @@ -1 +0,0 @@ -slint::include_modules!(); diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs deleted file mode 100644 index 66e10da..0000000 --- a/crates/app/src/view.rs +++ /dev/null @@ -1,627 +0,0 @@ -use gotcha_gitea::models; -use slint::Color; - -use crate::{ - activity, - domain::{HistoryCommit, HomeData, IssueDetails, Preferences, PullDetails, State}, - markdown, model, - storage::favorite_key, - ui::*, -}; - -pub fn refresh_servers(ui: &AppWindow, preferences: &Preferences) { - ui.set_servers(model( - preferences - .servers - .iter() - .map(|server| ServerRow { - name: server.name.clone().into(), - url: server.url.clone().into(), - }) - .collect(), - )); -} - -pub fn refresh_repositories(ui: &AppWindow, state: &State) { - let server_url = state - .active_server - .and_then(|i| state.preferences.servers.get(i)) - .map(|server| server.url.as_str()) - .unwrap_or_default(); - let mut repositories = state.repositories.clone(); - repositories.sort_by_key(|repository| { - ( - !state.preferences.favorites.contains(&favorite_key( - server_url, - &repository.owner, - &repository.name, - )), - repository.name.to_lowercase(), - ) - }); - ui.set_repositories(model( - repositories - .into_iter() - .map(|repository| { - let favorite = state.preferences.favorites.contains(&favorite_key( - server_url, - &repository.owner, - &repository.name, - )); - RepositoryRow { - name: repository.name.into(), - owner: repository.owner.into(), - description: repository.description.into(), - meta: format!( - "{} · {} open · {}", - repository.language, repository.open_issues, repository.updated - ) - .into(), - favorite, - } - }) - .collect(), - )); - ui.set_repositories_scroll_request(ui.get_repositories_scroll_request().wrapping_add(1)); -} - -pub fn refresh_issues(ui: &AppWindow, issues: &[models::Issue]) { - ui.set_issues(model( - issues - .iter() - .map(|issue| IssueRow { - number: issue.number.unwrap_or_default() as i32, - title: issue - .title - .clone() - .unwrap_or_else(|| "Untitled issue".into()) - .into(), - summary: issue - .body - .as_deref() - .map(summary) - .unwrap_or_else(|| "No description".into()) - .into(), - meta: issue_meta(issue).into(), - labels: model( - issue - .labels - .as_deref() - .unwrap_or_default() - .iter() - .filter_map(label_row) - .collect(), - ), - }) - .collect(), - )); - ui.set_issues_scroll_request(ui.get_issues_scroll_request().wrapping_add(1)); -} - -pub fn refresh_pulls(ui: &AppWindow, pulls: &[models::Issue]) { - ui.set_pulls(model( - pulls - .iter() - .filter_map(|pull| { - let repository = pull.repository.as_ref()?; - let owner = repository.owner.clone()?; - let name = repository.name.clone()?; - let author = pull - .user - .as_ref() - .and_then(|user| user.login.as_deref()) - .unwrap_or("unknown"); - let kind = if pull - .pull_request - .as_ref() - .and_then(|pull| pull.draft) - .unwrap_or(false) - { - "Draft" - } else { - pull.state.as_deref().unwrap_or("unknown") - }; - Some(PullRow { - number: pull.number.unwrap_or_default() as i32, - owner: owner.into(), - repository: name.into(), - title: pull - .title - .clone() - .unwrap_or_else(|| "Untitled pull request".into()) - .into(), - summary: pull - .body - .as_deref() - .map(summary) - .filter(|body| !body.is_empty()) - .unwrap_or_else(|| "No description".into()) - .into(), - meta: format!( - "{kind} · {author} · {} · {} comments", - compact_date(pull.updated_at.as_deref()), - pull.comments.unwrap_or_default() - ) - .into(), - }) - }) - .collect(), - )); -} - -pub fn refresh_commits(ui: &AppWindow, commits: &[HistoryCommit], lane_count: usize) { - ui.set_commits(model( - commits - .iter() - .filter_map(|row| { - let commit = &row.commit; - let sha = commit.sha.as_deref()?; - let details = commit.commit.as_ref(); - let author = details - .and_then(|commit| commit.author.as_ref()) - .and_then(|author| author.name.as_deref()) - .or_else(|| { - commit - .author - .as_ref() - .and_then(|author| author.login.as_deref()) - }) - .unwrap_or("unknown"); - let date = details - .and_then(|commit| commit.author.as_ref()) - .and_then(|author| author.date.as_deref()) - .or(commit.created.as_deref()); - Some(CommitRow { - sha: sha.into(), - title: details - .and_then(|commit| commit.message.as_deref()) - .map(|message| message.lines().next().unwrap_or(message)) - .unwrap_or("Commit") - .into(), - meta: format!("{author} · {}", compact_date(date)).into(), - refs: row.refs.join(" · ").into(), - lanes: model( - (0..lane_count) - .map(|lane| GraphLane { - color: Color::from_hsva( - (lane as f32 * 137.508) % 360.0, - 0.78, - 0.78, - 1.0, - ), - top: row.top_lanes.contains(&lane), - bottom: row.bottom_lanes.contains(&lane), - node: row.node_lane == Some(lane), - connect_left: row.connections.contains(&lane), - }) - .collect(), - ), - }) - }) - .collect(), - )); -} - -fn label_row(label: &models::Label) -> Option { - let name = label.name.as_deref()?.trim(); - let hex = label.color.as_deref()?.trim().trim_start_matches('#'); - if name.is_empty() || hex.len() != 6 { - return None; - } - let value = u32::from_str_radix(hex, 16).ok()?; - let (red, green, blue) = ( - ((value >> 16) & 0xff) as u8, - ((value >> 8) & 0xff) as u8, - (value & 0xff) as u8, - ); - let light = red as u32 * 299 + green as u32 * 587 + blue as u32 * 114 > 150_000; - Some(LabelRow { - name: name.into(), - background: Color::from_rgb_u8(red, green, blue), - foreground: if light { - Color::from_rgb_u8(28, 28, 30) - } else { - Color::from_rgb_u8(255, 255, 255) - }, - }) -} - -pub fn refresh_home(ui: &AppWindow, home: HomeData) { - let (cells, contributions) = heat_cells(&home.heatmap); - ui.set_heat_cells(model(cells)); - ui.set_contribution_count(contributions.min(i32::MAX as i64) as i32); - ui.set_activities(model(home.activities.iter().map(activity_row).collect())); - ui.set_home_scroll_request(ui.get_home_scroll_request().wrapping_add(1)); -} - -fn activity_row(activity: &models::Activity) -> ActivityRow { - use models::activity::OpType; - - let repository = activity - .repo - .as_ref() - .and_then(|repo| repo.full_name.as_deref()) - .unwrap_or("repository"); - let branch = activity - .ref_name - .as_deref() - .and_then(|name| name.strip_prefix("refs/heads/").or(Some(name))) - .unwrap_or("default branch"); - let (icon, title) = match activity.op_type { - Some(OpType::CommitRepo) if activity.content.as_deref().unwrap_or("").is_empty() => ( - "branch-create", - format!("Created branch {branch} in {repository}"), - ), - Some(OpType::CommitRepo | OpType::MirrorSyncPush) => { - ("push", format!("Pushed to {branch} in {repository}")) - } - Some(OpType::CreateRepo) => ("repo-create", format!("Created {repository}")), - Some(OpType::RenameRepo) => ("repo-rename", format!("Renamed {repository}")), - Some(OpType::StarRepo) => ("repo-star", format!("Starred {repository}")), - Some(OpType::WatchRepo) => ("repo-watch", format!("Started watching {repository}")), - Some(OpType::CreateIssue) => ("issue-open", format!("Opened an issue in {repository}")), - Some(OpType::CloseIssue) => ("issue-close", format!("Closed an issue in {repository}")), - Some(OpType::ReopenIssue) => ("issue-reopen", format!("Reopened an issue in {repository}")), - Some(OpType::CommentIssue) => ( - "issue-comment", - format!("Commented on an issue in {repository}"), - ), - Some(OpType::CreatePullRequest) => ( - "pull-open", - format!("Opened a pull request in {repository}"), - ), - Some(OpType::MergePullRequest | OpType::AutoMergePullRequest) => ( - "pull-merge", - format!("Merged a pull request in {repository}"), - ), - Some(OpType::ClosePullRequest) => ( - "pull-close", - format!("Closed a pull request in {repository}"), - ), - Some(OpType::ReopenPullRequest) => ( - "pull-reopen", - format!("Reopened a pull request in {repository}"), - ), - Some(OpType::CommentPull) => ( - "pull-comment", - format!("Commented on a pull request in {repository}"), - ), - Some(OpType::ApprovePullRequest) => ( - "pull-approve", - format!("Approved a pull request in {repository}"), - ), - Some(OpType::RejectPullRequest) => { - ("pull-reject", format!("Requested changes in {repository}")) - } - Some(OpType::PushTag) => ("tag-push", format!("Pushed tag {branch} in {repository}")), - Some(OpType::DeleteTag) => ( - "tag-delete", - format!("Deleted tag {branch} in {repository}"), - ), - Some(OpType::DeleteBranch) => ( - "branch-delete", - format!("Deleted branch {branch} in {repository}"), - ), - Some(OpType::PublishRelease) => ("release", format!("Published a release in {repository}")), - Some(_) | None => ("update", format!("Updated {repository}")), - }; - let target = activity::target(activity); - let (target, owner, repository, number, sha) = match target { - Some(activity::Target::Repository { owner, repository }) => { - ("repository", owner, repository, 0, String::new()) - } - Some(activity::Target::Issue { - owner, - repository, - number, - }) => ("issue", owner, repository, number, String::new()), - Some(activity::Target::Pull { - owner, - repository, - number, - }) => ("pull", owner, repository, number, String::new()), - Some(activity::Target::Commit { - owner, - repository, - sha, - }) => ("commit", owner, repository, 0, sha), - None => ("", String::new(), String::new(), 0, String::new()), - }; - ActivityRow { - icon: icon.into(), - title: title.into(), - detail: activity_detail(activity).into(), - meta: compact_date(activity.created.as_deref()).into(), - target: target.into(), - owner: owner.into(), - repository: repository.into(), - number: number.min(i32::MAX as i64) as i32, - sha: sha.into(), - } -} - -fn activity_detail(activity: &models::Activity) -> String { - let text = activity - .comment - .as_ref() - .and_then(|comment| comment.body.as_deref()) - .or(activity.content.as_deref()) - .unwrap_or(""); - if let Ok(payload) = serde_json::from_str::(text) { - let commits = payload.get("Len").and_then(|value| value.as_i64()); - let message = payload - .get("HeadCommit") - .and_then(|commit| commit.get("Message")) - .and_then(|message| message.as_str()) - .map(summary) - .unwrap_or_default(); - if !message.is_empty() { - return match commits { - Some(1) => format!("1 commit · {message}"), - Some(count) => format!("{count} commits · {message}"), - None => message, - }; - } - } - let text = summary(text); - if text.is_empty() { - "Server activity".into() - } else { - text - } -} - -fn heat_cells(data: &[models::UserHeatmapData]) -> (Vec, i64) { - const DAYS: i64 = 52 * 7; - let latest = data - .iter() - .filter_map(|entry| entry.timestamp) - .max() - .unwrap_or(0) - / 86_400; - let start = latest - DAYS + 1; - let mut counts = vec![0_i64; DAYS as usize]; - for entry in data { - let day = entry.timestamp.unwrap_or_default() / 86_400; - if (start..=latest).contains(&day) { - counts[(day - start) as usize] += entry.contributions.unwrap_or_default(); - } - } - let maximum = counts.iter().copied().max().unwrap_or_default(); - let contributions = counts.iter().sum(); - let cells = counts - .into_iter() - .enumerate() - .map(|(index, count)| HeatCell { - week: (index / 7) as i32, - day: (index % 7) as i32, - level: if count == 0 || maximum == 0 { - 0 - } else { - ((count * 4 + maximum - 1) / maximum).clamp(1, 4) as i32 - }, - }) - .collect(); - (cells, contributions) -} - -pub fn show_issue(ui: &AppWindow, details: &IssueDetails) { - let issue = &details.issue; - ui.set_issue_title( - issue - .title - .clone() - .unwrap_or_else(|| "Untitled issue".into()) - .into(), - ); - ui.set_issue_meta(issue_meta(issue).into()); - ui.set_issue_body(model(markdown::render( - issue - .body - .as_deref() - .filter(|body| !body.is_empty()) - .unwrap_or("No description provided."), - ))); - ui.set_comments(model(details.comments.iter().map(comment_row).collect())); - ui.set_issue_scroll_request(ui.get_issue_scroll_request().wrapping_add(1)); - ui.set_page("issue".into()); -} - -pub fn show_pull(ui: &AppWindow, details: &PullDetails) { - let pull = &details.pull; - let author = pull - .user - .as_ref() - .and_then(|user| user.login.as_deref()) - .unwrap_or("unknown"); - let head = pull - .head - .as_ref() - .and_then(|branch| branch.label.as_deref().or(branch.r#ref.as_deref())) - .unwrap_or("head"); - let base = pull - .base - .as_ref() - .and_then(|branch| branch.label.as_deref().or(branch.r#ref.as_deref())) - .unwrap_or("base"); - let state = if pull.merged.unwrap_or(false) { - "merged" - } else if pull.draft.unwrap_or(false) { - "draft" - } else { - pull.state.as_deref().unwrap_or("unknown") - }; - ui.set_detail_title( - pull.title - .clone() - .unwrap_or_else(|| "Untitled pull request".into()) - .into(), - ); - ui.set_detail_meta( - format!( - "#{} · {state} · {author} · {head} → {base}\n{} files · +{} −{} · {} comments", - pull.number.unwrap_or_default(), - pull.changed_files.unwrap_or_default(), - pull.additions.unwrap_or_default(), - pull.deletions.unwrap_or_default(), - pull.comments.unwrap_or_default(), - ) - .into(), - ); - ui.set_detail_body(model(markdown::render( - pull.body - .as_deref() - .filter(|body| !body.is_empty()) - .unwrap_or("No description provided."), - ))); - ui.set_comments(model(details.comments.iter().map(comment_row).collect())); - ui.set_files(model( - details - .files - .iter() - .filter_map(|file| { - Some(FileRow { - path: file.filename.clone()?.into(), - status: file - .status - .clone() - .unwrap_or_else(|| "modified".into()) - .into(), - }) - }) - .collect(), - )); - ui.set_detail_files_ref(pull_files_ref(pull).into()); - ui.set_page("pull".into()); -} - -fn pull_files_ref(pull: &models::PullRequest) -> String { - if pull.state.as_deref() == Some("open") { - let branch = pull - .head - .as_ref() - .and_then(|head| head.r#ref.as_deref().or(head.label.as_deref())) - .unwrap_or("head branch"); - format!("Files on {branch}") - } else if let Some(sha) = pull.merge_commit_sha.as_deref() { - format!("Files at merge commit {}", &sha[..sha.len().min(8)]) - } else { - "Files from pull request".into() - } -} - -fn comment_row(comment: &models::Comment) -> CommentRow { - CommentRow { - author: comment - .user - .as_ref() - .and_then(|user| user.login.as_deref()) - .or(comment.original_author.as_deref()) - .unwrap_or("unknown") - .into(), - body: model(markdown::render( - comment - .body - .as_deref() - .filter(|body| !body.is_empty()) - .unwrap_or("No comment text."), - )), - meta: compact_date( - comment - .updated_at - .as_deref() - .or(comment.created_at.as_deref()), - ) - .into(), - } -} - -fn issue_meta(issue: &models::Issue) -> String { - let author = issue - .user - .as_ref() - .and_then(|user| user.login.as_deref()) - .unwrap_or("unknown"); - format!( - "#{} · {} · updated {} · {} comments", - issue.number.unwrap_or_default(), - author, - compact_date(issue.updated_at.as_deref()), - issue.comments.unwrap_or_default() - ) -} - -fn summary(body: &str) -> String { - body.split_whitespace() - .take(24) - .collect::>() - .join(" ") -} - -pub fn compact_date(date: Option<&str>) -> String { - date.and_then(|date| date.get(..10)) - .unwrap_or("unknown") - .to_string() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn maps_heat_levels_and_labels() { - let heatmap = vec![ - models::UserHeatmapData { - timestamp: Some(100 * 86_400), - contributions: Some(1), - }, - models::UserHeatmapData { - timestamp: Some(101 * 86_400), - contributions: Some(4), - }, - ]; - let (cells, total) = heat_cells(&heatmap); - assert_eq!((cells.len(), total), (364, 5)); - assert_eq!((cells[362].level, cells[363].level), (1, 4)); - - let label = models::Label { - name: Some("bug".into()), - color: Some("d73a4a".into()), - ..Default::default() - }; - assert!(label_row(&label).is_some()); - assert!(label_row(&models::Label::default()).is_none()); - } - - #[test] - fn pull_files_use_the_live_branch_or_merge_commit() { - let mut pull = models::PullRequest { - state: Some("open".into()), - head: Some(Box::new(models::PrBranchInfo { - r#ref: Some("feature/files".into()), - ..Default::default() - })), - merge_commit_sha: Some("0123456789abcdef".into()), - ..Default::default() - }; - assert_eq!(pull_files_ref(&pull), "Files on feature/files"); - - pull.state = Some("closed".into()); - assert_eq!(pull_files_ref(&pull), "Files at merge commit 01234567"); - } - - #[test] - fn activity_rows_use_semantic_icons() { - let activity = models::Activity { - op_type: Some(models::activity::OpType::CreatePullRequest), - content: Some("42|feature".into()), - repo: Some(Box::new(models::Repository { - full_name: Some("octo/demo".into()), - ..Default::default() - })), - ..Default::default() - }; - - assert_eq!(activity_row(&activity).icon, "pull-open"); - } -} diff --git a/crates/app/ui/app.slint b/crates/app/ui/app.slint deleted file mode 100644 index a1370cf..0000000 --- a/crates/app/ui/app.slint +++ /dev/null @@ -1,614 +0,0 @@ -import { Button, ComboBox, LineEdit, ListView, ScrollView, Spinner } from "std-widgets.slint"; -export { - ActivityRow, CommentRow, CommitRow, DiffLine, FileRow, GraphLane, HeatCell, IssueRow, - LabelRow, MarkdownBlock, PullRow, RepositoryRow, ServerRow, -} from "types.slint"; -import { - ActivityRow, CommentRow, CommitRow, DiffLine, FileRow, GraphLane, HeatCell, IssueRow, - MarkdownBlock, PullRow, RepositoryRow, ServerRow, -} from "types.slint"; -export { AppTheme, IconTheme } from "components.slint"; -import { - AppearanceChoice, AppIcon, AppTheme, CommentCard, EdgeBack, EmptyState, FileCard, Header, - IconStyleChoice, MarkdownContent, PullToRefreshGesture, TabButton, -} from "components.slint"; - -export component AppWindow inherits Window { - title: "Gotcha"; - preferred-width: 390px; - preferred-height: 844px; - background: AppTheme.page; - private property toolbar-height: 68px; - - in-out property tab: "home"; - in-out property page: "servers"; - in-out property <[ServerRow]> servers; - in-out property <[RepositoryRow]> repositories; - in-out property <[IssueRow]> issues; - in-out property <[PullRow]> pulls; - in-out property <[CommentRow]> comments; - in-out property <[CommitRow]> commits; - in-out property <[FileRow]> files; - in-out property <[ActivityRow]> activities; - in-out property <[HeatCell]> heat_cells; - in-out property contribution_count; - in-out property has_active_server; - in-out property home_server; - in-out property server_title; - in-out property repository_title; - in-out property issue_title; - in-out property issue_meta; - in-out property <[MarkdownBlock]> issue_body; - in-out property detail_title; - in-out property detail_meta; - in-out property detail_files_ref; - in-out property <[MarkdownBlock]> detail_body; - in-out property <[DiffLine]> diff_lines; - in-out property diff_columns; - in-out property issue_filter: "open"; - in-out property pull_filter: "open"; - in-out property <[string]> branch_choices; - in-out property branch_index; - in-out property filter_open; - in-out property loading; - in-out property home_loading; - in-out property refreshing; - in-out property error; - in-out property home_scroll_request; - in-out property servers_scroll_request; - in-out property repositories_scroll_request; - in-out property issues_scroll_request; - in-out property issue_scroll_request; - private property home_at_top: true; - private property repositories_at_top: true; - private property issues_at_top: true; - private property issue_at_top: true; - - callback select_tab(string); - callback refresh_home(); - callback refresh_repositories(); - callback refresh_issues(); - callback refresh_issue(); - callback refresh_pulls(); - callback refresh_pull(); - callback refresh_commits(); - callback show_add_server(); - callback save_server(string, string, string); - callback select_server(int); - callback select_repository(string, string); - callback toggle_favorite(string, string); - callback select_issue(int); - callback select_pull(string, string, int); - callback select_commit(string); - callback select_branch(string); - callback select_file(string); - callback select_pull_file(string); - callback select_activity(string, string, string, int, string); - callback choose_filter(string); - callback choose_icon_style(int); - callback choose_appearance(int); - callback back(); - - if tab == "home": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { x: 0; y: 0; title: root.has_active_server ? root.home_server : "Home"; can_back: false; } - - if !root.has_active_server: EmptyState { - x: 32px; width: parent.width - 64px; y: 210px; height: 190px; - title: "No server selected"; - detail: "Open Issues to select a server or add your first one."; - } - - if root.has_active_server: Rectangle { - x: 16px; y: 72px; width: parent.width - 32px; height: 118px; - background: AppTheme.surface; border-radius: 12px; - Text { x: 13px; y: 10px; text: "Activity · last 12 months"; font-size: 13px; font-weight: 600; color: AppTheme.text; } - Text { x: 13px; y: 86px; text: root.contribution_count + " contributions"; font-size: 12px; color: AppTheme.text-secondary; } - for cell in root.heat_cells: Rectangle { - x: 13px + cell.week * 6.7px; y: 39px + cell.day * 6.2px; - width: 5.2px; height: 5.2px; border-radius: 1px; - background: cell.level == 0 ? (AppTheme.dark ? #3a3a3c : #e5e5e9) : cell.level == 1 ? #b8d9f4 : cell.level == 2 ? #72b5e8 : cell.level == 3 ? #278bd4 : #0969b7; - } - } - - if root.has_active_server && !root.home_loading && root.activities.length == 0: EmptyState { - x: 32px; width: parent.width - 64px; y: 250px; height: 150px; - title: "No recent activity"; - detail: "Your recent server actions will appear here."; - } - - if root.has_active_server: PullToRefreshGesture { - x: 16px; y: 202px; width: parent.width - 32px; height: parent.height - 214px; - at_top: root.home_at_top; refreshing: root.home_loading; - refresh => { root.refresh_home(); } - home_list := ListView { - property scroll_request: root.home_scroll_request; - width: 100%; height: 100%; mouse-drag-pan-enabled: true; - changed scroll_request => { self.viewport-y = 0px; root.home_at_top = true; } - scrolled => { root.home_at_top = self.viewport-y >= 0px; } - for activity in root.activities: Rectangle { - height: 94px; - background: AppTheme.surface; border-radius: 12px; - TouchArea { - enabled: activity.target != ""; - clicked => { root.select_activity(activity.target, activity.owner, activity.repository, activity.number, activity.sha); } - } - Rectangle { - x: 12px; y: 14px; width: 34px; height: 34px; border-radius: 17px; - background: AppTheme.accent-soft; - AppIcon { x: 5px; y: 5px; width: 24px; height: 24px; kind: activity.icon; } - } - Text { x: 57px; y: 11px; width: parent.width - 69px; height: 24px; text: activity.title; font-size: 15px; font-weight: 600; color: AppTheme.text; overflow: elide; } - Text { x: 57px; y: 36px; width: parent.width - 69px; height: 35px; text: activity.detail; font-size: 13px; color: AppTheme.text-secondary; wrap: word-wrap; overflow: elide; } - Text { x: 57px; y: 72px; width: parent.width - 69px; text: activity.meta; font-size: 11px; color: AppTheme.text-tertiary; overflow: elide; } - } - } - } - - if root.home_loading: Spinner { width: 34px; height: 34px; x: (parent.width - self.width) / 2; y: 222px; } - } - - if tab == "settings": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { - x: 0; y: 0; can_back: false; - title: "Settings"; - } - Rectangle { - x: 16px; y: 76px; width: parent.width - 32px; height: 184px; - background: AppTheme.surface; border-radius: 12px; - Text { x: 15px; y: 13px; text: "Icon style"; font-size: 18px; font-weight: 650; color: AppTheme.text; } - Text { x: 15px; y: 43px; width: parent.width - 30px; text: "Choose how navigation and activity icons look throughout Gotcha."; font-size: 13px; color: AppTheme.text-secondary; wrap: word-wrap; } - IconStyleChoice { x: 12px; y: 91px; width: (parent.width - 36px) / 3; height: 78px; value: 0; label: "Outline"; clicked => { root.choose_icon_style(0); } } - IconStyleChoice { x: 18px + (parent.width - 36px) / 3; y: 91px; width: (parent.width - 36px) / 3; height: 78px; value: 1; label: "Duotone"; clicked => { root.choose_icon_style(1); } } - IconStyleChoice { x: 24px + 2 * (parent.width - 36px) / 3; y: 91px; width: (parent.width - 36px) / 3; height: 78px; value: 2; label: "Bold"; clicked => { root.choose_icon_style(2); } } - } - Rectangle { - x: 16px; y: 276px; width: parent.width - 32px; height: 136px; - background: AppTheme.surface; border-radius: 12px; - Text { x: 15px; y: 13px; text: "Appearance"; font-size: 18px; font-weight: 650; color: AppTheme.text; } - Text { x: 15px; y: 43px; width: parent.width - 30px; text: "Follow iOS automatically or choose a fixed light or dark appearance."; font-size: 13px; color: AppTheme.text-secondary; wrap: word-wrap; } - AppearanceChoice { x: 12px; y: 81px; width: (parent.width - 36px) / 3; height: 42px; value: 0; label: "Auto"; clicked => { root.choose_appearance(0); } } - AppearanceChoice { x: 18px + (parent.width - 36px) / 3; y: 81px; width: (parent.width - 36px) / 3; height: 42px; value: 1; label: "Light"; clicked => { root.choose_appearance(1); } } - AppearanceChoice { x: 24px + 2 * (parent.width - 36px) / 3; y: 81px; width: (parent.width - 36px) / 3; height: 42px; value: 2; label: "Dark"; clicked => { root.choose_appearance(2); } } - } - } - - if (tab == "issues" || tab == "repositories") && page == "servers": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { x: 0; y: 0; title: "Servers"; can_back: false; } - if root.servers.length > 0: servers_list := ListView { - property scroll_request: root.servers_scroll_request; - x: 16px; y: 76px; width: parent.width - 32px; height: parent.height - 154px; - mouse-drag-pan-enabled: true; - changed scroll_request => { self.viewport-y = 0px; } - for server[index] in root.servers: Rectangle { - height: 76px; - background: AppTheme.surface; - border-color: AppTheme.border; - border-width: 1px; - border-radius: 12px; - TouchArea { clicked => { root.select_server(index); } } - Text { x: 16px; y: 13px; width: parent.width - 54px; text: server.name; font-size: 17px; font-weight: 600; color: AppTheme.text; overflow: elide; } - Text { x: 16px; y: 42px; width: parent.width - 54px; text: server.url; font-size: 13px; color: AppTheme.text-secondary; overflow: elide; } - AppIcon { x: parent.width - 29px; y: 26px; width: 20px; height: 20px; kind: "disclosure"; icon-color: AppTheme.text-tertiary; } - } - } - Button { x: 16px; y: parent.height - 66px; width: parent.width - 32px; height: 48px; text: "Add Server"; clicked => { root.show_add_server(); } } - } - - if (tab == "issues" || tab == "repositories") && page == "add": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { x: 0; y: 0; title: "Add Server"; can_back: true; back => { root.back(); } } - VerticalLayout { - x: 20px; y: 92px; width: parent.width - 40px; height: 260px; spacing: 10px; - Text { text: "Name"; font-size: 13px; color: AppTheme.text-secondary; } - server_name := LineEdit { placeholder-text: "Work"; } - Text { text: "Server URL"; font-size: 13px; color: AppTheme.text-secondary; } - server_url := LineEdit { placeholder-text: "https://gitea.example.com"; } - Text { text: "Access token"; font-size: 13px; color: AppTheme.text-secondary; } - server_token := LineEdit { placeholder-text: "Token"; input-type: InputType.password; } - } - Button { - x: 20px; y: 380px; width: parent.width - 40px; height: 48px; - text: root.loading ? "Connecting…" : "Add Server"; - enabled: !root.loading; - clicked => { root.save_server(server_name.text, server_url.text, server_token.text); } - } - EdgeBack { x: 0; y: 0; back => { root.back(); } } - } - - if (tab == "issues" || tab == "repositories") && page == "repositories": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { x: 0; y: 0; title: root.server_title; can_back: true; back => { root.back(); } } - if root.refreshing: Spinner { x: parent.width - 42px; y: 17px; width: 24px; height: 24px; } - if !root.loading && root.repositories.length == 0: EmptyState { - x: 34px; width: parent.width - 68px; y: 220px; height: 160px; - title: "No repositories"; - detail: "This account does not own any repositories on this server."; - } - PullToRefreshGesture { - x: 20px; y: 70px; width: parent.width - 40px; height: parent.height - 82px; - at_top: root.repositories_at_top; refreshing: root.refreshing; - refresh => { root.refresh_repositories(); } - repositories_list := ListView { - property scroll_request: root.repositories_scroll_request; - width: 100%; height: 100%; mouse-drag-pan-enabled: true; - changed scroll_request => { self.viewport-y = 0px; root.repositories_at_top = true; } - scrolled => { root.repositories_at_top = self.viewport-y >= 0px; } - for repo in root.repositories: Rectangle { - height: 104px; - background: AppTheme.surface; - border-color: AppTheme.border; - border-width: 1px; - border-radius: 12px; - TouchArea { clicked => { root.select_repository(repo.owner, repo.name); } } - Text { x: 15px; y: 11px; width: parent.width - 62px; text: repo.name; font-size: 17px; font-weight: 600; color: AppTheme.text; overflow: elide; } - Text { x: 15px; y: 39px; width: parent.width - 30px; text: repo.description; font-size: 14px; color: AppTheme.text-secondary; overflow: elide; } - Text { x: 15px; y: 71px; width: parent.width - 30px; text: repo.meta; font-size: 12px; color: AppTheme.text-tertiary; overflow: elide; } - TouchArea { - x: parent.width - 52px; y: 2px; width: 48px; height: 48px; - clicked => { root.toggle_favorite(repo.owner, repo.name); } - AppIcon { x: 10px; y: 10px; width: 28px; height: 28px; kind: repo.favorite ? "favorite-filled" : "favorite"; icon-color: repo.favorite ? #f2a900 : AppTheme.text-tertiary; } - } - } - } - } - EdgeBack { x: 0; y: 0; back => { root.back(); } } - } - - if tab == "issues" && page == "issues": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { - x: 0; y: 0; title: root.repository_title; can_back: true; can_filter: true; - back => { root.back(); } - filter => { root.filter_open = true; } - } - if root.refreshing: Spinner { x: parent.width - 42px; y: 17px; width: 24px; height: 24px; } - if !root.loading && root.issues.length == 0: EmptyState { - x: 34px; width: parent.width - 68px; y: 220px; height: 160px; - title: "No " + root.issue_filter + " issues"; - detail: "No issues match the selected status."; - } - PullToRefreshGesture { - x: 20px; y: 70px; width: parent.width - 40px; height: parent.height - 82px; - at_top: root.issues_at_top; refreshing: root.refreshing; - refresh => { root.refresh_issues(); } - issues_list := ListView { - property scroll_request: root.issues_scroll_request; - width: 100%; height: 100%; mouse-drag-pan-enabled: true; - changed scroll_request => { self.viewport-y = 0px; root.issues_at_top = true; } - scrolled => { root.issues_at_top = self.viewport-y >= 0px; } - for issue in root.issues: Rectangle { - height: issue.labels.length > 0 ? 138px : 112px; - background: AppTheme.surface; - border-color: AppTheme.border; - border-width: 1px; - border-radius: 12px; - TouchArea { clicked => { root.select_issue(issue.number); } } - Text { x: 15px; y: 11px; width: parent.width - 44px; text: issue.title; font-size: 16px; font-weight: 600; color: AppTheme.text; overflow: elide; } - Text { x: 15px; y: 39px; width: parent.width - 30px; height: 38px; text: issue.summary; font-size: 13px; color: AppTheme.text-secondary; wrap: word-wrap; overflow: elide; } - if issue.labels.length > 0: HorizontalLayout { - x: 15px; y: 82px; width: parent.width - 30px; height: 22px; spacing: 5px; - for label in issue.labels: Rectangle { - width: min(104px, max(48px, (parent.width - (issue.labels.length - 1) * 5px) / issue.labels.length)); - height: 22px; background: label.background; border-radius: 11px; - Text { x: 7px; width: parent.width - 14px; text: label.name; color: label.foreground; font-size: 11px; font-weight: 600; horizontal-alignment: center; vertical-alignment: center; overflow: elide; } - } - } - Text { x: 15px; y: issue.labels.length > 0 ? 112px : 86px; width: parent.width - 30px; text: issue.meta; font-size: 12px; color: AppTheme.text-tertiary; overflow: elide; } - } - } - } - EdgeBack { x: 0; y: 0; back => { root.back(); } } - } - - if tab == "issues" && page == "issue": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { x: 0; y: 0; title: "Issue"; can_back: true; back => { root.back(); } } - if root.refreshing: Spinner { x: parent.width - 42px; y: 17px; width: 24px; height: 24px; } - PullToRefreshGesture { - x: 18px; y: 78px; width: parent.width - 36px; height: parent.height - 96px; - at_top: root.issue_at_top; refreshing: root.refreshing; - refresh => { root.refresh_issue(); } - issue_scroll := ScrollView { - property scroll_request: root.issue_scroll_request; - width: 100%; height: 100%; mouse-drag-pan-enabled: true; - changed scroll_request => { self.viewport-y = 0px; root.issue_at_top = true; } - scrolled => { root.issue_at_top = self.viewport-y >= 0px; } - VerticalLayout { - width: parent.width; - spacing: 12px; - alignment: start; - Text { text: root.issue_title; font-size: 23px; font-weight: 650; color: AppTheme.text; wrap: word-wrap; } - Text { text: root.issue_meta; font-size: 13px; color: AppTheme.text-secondary; wrap: word-wrap; } - Rectangle { height: 1px; background: AppTheme.separator; } - MarkdownContent { width: parent.width; blocks: root.issue_body; } - if root.comments.length > 0: Text { text: "Comments"; font-size: 17px; font-weight: 600; color: AppTheme.text; } - for comment in root.comments: CommentCard { width: parent.width; comment: comment; } - } - } - } - EdgeBack { x: 0; y: 0; back => { root.back(); } } - } - - if tab == "pulls" && !root.has_active_server: Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { x: 0; y: 0; title: "Home"; can_back: false; } - EmptyState { - x: 32px; width: parent.width - 64px; y: 210px; height: 190px; - title: "No server selected"; - detail: "Open Issues or Repositories to select a server or add your first one."; - } - } - - if tab == "pulls" && root.has_active_server && page == "pulls": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { - x: 0; y: 0; title: "Pull Requests"; can_back: false; can_filter: true; - filter => { root.filter_open = true; } - } - if root.refreshing: Spinner { x: parent.width - 92px; y: 17px; width: 24px; height: 24px; } - if !root.loading && root.pulls.length == 0: EmptyState { - x: 34px; width: parent.width - 68px; y: 220px; height: 160px; - title: "No " + root.pull_filter + " pull requests"; - detail: "No pull requests match the selected status."; - } - PullToRefreshGesture { - x: 16px; y: 70px; width: parent.width - 32px; height: parent.height - 82px; - at_top: root.issues_at_top; refreshing: root.refreshing; - refresh => { root.refresh_pulls(); } - ListView { - width: 100%; height: 100%; mouse-drag-pan-enabled: true; - scrolled => { root.issues_at_top = self.viewport-y >= 0px; } - for pull in root.pulls: Rectangle { - height: 112px; - background: AppTheme.surface; border-color: AppTheme.border; border-width: 1px; border-radius: 12px; - TouchArea { clicked => { root.select_pull(pull.owner, pull.repository, pull.number); } } - Text { x: 14px; y: 10px; width: parent.width - 28px; text: pull.repository + " #" + pull.number; font-size: 12px; color: AppTheme.text-secondary; overflow: elide; } - Text { x: 14px; y: 31px; width: parent.width - 28px; text: pull.title; font-size: 16px; font-weight: 600; color: AppTheme.text; overflow: elide; } - Text { x: 14px; y: 57px; width: parent.width - 28px; text: pull.summary; font-size: 13px; color: AppTheme.text-secondary; overflow: elide; } - Text { x: 14px; y: 85px; width: parent.width - 28px; text: pull.meta; font-size: 12px; color: AppTheme.text-tertiary; overflow: elide; } - } - } - } - } - - if tab == "pulls" && page == "pull": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { x: 0; y: 0; title: "Pull Request"; can_back: true; back => { root.back(); } } - if root.refreshing: Spinner { x: parent.width - 42px; y: 17px; width: 24px; height: 24px; } - PullToRefreshGesture { - x: 18px; y: 78px; width: parent.width - 36px; height: parent.height - 96px; - at_top: root.issue_at_top; refreshing: root.refreshing; - refresh => { root.refresh_pull(); } - ScrollView { - width: 100%; height: 100%; mouse-drag-pan-enabled: true; - scrolled => { root.issue_at_top = self.viewport-y >= 0px; } - VerticalLayout { - width: parent.width; spacing: 12px; alignment: start; - Text { text: root.detail_title; font-size: 23px; font-weight: 650; color: AppTheme.text; wrap: word-wrap; } - Text { text: root.detail_meta; font-size: 13px; color: AppTheme.text-secondary; wrap: word-wrap; } - Rectangle { height: 1px; background: AppTheme.separator; } - MarkdownContent { width: parent.width; blocks: root.detail_body; } - if root.files.length > 0: Text { text: root.detail_files_ref; font-size: 17px; font-weight: 600; color: AppTheme.text; } - for file in root.files: FileCard { width: parent.width; file: file; clicked => { root.select_pull_file(file.path); } } - if root.comments.length > 0: Text { text: "Comments"; font-size: 17px; font-weight: 600; color: AppTheme.text; } - for comment in root.comments: CommentCard { width: parent.width; comment: comment; } - } - } - } - EdgeBack { x: 0; y: 0; back => { root.back(); } } - } - - if tab == "repositories" && page == "commits": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { x: 0; y: 0; title: root.repository_title; can_back: true; back => { root.back(); } } - ComboBox { - x: 16px; y: 66px; width: parent.width - 32px; height: 42px; - model: root.branch_choices; current-index: root.branch_index; - selected(value) => { root.select_branch(value); } - } - if root.refreshing: Spinner { x: parent.width - 42px; y: 17px; width: 24px; height: 24px; } - if !root.loading && root.commits.length == 0: EmptyState { - x: 34px; width: parent.width - 68px; y: 220px; height: 160px; - title: "No commits"; - detail: "This repository has no commit history."; - } - PullToRefreshGesture { - x: 16px; y: 116px; width: parent.width - 32px; height: parent.height - 128px; - at_top: root.repositories_at_top; refreshing: root.refreshing; - refresh => { root.refresh_commits(); } - ListView { - width: 100%; height: 100%; mouse-drag-pan-enabled: true; - scrolled => { root.repositories_at_top = self.viewport-y >= 0px; } - for commit in root.commits: commit_row := Rectangle { - property graph-width: commit.lanes.length > 0 ? min(72px, commit.lanes.length * 10px + 8px) : 0px; - property lane-spacing: (self.graph-width - 8px) / max(1, commit.lanes.length); - height: 86px; background: transparent; - Rectangle { width: 100%; height: 100%; background: AppTheme.surface; border-color: AppTheme.border; border-width: 1px; border-radius: 12px; } - TouchArea { clicked => { root.select_commit(commit.sha); } } - for lane[index] in commit.lanes: Rectangle { - x: 4px + index * commit_row.lane-spacing; - width: 3px; height: 100%; background: transparent; - if lane.top: Rectangle { - y: -1px; width: 3px; - height: lane.connect_left && !lane.bottom ? 37px : 42px; - background: lane.color; - } - if lane.bottom: Rectangle { - y: lane.connect_left && !lane.top ? 45px : 40px; - width: 3px; - height: parent.height - (lane.connect_left && !lane.top ? 44px : 39px); - background: lane.color; - } - if lane.connect_left && lane.top == lane.bottom: Rectangle { - x: -commit_row.lane-spacing; y: 39px; - width: commit_row.lane-spacing + 3px; height: 3px; - background: lane.color; - } - if lane.connect_left && lane.bottom && !lane.top: Path { - x: 1.5px - commit_row.lane-spacing; y: 35px; - width: commit_row.lane-spacing; height: 11px; - viewbox-width: 10; viewbox-height: 11; - commands: "M 0 5.5 L 5 5.5 Q 10 5.5 10 10.5"; - fill: transparent; stroke: lane.color; stroke-width: 3px; stroke-line-cap: round; - } - if lane.connect_left && lane.top && !lane.bottom: Path { - x: 1.5px - commit_row.lane-spacing; y: 35px; - width: commit_row.lane-spacing; height: 11px; - viewbox-width: 10; viewbox-height: 11; - commands: "M 10 0.5 Q 10 5.5 5 5.5 L 0 5.5"; - fill: transparent; stroke: lane.color; stroke-width: 3px; stroke-line-cap: round; - } - if lane.node: Rectangle { - x: -3px; y: 36px; width: 9px; height: 9px; border-radius: 5px; - background: lane.color; border-color: AppTheme.surface; border-width: 2px; - } - } - Text { x: parent.graph-width + 14px; y: 8px; width: parent.width - self.x - 14px; text: commit.title; font-size: 15px; font-weight: 600; color: AppTheme.text; overflow: elide; } - Text { x: parent.graph-width + 14px; y: 33px; width: parent.width - self.x - 14px; text: commit.refs != "" ? commit.refs : commit.meta; font-size: 12px; color: commit.refs != "" ? AppTheme.accent : AppTheme.text-secondary; overflow: elide; } - Text { x: parent.graph-width + 14px; y: 53px; width: parent.width - self.x - 14px; text: commit.refs != "" ? commit.meta + " · " + commit.sha : commit.sha; font-size: 11px; color: AppTheme.text-tertiary; overflow: elide; } - } - } - } - EdgeBack { x: 0; y: 0; back => { root.back(); } } - } - - if tab == "repositories" && page == "files": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { x: 0; y: 0; title: "Changed Files"; can_back: true; back => { root.back(); } } - if !root.loading && root.files.length == 0: EmptyState { - x: 34px; width: parent.width - 68px; y: 220px; height: 160px; - title: "No changed files"; - detail: "This commit does not contain file changes."; - } - ListView { - x: 16px; y: 70px; width: parent.width - 32px; height: parent.height - 82px; mouse-drag-pan-enabled: true; - for file in root.files: FileCard { file: file; clicked => { root.select_file(file.path); } } - } - EdgeBack { x: 0; y: 0; back => { root.back(); } } - } - - if (tab == "repositories" || tab == "pulls") && page == "diff": Rectangle { - x: root.safe-area-insets.left; y: root.safe-area-insets.top; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.height - root.safe-area-insets.top - root.safe-area-insets.bottom - root.toolbar-height; - background: AppTheme.page; - Header { x: 0; y: 0; title: root.detail_title; can_back: true; back => { root.back(); } } - ScrollView { - x: 12px; y: 70px; width: parent.width - 24px; height: parent.height - 82px; mouse-drag-pan-enabled: true; - viewport-width: max(self.visible-width, root.diff_columns * 7px + 100px); - viewport-height: diff_rows.preferred-height; - diff_rows := VerticalLayout { - width: parent.viewport-width; - for line in root.diff_lines: Rectangle { - height: 20px; - background: line.kind == "addition" ? (AppTheme.dark ? #173d25 : #ddf5e3) : line.kind == "removal" ? (AppTheme.dark ? #4a2020 : #fde2e1) : line.kind == "hunk" ? (AppTheme.dark ? #17324d : #dcecff) : line.kind == "header" ? AppTheme.surface-muted : AppTheme.surface; - Text { x: 0; width: 38px; text: line.old_number; font-family: "monospace"; font-size: 11px; color: AppTheme.text-tertiary; horizontal-alignment: right; vertical-alignment: center; } - Text { x: 42px; width: 38px; text: line.new_number; font-family: "monospace"; font-size: 11px; color: AppTheme.text-tertiary; horizontal-alignment: right; vertical-alignment: center; } - Rectangle { x: 84px; width: 1px; height: 100%; background: AppTheme.separator; } - Text { - x: 90px; width: parent.width - 94px; text: line.text; font-family: "monospace"; font-size: 11px; - color: line.kind == "addition" ? (AppTheme.dark ? #7fe787 : #176b2c) : line.kind == "removal" ? (AppTheme.dark ? #ff7b72 : #a12720) : line.kind == "hunk" ? (AppTheme.dark ? #79c0ff : #175b9e) : AppTheme.text; - wrap: no-wrap; overflow: clip; vertical-alignment: center; - } - } - } - } - EdgeBack { x: 0; y: 0; back => { root.back(); } } - } - - Rectangle { - x: root.safe-area-insets.left; - y: root.height - root.safe-area-insets.bottom - root.toolbar-height; - width: root.width - root.safe-area-insets.left - root.safe-area-insets.right; - height: root.toolbar-height; - background: AppTheme.toolbar; - TabButton { - x: 0; width: parent.width / 5; height: parent.height; kind: "home"; label: "Home"; active: root.tab == "home"; - clicked => { - if root.tab == "home" { root.home_scroll_request += 1; } - root.select_tab("home"); - } - } - TabButton { - x: parent.width / 5; width: parent.width / 5; height: parent.height; kind: "issues"; label: "Issues"; active: root.tab == "issues"; - clicked => { - if root.tab == "issues" { - if root.page == "servers" { root.servers_scroll_request += 1; } - if root.page == "repositories" { root.repositories_scroll_request += 1; } - if root.page == "issues" { root.issues_scroll_request += 1; } - if root.page == "issue" { root.issue_scroll_request += 1; } - } - root.select_tab("issues"); - } - } - TabButton { x: parent.width * 2 / 5; width: parent.width / 5; height: parent.height; kind: "repositories"; label: "Repos"; active: root.tab == "repositories"; clicked => { root.select_tab("repositories"); } } - TabButton { x: parent.width * 3 / 5; width: parent.width / 5; height: parent.height; kind: "pulls"; label: "Pulls"; active: root.tab == "pulls"; clicked => { root.select_tab("pulls"); } } - TabButton { x: parent.width * 4 / 5; width: parent.width / 5; height: parent.height; kind: "settings"; label: "Settings"; active: root.tab == "settings"; clicked => { root.select_tab("settings"); } } - } - - if root.loading && (root.tab == "issues" || root.tab == "repositories" || root.tab == "pulls"): Rectangle { - width: 100%; height: 100%; - background: AppTheme.loading-overlay; - TouchArea { } - Spinner { width: 38px; height: 38px; x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; } - } - if root.filter_open: Rectangle { - width: 100%; height: 100%; background: #00000066; - TouchArea { clicked => { root.filter_open = false; } } - Rectangle { - x: 28px; y: (parent.height - 190px) / 2; width: parent.width - 56px; height: 190px; - background: AppTheme.surface; border-radius: 14px; - Text { x: 18px; y: 16px; width: parent.width - 36px; text: "Status"; font-size: 19px; font-weight: 650; color: AppTheme.text; } - VerticalLayout { - x: 18px; y: 54px; width: parent.width - 36px; height: 118px; spacing: 8px; - Button { text: "Open"; clicked => { root.choose_filter("open"); root.filter_open = false; } } - Button { text: "Closed"; clicked => { root.choose_filter("closed"); root.filter_open = false; } } - } - } - } - if root.error != "": Rectangle { - x: 14px; y: parent.height - root.safe-area-insets.bottom - 132px; width: parent.width - 28px; height: 66px; - background: #d93d35; border-radius: 12px; - Text { x: 14px; width: parent.width - 28px; text: root.error; color: white; font-size: 13px; wrap: word-wrap; vertical-alignment: center; } - TouchArea { clicked => { root.error = ""; } } - } -} diff --git a/crates/app/ui/components.slint b/crates/app/ui/components.slint deleted file mode 100644 index c0a4411..0000000 --- a/crates/app/ui/components.slint +++ /dev/null @@ -1,318 +0,0 @@ -import { Palette } from "std-widgets.slint"; -import { CommentRow, FileRow, MarkdownBlock } from "types.slint"; - -export global IconTheme { - in-out property style: 0; -} - -export global AppTheme { - in-out property mode: 0; - private property system-background: Palette.background; - out property dark: mode == 2 || (mode == 0 && system-background.to-oklch().lightness < 0.5); - out property accent: dark ? #0a84ff : #0879e1; - out property accent-soft: dark ? #17324d : #e5f2fd; - out property page: dark ? #1c1c1e : #f2f2f7; - out property surface: dark ? #2c2c2e : #ffffff; - out property surface-muted: dark ? #3a3a3c : #f5f5f8; - out property header: dark ? #242426 : #f8f8fa; - out property toolbar: dark ? #242426 : #fbfbfc; - out property border: dark ? #48484a : #dedee3; - out property separator: dark ? #3a3a3c : #d8d8dd; - out property text: dark ? #f2f2f7 : #17171a; - out property text-secondary: dark ? #c7c7cc : #5f5f66; - out property text-tertiary: dark ? #8e8e93 : #85858c; - out property code-background: dark ? #242426 : #e9e9ed; - out property quote-background: dark ? #242426 : #f6f6f8; - out property loading-overlay: dark ? #00000088 : #ffffff88; - changed mode => { - Palette.color-scheme = mode == 1 ? ColorScheme.light : mode == 2 ? ColorScheme.dark : ColorScheme.unknown; - } -} - -export component AppIcon inherits Rectangle { - in property kind; - in property style: IconTheme.style; - in property icon-color: AppTheme.accent; - background: transparent; - - private property duotone: root.style == 1; - private property bold: root.style == 2; - private property issue-kind: root.kind == "issues" || root.kind == "issue-open" || root.kind == "issue-close" || root.kind == "issue-reopen" || root.kind == "issue-comment"; - private property repository-kind: root.kind == "repositories" || root.kind == "repo-create" || root.kind == "repo-rename" || root.kind == "repo-star" || root.kind == "repo-watch" || root.kind == "update"; - private property pull-kind: root.kind == "pulls" || root.kind == "pull-open" || root.kind == "pull-merge" || root.kind == "pull-close" || root.kind == "pull-reopen" || root.kind == "pull-comment" || root.kind == "pull-approve" || root.kind == "pull-reject"; - private property tag-kind: root.kind == "tag-push" || root.kind == "tag-delete"; - private property branch-kind: root.kind == "branch-create" || root.kind == "branch-delete"; - private property closed-shape: root.kind == "home" || root.issue-kind || root.repository-kind || root.kind == "favorite" || root.kind == "favorite-filled" || root.tag-kind || root.kind == "release"; - private property has-badge: root.kind == "branch-create" || root.kind == "branch-delete" || root.kind == "push" || root.kind == "repo-create" || root.kind == "repo-rename" || root.kind == "repo-star" || root.kind == "repo-watch" || root.kind == "update" || root.kind == "issue-open" || root.kind == "issue-close" || root.kind == "issue-reopen" || root.kind == "issue-comment" || root.kind == "pull-open" || root.kind == "pull-merge" || root.kind == "pull-close" || root.kind == "pull-reopen" || root.kind == "pull-comment" || root.kind == "pull-approve" || root.kind == "pull-reject" || root.kind == "tag-push" || root.kind == "tag-delete"; - private property positive: root.kind == "issue-close" || root.kind == "pull-merge" || root.kind == "pull-approve"; - private property destructive: root.kind == "branch-delete" || root.kind == "pull-close" || root.kind == "pull-reject" || root.kind == "tag-delete"; - private property badge-color: root.positive ? #20a866 : root.destructive ? #e45b4d : root.duotone ? #26bfd1 : root.icon-color; - private property shape: root.kind == "home" - ? "M3 10.5 L12 3 L21 10.5 V21 H15 V14 H9 V21 H3 Z" - : root.issue-kind - ? "M4 5 H20 V9 C18 9 18 15 20 15 V19 H4 V15 C6 15 6 9 4 9 Z" - : root.repository-kind - ? "M6 3 H18 C19.1 3 20 3.9 20 5 V21 H6 C4.9 21 4 20.1 4 19 V5 C4 3.9 4.9 3 6 3 Z" - : root.pull-kind - ? "M7 4 V20 M17 4 V11 C17 15 14 17 10 17 M7 4 H7 M17 4 H17" - : root.kind == "settings" - ? "M12 3 V6 M12 18 V21 M3 12 H6 M18 12 H21 M5.6 5.6 L7.8 7.8 M16.2 16.2 L18.4 18.4 M18.4 5.6 L16.2 7.8 M7.8 16.2 L5.6 18.4" - : root.kind == "back" - ? (root.bold ? "M16 4 L7 12 L16 20 Z" : "M16 4 L7 12 L16 20") - : root.kind == "filter" - ? (root.bold ? "M3 5 H21 L14 13 V20 L10 22 V13 Z" : "M4 6 H20 M4 12 H20 M4 18 H20") - : root.kind == "favorite" || root.kind == "favorite-filled" - ? "M12 3 L14.8 8.7 L21 9.6 L16.5 14 L17.6 20.3 L12 17.3 L6.4 20.3 L7.5 14 L3 9.6 L9.2 8.7 Z" - : root.kind == "disclosure" - ? (root.bold ? "M8 4 L17 12 L8 20 Z" : "M8 4 L17 12 L8 20") - : root.branch-kind - ? "M7 4 V20 M7 11 C11 11 14 9 14 5" - : root.kind == "push" - ? "M4 8 H13 M4 12 H13 M4 16 H13" - : root.tag-kind - ? "M3 5 V13 L11 21 L21 11 L13 3 H5 C3.9 3 3 3.9 3 5 Z" - : root.kind == "release" - ? "M4 8 L12 4 L20 8 V18 L12 22 L4 18 Z" - : "M12 4 V20 M4 12 H20"; - private property detail: root.kind == "home" - ? "M9 21 V14 H15 V21" - : root.issue-kind - ? "M12 8 V13 M12 16.5 V17" - : root.repository-kind - ? "M7 3 V21 M11 9 L8.5 12 L11 15 M15 9 L17.5 12 L15 15" - : root.pull-kind - ? "M7 4 A1.8 1.8 0 1 1 6.99 4 M7 20 A1.8 1.8 0 1 1 6.99 20 M17 4 A1.8 1.8 0 1 1 16.99 4" - : root.kind == "settings" - ? "M12 8 A4 4 0 1 1 11.99 8" - : root.kind == "filter" && !root.bold - ? "M9 4 V8 M15 10 V14 M8 16 V20" - : root.tag-kind - ? "M8 8 A1.5 1.5 0 1 1 7.99 8" - : root.kind == "release" - ? "M12 18 V8 M9 11 L12 8 L15 11 M7 6 V3 M17 6 V3" - : ""; - private property badge-detail: root.kind == "branch-create" || root.kind == "repo-create" || root.kind == "issue-open" || root.kind == "pull-open" - ? "M18 15.5 V20.5 M15.5 18 H20.5" - : root.kind == "issue-close" || root.kind == "pull-merge" || root.kind == "pull-approve" - ? "M15.4 18 L17.2 19.8 L20.7 16.2" - : root.kind == "branch-delete" || root.kind == "tag-delete" - ? "M15.3 18 H20.7" - : root.kind == "pull-close" || root.kind == "pull-reject" - ? "M16 16 L20 20 M20 16 L16 20" - : root.kind == "push" || root.kind == "tag-push" - ? "M18 20.5 V15.5 M15.7 17.8 L18 15.5 L20.3 17.8" - : root.kind == "repo-rename" - ? "M15.3 20.6 L16 18 L19.7 14.3 L21.7 16.3 L18 20 Z" - : root.kind == "repo-star" - ? "M18 14.5 L19 16.7 L21.4 17 L19.6 18.7 L20.1 21 L18 19.8 L15.9 21 L16.4 18.7 L14.6 17 L17 16.7 Z" - : root.kind == "repo-watch" - ? "M14.5 18 C16.3 15.8 19.7 15.8 21.5 18 C19.7 20.2 16.3 20.2 14.5 18 M18 17 A1 1 0 1 1 17.99 17" - : root.kind == "issue-reopen" || root.kind == "pull-reopen" - ? "M20.8 17 A3 3 0 1 0 20.2 20.3 M20.8 17 H18.5 M20.8 17 V19.3" - : root.kind == "issue-comment" || root.kind == "pull-comment" - ? "M15.5 17 H20.5 V20 H18 L16 21 V20 H15.5 Z M17 18.5 H17.1 M19 18.5 H19.1" - : root.kind == "update" - ? "M18 14.8 L18.8 17.2 L21.2 18 L18.8 18.8 L18 21.2 L17.2 18.8 L14.8 18 L17.2 17.2 Z" - : ""; - - Path { - width: 100%; height: 100%; viewbox-width: 24; viewbox-height: 24; - commands: root.shape; - fill: root.closed-shape ? (root.bold || root.kind == "favorite-filled" ? root.icon-color : root.duotone ? #26bfd133 : transparent) : transparent; - stroke: root.closed-shape && (root.bold || root.kind == "favorite-filled") ? transparent : root.icon-color; - stroke-width: root.bold ? 3px : root.duotone ? 2.2px : 1.8px; - stroke-line-cap: round; stroke-line-join: round; - } - if root.detail != "": Path { - width: 100%; height: 100%; viewbox-width: 24; viewbox-height: 24; - commands: root.detail; fill: transparent; - stroke: root.bold && root.closed-shape ? #ffffff : root.icon-color; - stroke-width: root.bold ? 2.4px : 1.8px; - stroke-line-cap: round; stroke-line-join: round; - } - if root.has-badge: Path { - width: 100%; height: 100%; viewbox-width: 24; viewbox-height: 24; - commands: "M18 13.5 A4.5 4.5 0 1 1 17.99 13.5 Z"; - fill: root.style == 0 ? AppTheme.surface : root.badge-color; - stroke: root.style == 0 ? root.badge-color : #ffffff; - stroke-width: root.style == 2 ? 1.5px : 1.2px; - } - if root.has-badge: Path { - width: 100%; height: 100%; viewbox-width: 24; viewbox-height: 24; - commands: root.badge-detail; fill: root.style == 0 ? transparent : #ffffff; - stroke: root.style == 0 ? root.badge-color : #ffffff; - stroke-width: root.bold ? 2px : 1.6px; - stroke-line-cap: round; stroke-line-join: round; - } -} - -export component IconStyleChoice inherits Rectangle { - in property value; - in property label; - callback clicked(); - background: IconTheme.style == root.value ? AppTheme.accent-soft : AppTheme.surface-muted; - border-color: IconTheme.style == root.value ? AppTheme.accent : AppTheme.border; - border-width: IconTheme.style == root.value ? 2px : 1px; - border-radius: 11px; - TouchArea { clicked => { root.clicked(); } } - AppIcon { x: (parent.width - 30px) / 2; y: 10px; width: 30px; height: 30px; kind: "repositories"; style: root.value; } - Text { y: 48px; width: 100%; text: root.label; font-size: 12px; font-weight: IconTheme.style == root.value ? 650 : 500; color: AppTheme.text; horizontal-alignment: center; } -} - -export component AppearanceChoice inherits Rectangle { - in property value; - in property label; - callback clicked(); - background: AppTheme.mode == root.value ? AppTheme.accent-soft : AppTheme.surface-muted; - border-color: AppTheme.mode == root.value ? AppTheme.accent : AppTheme.border; - border-width: AppTheme.mode == root.value ? 2px : 1px; - border-radius: 11px; - TouchArea { clicked => { root.clicked(); } } - Text { - width: 100%; height: 100%; text: root.label; - font-size: 14px; font-weight: AppTheme.mode == root.value ? 650 : 500; - color: AppTheme.mode == root.value ? AppTheme.accent : AppTheme.text; - horizontal-alignment: center; vertical-alignment: center; - } -} - -export component Header inherits Rectangle { - in property title; - in property can_back; - in property can_filter: false; - callback back(); - callback filter(); - height: 58px; - width: 100%; - background: AppTheme.header; - border-color: AppTheme.border; - border-width: 0px; - - if can_back: TouchArea { - x: 8px; width: 54px; - clicked => { root.back(); } - AppIcon { x: 7px; y: 17px; width: 24px; height: 24px; kind: "back"; } - } - Text { - x: root.can_back || root.can_filter ? 62px : 12px; - width: parent.width - 2 * self.x; - text: root.title; - horizontal-alignment: center; - vertical-alignment: center; - font-size: 18px; - font-weight: 600; - color: AppTheme.text; - overflow: elide; - } - if can_filter: TouchArea { - x: parent.width - 58px; width: 58px; - clicked => { root.filter(); } - AppIcon { x: 17px; y: 17px; width: 24px; height: 24px; kind: "filter"; } - } - Rectangle { y: parent.height - 1px; height: 1px; background: AppTheme.border; } -} - -export component EdgeBack inherits SwipeGestureHandler { - callback back(); - width: 24px; - height: 100%; - handle-swipe-right: true; - swiped => { root.back(); } -} - -export component EmptyState inherits VerticalLayout { - in property title; - in property detail; - alignment: center; - spacing: 8px; - Text { text: root.title; font-size: 21px; font-weight: 600; horizontal-alignment: center; color: AppTheme.text; } - Text { text: root.detail; font-size: 15px; horizontal-alignment: center; wrap: word-wrap; color: AppTheme.text-secondary; } -} - -export component MarkdownContent inherits VerticalLayout { - in property <[MarkdownBlock]> blocks; - spacing: 8px; - - for block in root.blocks: Rectangle { - height: block.kind == "rule" ? 9px : rendered.preferred-height + (block.kind == "code" || block.kind == "table" ? 16px : 0px); - background: block.kind == "code" || block.kind == "table" ? AppTheme.code-background : block.kind == "quote" ? AppTheme.quote-background : transparent; - border-radius: block.kind == "code" || block.kind == "table" ? 7px : 0px; - if block.kind == "quote": Rectangle { width: 3px; height: 100%; background: AppTheme.text-tertiary; border-radius: 2px; } - if block.kind == "rule": Rectangle { y: 4px; width: 100%; height: 1px; background: AppTheme.separator; } - rendered := StyledText { - x: block.kind == "code" || block.kind == "table" ? 8px : block.kind == "quote" ? 11px : 0px; - y: block.kind == "code" || block.kind == "table" ? 8px : 0px; - width: parent.width - self.x - (block.kind == "code" || block.kind == "table" ? 8px : 0px); - text: block.text; - default-color: AppTheme.text; - link-color: AppTheme.accent; - default-font-family: block.kind == "code" || block.kind == "table" ? "monospace" : ""; - default-font-size: block.kind == "heading1" ? 24px : block.kind == "heading2" ? 21px : block.kind == "heading3" ? 19px : block.kind == "heading4" ? 17px : 16px; - } - } -} - -export component CommentCard inherits Rectangle { - in property comment; - height: content.preferred-height + 24px; - background: AppTheme.surface; - border-radius: 10px; - content := VerticalLayout { - x: 12px; y: 10px; width: parent.width - 24px; - spacing: 5px; - Text { text: root.comment.author; font-size: 13px; font-weight: 600; color: AppTheme.text; } - MarkdownContent { width: parent.width; blocks: root.comment.body; } - Text { text: root.comment.meta; font-size: 11px; color: AppTheme.text-tertiary; } - } -} - -export component FileCard inherits Rectangle { - in property file; - callback clicked(); - height: 66px; - background: AppTheme.surface; - border-color: AppTheme.border; - border-width: 1px; - border-radius: 10px; - TouchArea { clicked => { root.clicked(); } } - Text { x: 13px; y: 10px; width: parent.width - 26px; text: root.file.path; font-size: 14px; font-weight: 600; color: AppTheme.text; overflow: elide; } - Text { x: 13px; y: 38px; width: parent.width - 26px; text: root.file.status; font-size: 12px; color: AppTheme.text-secondary; } -} - -export component TabButton inherits Rectangle { - in property kind; - in property label; - in property active; - private property icon-area-height: root.height * 3 / 4; - private property icon-size: min(42px, self.icon-area-height - 6px); - callback clicked(); - background: transparent; - TouchArea { clicked => { root.clicked(); } } - AppIcon { - x: (parent.width - root.icon-size) / 2; - y: (root.icon-area-height - root.icon-size) / 2; - width: root.icon-size; height: root.icon-size; - kind: root.kind; icon-color: root.active ? AppTheme.accent : AppTheme.text-tertiary; - } - Text { - y: root.icon-area-height; height: parent.height - self.y; width: 100%; text: root.label; - color: root.active ? AppTheme.accent : AppTheme.text-tertiary; font-size: 9px; - font-weight: root.active ? 600 : 400; - horizontal-alignment: center; vertical-alignment: center; - } -} - -export component PullToRefreshGesture inherits SwipeGestureHandler { - in property at_top; - in property refreshing; - callback refresh(); - enabled: root.at_top && !root.refreshing; - handle-swipe-down: true; - swiped => { root.refresh(); } - @children - if self.swiping: Rectangle { - x: (parent.width - 142px) / 2; y: 8px; width: 142px; height: 32px; - background: AppTheme.surface-muted; border-radius: 16px; - Text { text: "Release to refresh"; font-size: 12px; color: AppTheme.text-secondary; horizontal-alignment: center; vertical-alignment: center; } - } -} diff --git a/crates/app/ui/types.slint b/crates/app/ui/types.slint deleted file mode 100644 index c7dd6fb..0000000 --- a/crates/app/ui/types.slint +++ /dev/null @@ -1,24 +0,0 @@ -export struct ServerRow { name: string, url: string } -export struct RepositoryRow { - name: string, owner: string, description: string, meta: string, favorite: bool, -} -export struct LabelRow { name: string, background: color, foreground: color } -export struct IssueRow { - number: int, title: string, summary: string, meta: string, labels: [LabelRow], -} -export struct PullRow { - number: int, owner: string, repository: string, title: string, summary: string, meta: string, -} -export struct MarkdownBlock { text: styled-text, kind: string } -export struct CommentRow { author: string, body: [MarkdownBlock], meta: string } -export struct GraphLane { color: color, top: bool, bottom: bool, node: bool, connect_left: bool } -export struct CommitRow { - sha: string, title: string, meta: string, refs: string, lanes: [GraphLane], -} -export struct FileRow { path: string, status: string } -export struct ActivityRow { - icon: string, title: string, detail: string, meta: string, - target: string, owner: string, repository: string, number: int, sha: string, -} -export struct DiffLine { old_number: string, new_number: string, text: string, kind: string } -export struct HeatCell { week: int, day: int, level: int } diff --git a/ios/Generated/gotcha_core.modulemap b/ios/Generated/gotcha_core.modulemap new file mode 100644 index 0000000..8a89753 --- /dev/null +++ b/ios/Generated/gotcha_core.modulemap @@ -0,0 +1,7 @@ +module gotcha_core { + header "gotcha_coreFFI.h" + export * + use "Darwin" + use "_Builtin_stdbool" + use "_Builtin_stdint" +} \ No newline at end of file diff --git a/ios/Generated/gotcha_core.swift b/ios/Generated/gotcha_core.swift new file mode 100644 index 0000000..bf60a4a --- /dev/null +++ b/ios/Generated/gotcha_core.swift @@ -0,0 +1,2858 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +// swiftlint:disable all +import Foundation + +// Depending on the consumer's build setup, the low-level FFI code +// might be in a separate module, or it might be compiled inline into +// this module. This is a bit of light hackery to work with both. +#if canImport(gotcha_coreFFI) +import gotcha_coreFFI +#endif + +fileprivate extension RustBuffer { + // Allocate a new buffer, copying the contents of a `UInt8` array. + init(bytes: [UInt8]) { + let rbuf = bytes.withUnsafeBufferPointer { ptr in + RustBuffer.from(ptr) + } + self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) + } + + static func empty() -> RustBuffer { + RustBuffer(capacity: 0, len:0, data: nil) + } + + static func from(_ ptr: UnsafeBufferPointer) -> RustBuffer { + try! rustCall { ffi_gotcha_core_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) } + } + + // Frees the buffer in place. + // The buffer must not be used after this is called. + func deallocate() { + try! rustCall { ffi_gotcha_core_rustbuffer_free(self, $0) } + } +} + +fileprivate extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer) { + self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress) + } + + init(rawBufferPointer: UnsafeRawBufferPointer) { + self.init( + len: Int32(rawBufferPointer.count), + data: rawBufferPointer.baseAddress?.assumingMemoryBound(to: UInt8.self) + ) + } +} + +// Converter for `&[u8]` / `[ByRef] bytes` arguments. +// +// Conforms to `FfiConverter` so the compiler enforces the full converter +// method set. Only the scope-bound `lower(_:_body:)` overload is sound — +// zero-copy byte buffers only flow foreign -> Rust, and only in argument +// position. The four protocol-witness methods (`lift`, `lower`, `read`, +// `write`) `fatalError` at runtime if anyone reaches them. +// +// The scope-bound `lower` takes a closure because the `ForeignBytes` +// pointer is only guaranteed valid for the duration of +// `Data.withUnsafeBytes`. Callers must run the full FFI call inside +// the closure body. +fileprivate enum FfiConverterByRefBytes: FfiConverter { + typealias SwiftType = Data + typealias FfiType = ForeignBytes + + static func lower(_ value: Data, _ body: (ForeignBytes) throws -> R) rethrows -> R { + return try value.withUnsafeBytes { rawBuf in + try body(ForeignBytes(rawBufferPointer: rawBuf)) + } + } + + static func lower(_ value: Data) -> ForeignBytes { + fatalError("ByRef bytes cannot use the plain lower: returning ForeignBytes escapes the Data.withUnsafeBytes scope. Use the scope-bound lower(_:_body:) overload instead.") + } + + static func lift(_ value: ForeignBytes) throws -> Data { + fatalError("ByRef bytes cannot be lifted: zero-copy &[u8] only flows foreign->Rust") + } + + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Data { + fatalError("ByRef bytes cannot be read from a buffer: zero-copy &[u8] is only supported in argument position, not nested in records/options/etc.") + } + + static func write(_ value: Data, into buf: inout [UInt8]) { + fatalError("ByRef bytes cannot be written to a buffer: zero-copy &[u8] is only supported in argument position, not nested in records/options/etc.") + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + +// Helper classes/extensions that don't change. +// Someday, this will be in a library of its own. + +fileprivate extension Data { + init(rustBuffer: RustBuffer) { + self.init( + bytesNoCopy: rustBuffer.data!, + count: Int(rustBuffer.len), + deallocator: .none + ) + } +} + +// Define reader functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. +// +// With external types, one swift source file needs to be able to call the read +// method on another source file's FfiConverter, but then what visibility +// should Reader have? +// - If Reader is fileprivate, then this means the read() must also +// be fileprivate, which doesn't work with external types. +// - If Reader is internal/public, we'll get compile errors since both source +// files will try define the same type. +// +// Instead, the read() method and these helper functions input a tuple of data + +fileprivate func createReader(data: Data) -> (data: Data, offset: Data.Index) { + (data: data, offset: 0) +} + +// Reads an integer at the current offset, in big-endian order, and advances +// the offset on success. Throws if reading the integer would move the +// offset past the end of the buffer. +fileprivate func readInt(_ reader: inout (data: Data, offset: Data.Index)) throws -> T { + let range = reader.offset...size + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = reader.data[reader.offset] + reader.offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value, { reader.data.copyBytes(to: $0, from: range)}) + reader.offset = range.upperBound + return value.bigEndian +} + +// Reads an arbitrary number of bytes, to be used to read +// raw bytes, this is useful when lifting strings +fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array { + let range = reader.offset..<(reader.offset+count) + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer({ buffer in + reader.data.copyBytes(to: buffer, from: range) + }) + reader.offset = range.upperBound + return value +} + +// Reads a float at the current offset. +fileprivate func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float { + return Float(bitPattern: try readInt(&reader)) +} + +// Reads a float at the current offset. +fileprivate func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double { + return Double(bitPattern: try readInt(&reader)) +} + +// Indicates if the offset has reached the end of the buffer. +fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool { + return reader.offset < reader.data.count +} + +// Define writer functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. See the above discussion on Readers for details. + +fileprivate func createWriter() -> [UInt8] { + return [] +} + +fileprivate func writeBytes(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 { + writer.append(contentsOf: byteArr) +} + +// Writes an integer in big-endian order. +// +// Warning: make sure what you are trying to write +// is in the correct type! +fileprivate func writeInt(_ writer: inout [UInt8], _ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) } +} + +fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) { + writeInt(&writer, value.bitPattern) +} + +fileprivate func writeDouble(_ writer: inout [UInt8], _ value: Double) { + writeInt(&writer, value.bitPattern) +} + +// Protocol for types that transfer other types across the FFI. This is +// analogous to the Rust trait of the same name. +fileprivate protocol FfiConverter { + associatedtype FfiType + associatedtype SwiftType + + static func lift(_ value: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType + static func write(_ value: SwiftType, into buf: inout [UInt8]) +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { } + +extension FfiConverterPrimitive { +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lift(_ value: FfiType) throws -> SwiftType { + return value + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lower(_ value: SwiftType) -> FfiType { + return value + } +} + +// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`. +// Used for complex types where it's hard to write a custom lift/lower. +fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {} + +extension FfiConverterRustBuffer { +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lift(_ buf: RustBuffer) throws -> SwiftType { + var reader = createReader(data: Data(rustBuffer: buf)) + let value = try read(from: &reader) + if hasRemaining(reader) { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lower(_ value: SwiftType) -> RustBuffer { + var writer = createWriter() + write(value, into: &writer) + return RustBuffer(bytes: writer) + } +} +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +fileprivate enum UniffiInternalError: LocalizedError { + case bufferOverflow + case incompleteData + case unexpectedOptionalTag + case unexpectedEnumCase + case unexpectedNullPointer + case unexpectedRustCallStatusCode + case unexpectedRustCallError + case unexpectedStaleHandle + case rustPanic(_ message: String) + + public var errorDescription: String? { + switch self { + case .bufferOverflow: return "Reading the requested value would read past the end of the buffer" + case .incompleteData: return "The buffer still has data after lifting its containing value" + case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1" + case .unexpectedEnumCase: return "Raw enum value doesn't match any cases" + case .unexpectedNullPointer: return "Raw pointer value was null" + case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code" + case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified" + case .unexpectedStaleHandle: return "The object in the handle map has been dropped already" + case let .rustPanic(message): return message + } + } +} + +fileprivate extension NSLock { + func withLock(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + +fileprivate let CALL_SUCCESS: Int8 = 0 +fileprivate let CALL_ERROR: Int8 = 1 +fileprivate let CALL_UNEXPECTED_ERROR: Int8 = 2 +fileprivate let CALL_CANCELLED: Int8 = 3 + +fileprivate extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer.init( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall(_ callback: (UnsafeMutablePointer) -> T) throws -> T { + let neverThrow: ((RustBuffer) throws -> Never)? = nil + return try makeRustCall(callback, errorHandler: neverThrow) +} + +private func rustCallWithError( + _ errorHandler: @escaping (RustBuffer) throws -> E, + _ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: errorHandler) +} + +private func makeRustCall( + _ callback: (UnsafeMutablePointer) -> T, + errorHandler: ((RustBuffer) throws -> E)? +) throws -> T { + uniffiEnsureGotchaCoreInitialized() + var callStatus = RustCallStatus.init() + let returnedVal = callback(&callStatus) + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler) + return returnedVal +} + +private func uniffiCheckCallStatus( + callStatus: RustCallStatus, + errorHandler: ((RustBuffer) throws -> E)? +) throws { + switch callStatus.code { + case CALL_SUCCESS: + return + + case CALL_ERROR: + if let errorHandler = errorHandler { + throw try errorHandler(callStatus.errorBuf) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.unexpectedRustCallError + } + + case CALL_UNEXPECTED_ERROR: + // When the rust code sees a panic, it tries to construct a RustBuffer + // with the message. But if that code panics, then it just sends back + // an empty buffer. + if callStatus.errorBuf.len > 0 { + throw UniffiInternalError.rustPanic(try FfiConverterString.lift(callStatus.errorBuf)) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.rustPanic("Rust panic") + } + + case CALL_CANCELLED: + fatalError("Cancellation not supported yet") + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} + +private func uniffiTraitInterfaceCall( + callStatus: UnsafeMutablePointer, + makeCall: () throws -> T, + writeReturn: (T) -> () +) { + do { + try writeReturn(makeCall()) + } catch let error { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error)) + } +} + +private func uniffiTraitInterfaceCallWithError( + callStatus: UnsafeMutablePointer, + makeCall: () throws -> T, + writeReturn: (T) -> (), + lowerError: (E) -> RustBuffer +) { + do { + try writeReturn(makeCall()) + } catch let error as E { + callStatus.pointee.code = CALL_ERROR + callStatus.pointee.errorBuf = lowerError(error) + } catch { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error)) + } +} +// Initial value and increment amount for handles. +// These ensure that SWIFT handles always have the lowest bit set +fileprivate let UNIFFI_HANDLEMAP_INITIAL: UInt64 = 1 +fileprivate let UNIFFI_HANDLEMAP_DELTA: UInt64 = 2 + +fileprivate final class UniffiHandleMap: @unchecked Sendable { + // All mutation happens with this lock held, which is why we implement @unchecked Sendable. + private let lock = NSLock() + private var map: [UInt64: T] = [:] + private var currentHandle: UInt64 = UNIFFI_HANDLEMAP_INITIAL + + func insert(obj: T) -> UInt64 { + lock.withLock { + return doInsert(obj) + } + } + + // Low-level insert function, this assumes `lock` is held. + private func doInsert(_ obj: T) -> UInt64 { + let handle = currentHandle + currentHandle += UNIFFI_HANDLEMAP_DELTA + map[handle] = obj + return handle + } + + func get(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map[handle] else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + func clone(handle: UInt64) throws -> UInt64 { + try lock.withLock { + guard let obj = map[handle] else { + throw UniffiInternalError.unexpectedStaleHandle + } + return doInsert(obj) + } + } + + @discardableResult + func remove(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map.removeValue(forKey: handle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + var count: Int { + get { + map.count + } + } +} + + +// Public interface members begin here. + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterUInt32: FfiConverterPrimitive { + typealias FfiType = UInt32 + typealias SwiftType = UInt32 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterInt64: FfiConverterPrimitive { + typealias FfiType = Int64 + typealias SwiftType = Int64 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int64 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int64, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterBool : FfiConverter { + typealias FfiType = Int8 + typealias SwiftType = Bool + + public static func lift(_ value: Int8) throws -> Bool { + return value != 0 + } + + public static func lower(_ value: Bool) -> Int8 { + return value ? 1 : 0 + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Bool, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterString: FfiConverter { + typealias SwiftType = String + typealias FfiType = RustBuffer + + public static func lift(_ value: RustBuffer) throws -> String { + defer { + value.deallocate() + } + if value.data == nil { + return String() + } + let bytes = UnsafeBufferPointer(start: value.data!, count: Int(value.len)) + // Use Swift's native UTF-8 decoder; `String(bytes:encoding:.utf8)` goes + // through Foundation's NSString and silently strips a leading U+FEFF BOM. + // Invalid UTF-8 substitutes U+FFFD instead of trapping (unreachable + // given Rust's `String` invariant). + return String(decoding: bytes, as: UTF8.self) + } + + public static func lower(_ value: String) -> RustBuffer { + return value.utf8CString.withUnsafeBufferPointer { ptr in + // The swift string gives us int8_t, we want uint8_t. + ptr.withMemoryRebound(to: UInt8.self) { ptr in + // The swift string gives us a trailing null byte, we don't want it. + let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1)) + return RustBuffer.from(buf) + } + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { + let len: Int32 = try readInt(&buf) + // See `lift` above for why we avoid Foundation's NSString-backed decoder here. + return String(decoding: try readBytes(&buf, count: Int(len)), as: UTF8.self) + } + + public static func write(_ value: String, into buf: inout [UInt8]) { + let len = Int32(value.utf8.count) + writeInt(&buf, len) + writeBytes(&buf, value.utf8) + } +} + + + + +public protocol GotchaCoreProtocol: AnyObject, Sendable { + + func activeServerIndex() -> UInt32? + + func activeServerName() -> String? + + func addServer(name: String, url: String, token: String) async throws -> UInt32 + + func commitDiff(owner: String, repository: String, sha: String, path: String) async throws -> DiffPage + + func commitFiles(owner: String, repository: String, sha: String) async throws -> [FileRow] + + func commits(owner: String, repository: String, branch: String?) async throws -> CommitPage + + func home() async throws -> HomePage + + func issue(owner: String, repository: String, number: Int64) async throws -> IssuePage + + func issues(owner: String, repository: String) async throws -> [IssueRow] + + func milestone(owner: String, repository: String, id: Int64) async throws -> MilestonePage + + func milestones(owner: String, repository: String) async throws -> [MilestoneRow] + + func pull(owner: String, repository: String, number: Int64) async throws -> PullPage + + func pullDiff(owner: String, repository: String, number: Int64, path: String) async throws -> DiffPage + + func pulls() async throws -> [PullRow] + + func repositories() async throws -> [RepositoryRow] + + func selectServer(index: UInt32) throws + + func servers() -> [ServerRow] + + func setAppearance(index: UInt32) throws + + func setIssueStatus(status: String) throws + + func setPullStatus(status: String) throws + + func settings() -> Settings + + func startupError() -> String? + + func toggleFavorite(owner: String, repository: String) throws -> [RepositoryRow] + +} +open class GotchaCore: GotchaCoreProtocol, @unchecked Sendable { + fileprivate let handle: UInt64 + + /// Used to instantiate a [FFIObject] without an actual handle, for fakes in tests, mostly. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public struct NoHandle { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + required public init(unsafeFromHandle handle: UInt64) { + self.handle = handle + } + + // This constructor can be used to instantiate a fake object. + // - Parameter noHandle: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + // + // - Warning: + // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing handle the FFI lower functions will crash. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public init(noHandle: NoHandle) { + self.handle = 0 + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public func uniffiCloneHandle() -> UInt64 { + return try! rustCall { uniffi_gotcha_core_fn_clone_gotchacore(self.handle, $0) } + } +public convenience init() { + let handle = + try! rustCall() { + uniffiCallStatus in + uniffi_gotcha_core_fn_constructor_gotchacore_new(uniffiCallStatus + ) +} + self.init(unsafeFromHandle: handle) +} + + deinit { + if handle == 0 { + // Mock objects have handle=0 don't try to free them + return + } + + try! rustCall { uniffi_gotcha_core_fn_free_gotchacore(handle, $0) } + } + + + + +open func activeServerIndex() -> UInt32? { + return try! FfiConverterOptionUInt32.lift(try! rustCall() { + uniffiCallStatus in + uniffi_gotcha_core_fn_method_gotchacore_active_server_index( + self.uniffiCloneHandle(),uniffiCallStatus + ) +}) +} + +open func activeServerName() -> String? { + return try! FfiConverterOptionString.lift(try! rustCall() { + uniffiCallStatus in + uniffi_gotcha_core_fn_method_gotchacore_active_server_name( + self.uniffiCloneHandle(),uniffiCallStatus + ) +}) +} + +open func addServer(name: String, url: String, token: String)async throws -> UInt32 { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_add_server( + self.uniffiCloneHandle(),FfiConverterString.lower(name),FfiConverterString.lower(url),FfiConverterString.lower(token) + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_u32, + completeFunc: ffi_gotcha_core_rust_future_complete_u32, + freeFunc: ffi_gotcha_core_rust_future_free_u32, + liftFunc: FfiConverterUInt32.lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func commitDiff(owner: String, repository: String, sha: String, path: String)async throws -> DiffPage { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_commit_diff( + self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterString.lower(sha),FfiConverterString.lower(path) + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeDiffPage_lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func commitFiles(owner: String, repository: String, sha: String)async throws -> [FileRow] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_commit_files( + self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterString.lower(sha) + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterSequenceTypeFileRow.lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func commits(owner: String, repository: String, branch: String?)async throws -> CommitPage { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_commits( + self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterOptionString.lower(branch) + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeCommitPage_lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func home()async throws -> HomePage { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_home( + self.uniffiCloneHandle() + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeHomePage_lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func issue(owner: String, repository: String, number: Int64)async throws -> IssuePage { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_issue( + self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterInt64.lower(number) + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeIssuePage_lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func issues(owner: String, repository: String)async throws -> [IssueRow] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_issues( + self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository) + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterSequenceTypeIssueRow.lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func milestone(owner: String, repository: String, id: Int64)async throws -> MilestonePage { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_milestone( + self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterInt64.lower(id) + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeMilestonePage_lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func milestones(owner: String, repository: String)async throws -> [MilestoneRow] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_milestones( + self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository) + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterSequenceTypeMilestoneRow.lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func pull(owner: String, repository: String, number: Int64)async throws -> PullPage { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_pull( + self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterInt64.lower(number) + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypePullPage_lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func pullDiff(owner: String, repository: String, number: Int64, path: String)async throws -> DiffPage { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_pull_diff( + self.uniffiCloneHandle(),FfiConverterString.lower(owner),FfiConverterString.lower(repository),FfiConverterInt64.lower(number),FfiConverterString.lower(path) + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeDiffPage_lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func pulls()async throws -> [PullRow] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_pulls( + self.uniffiCloneHandle() + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterSequenceTypePullRow.lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func repositories()async throws -> [RepositoryRow] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_gotcha_core_fn_method_gotchacore_repositories( + self.uniffiCloneHandle() + ) + }, + pollFunc: ffi_gotcha_core_rust_future_poll_rust_buffer, + completeFunc: ffi_gotcha_core_rust_future_complete_rust_buffer, + freeFunc: ffi_gotcha_core_rust_future_free_rust_buffer, + liftFunc: FfiConverterSequenceTypeRepositoryRow.lift, + errorHandler: FfiConverterTypeGotchaError_lift + ) +} + +open func selectServer(index: UInt32)throws {try rustCallWithError(FfiConverterTypeGotchaError_lift) { + uniffiCallStatus in + uniffi_gotcha_core_fn_method_gotchacore_select_server( + self.uniffiCloneHandle(), + FfiConverterUInt32.lower(index),uniffiCallStatus + ) +} +} + +open func servers() -> [ServerRow] { + return try! FfiConverterSequenceTypeServerRow.lift(try! rustCall() { + uniffiCallStatus in + uniffi_gotcha_core_fn_method_gotchacore_servers( + self.uniffiCloneHandle(),uniffiCallStatus + ) +}) +} + +open func setAppearance(index: UInt32)throws {try rustCallWithError(FfiConverterTypeGotchaError_lift) { + uniffiCallStatus in + uniffi_gotcha_core_fn_method_gotchacore_set_appearance( + self.uniffiCloneHandle(), + FfiConverterUInt32.lower(index),uniffiCallStatus + ) +} +} + +open func setIssueStatus(status: String)throws {try rustCallWithError(FfiConverterTypeGotchaError_lift) { + uniffiCallStatus in + uniffi_gotcha_core_fn_method_gotchacore_set_issue_status( + self.uniffiCloneHandle(), + FfiConverterString.lower(status),uniffiCallStatus + ) +} +} + +open func setPullStatus(status: String)throws {try rustCallWithError(FfiConverterTypeGotchaError_lift) { + uniffiCallStatus in + uniffi_gotcha_core_fn_method_gotchacore_set_pull_status( + self.uniffiCloneHandle(), + FfiConverterString.lower(status),uniffiCallStatus + ) +} +} + +open func settings() -> Settings { + return try! FfiConverterTypeSettings_lift(try! rustCall() { + uniffiCallStatus in + uniffi_gotcha_core_fn_method_gotchacore_settings( + self.uniffiCloneHandle(),uniffiCallStatus + ) +}) +} + +open func startupError() -> String? { + return try! FfiConverterOptionString.lift(try! rustCall() { + uniffiCallStatus in + uniffi_gotcha_core_fn_method_gotchacore_startup_error( + self.uniffiCloneHandle(),uniffiCallStatus + ) +}) +} + +open func toggleFavorite(owner: String, repository: String)throws -> [RepositoryRow] { + return try FfiConverterSequenceTypeRepositoryRow.lift(try rustCallWithError(FfiConverterTypeGotchaError_lift) { + uniffiCallStatus in + uniffi_gotcha_core_fn_method_gotchacore_toggle_favorite( + self.uniffiCloneHandle(), + FfiConverterString.lower(owner), + FfiConverterString.lower(repository),uniffiCallStatus + ) +}) +} + + + +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeGotchaCore: FfiConverter { + typealias FfiType = UInt64 + typealias SwiftType = GotchaCore + + public static func lift(_ handle: UInt64) throws -> GotchaCore { + return GotchaCore(unsafeFromHandle: handle) + } + + public static func lower(_ value: GotchaCore) -> UInt64 { + return value.uniffiCloneHandle() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> GotchaCore { + let handle: UInt64 = try readInt(&buf) + return try lift(handle) + } + + public static func write(_ value: GotchaCore, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeGotchaCore_lift(_ handle: UInt64) throws -> GotchaCore { + return try FfiConverterTypeGotchaCore.lift(handle) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeGotchaCore_lower(_ value: GotchaCore) -> UInt64 { + return FfiConverterTypeGotchaCore.lower(value) +} + + + + +public struct ActivityRow: Equatable, Hashable { + public var icon: String + public var title: String + public var detail: String + public var meta: String + public var target: String + public var owner: String + public var repository: String + public var number: Int64 + public var sha: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(icon: String, title: String, detail: String, meta: String, target: String, owner: String, repository: String, number: Int64, sha: String) { + self.icon = icon + self.title = title + self.detail = detail + self.meta = meta + self.target = target + self.owner = owner + self.repository = repository + self.number = number + self.sha = sha + } + + + + +} + +#if compiler(>=6) +extension ActivityRow: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeActivityRow: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ActivityRow { + return + try ActivityRow( + icon: FfiConverterString.read(from: &buf), + title: FfiConverterString.read(from: &buf), + detail: FfiConverterString.read(from: &buf), + meta: FfiConverterString.read(from: &buf), + target: FfiConverterString.read(from: &buf), + owner: FfiConverterString.read(from: &buf), + repository: FfiConverterString.read(from: &buf), + number: FfiConverterInt64.read(from: &buf), + sha: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: ActivityRow, into buf: inout [UInt8]) { + FfiConverterString.write(value.icon, into: &buf) + FfiConverterString.write(value.title, into: &buf) + FfiConverterString.write(value.detail, into: &buf) + FfiConverterString.write(value.meta, into: &buf) + FfiConverterString.write(value.target, into: &buf) + FfiConverterString.write(value.owner, into: &buf) + FfiConverterString.write(value.repository, into: &buf) + FfiConverterInt64.write(value.number, into: &buf) + FfiConverterString.write(value.sha, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeActivityRow_lift(_ buf: RustBuffer) throws -> ActivityRow { + return try FfiConverterTypeActivityRow.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeActivityRow_lower(_ value: ActivityRow) -> RustBuffer { + return FfiConverterTypeActivityRow.lower(value) +} + + +public struct CommentRow: Equatable, Hashable { + public var author: String + public var body: String + public var meta: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(author: String, body: String, meta: String) { + self.author = author + self.body = body + self.meta = meta + } + + + + +} + +#if compiler(>=6) +extension CommentRow: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeCommentRow: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CommentRow { + return + try CommentRow( + author: FfiConverterString.read(from: &buf), + body: FfiConverterString.read(from: &buf), + meta: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: CommentRow, into buf: inout [UInt8]) { + FfiConverterString.write(value.author, into: &buf) + FfiConverterString.write(value.body, into: &buf) + FfiConverterString.write(value.meta, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCommentRow_lift(_ buf: RustBuffer) throws -> CommentRow { + return try FfiConverterTypeCommentRow.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCommentRow_lower(_ value: CommentRow) -> RustBuffer { + return FfiConverterTypeCommentRow.lower(value) +} + + +public struct CommitPage: Equatable, Hashable { + public var branches: [String] + public var commits: [CommitRow] + public var laneCount: UInt32 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(branches: [String], commits: [CommitRow], laneCount: UInt32) { + self.branches = branches + self.commits = commits + self.laneCount = laneCount + } + + + + +} + +#if compiler(>=6) +extension CommitPage: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeCommitPage: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CommitPage { + return + try CommitPage( + branches: FfiConverterSequenceString.read(from: &buf), + commits: FfiConverterSequenceTypeCommitRow.read(from: &buf), + laneCount: FfiConverterUInt32.read(from: &buf) + ) + } + + public static func write(_ value: CommitPage, into buf: inout [UInt8]) { + FfiConverterSequenceString.write(value.branches, into: &buf) + FfiConverterSequenceTypeCommitRow.write(value.commits, into: &buf) + FfiConverterUInt32.write(value.laneCount, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCommitPage_lift(_ buf: RustBuffer) throws -> CommitPage { + return try FfiConverterTypeCommitPage.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCommitPage_lower(_ value: CommitPage) -> RustBuffer { + return FfiConverterTypeCommitPage.lower(value) +} + + +public struct CommitRow: Equatable, Hashable { + public var sha: String + public var title: String + public var meta: String + public var refs: String + public var topLanes: [UInt32] + public var bottomLanes: [UInt32] + public var nodeLane: UInt32? + public var connections: [UInt32] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(sha: String, title: String, meta: String, refs: String, topLanes: [UInt32], bottomLanes: [UInt32], nodeLane: UInt32?, connections: [UInt32]) { + self.sha = sha + self.title = title + self.meta = meta + self.refs = refs + self.topLanes = topLanes + self.bottomLanes = bottomLanes + self.nodeLane = nodeLane + self.connections = connections + } + + + + +} + +#if compiler(>=6) +extension CommitRow: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeCommitRow: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CommitRow { + return + try CommitRow( + sha: FfiConverterString.read(from: &buf), + title: FfiConverterString.read(from: &buf), + meta: FfiConverterString.read(from: &buf), + refs: FfiConverterString.read(from: &buf), + topLanes: FfiConverterSequenceUInt32.read(from: &buf), + bottomLanes: FfiConverterSequenceUInt32.read(from: &buf), + nodeLane: FfiConverterOptionUInt32.read(from: &buf), + connections: FfiConverterSequenceUInt32.read(from: &buf) + ) + } + + public static func write(_ value: CommitRow, into buf: inout [UInt8]) { + FfiConverterString.write(value.sha, into: &buf) + FfiConverterString.write(value.title, into: &buf) + FfiConverterString.write(value.meta, into: &buf) + FfiConverterString.write(value.refs, into: &buf) + FfiConverterSequenceUInt32.write(value.topLanes, into: &buf) + FfiConverterSequenceUInt32.write(value.bottomLanes, into: &buf) + FfiConverterOptionUInt32.write(value.nodeLane, into: &buf) + FfiConverterSequenceUInt32.write(value.connections, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCommitRow_lift(_ buf: RustBuffer) throws -> CommitRow { + return try FfiConverterTypeCommitRow.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCommitRow_lower(_ value: CommitRow) -> RustBuffer { + return FfiConverterTypeCommitRow.lower(value) +} + + +public struct DiffLine: Equatable, Hashable { + public var oldNumber: String + public var newNumber: String + public var text: String + public var kind: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(oldNumber: String, newNumber: String, text: String, kind: String) { + self.oldNumber = oldNumber + self.newNumber = newNumber + self.text = text + self.kind = kind + } + + + + +} + +#if compiler(>=6) +extension DiffLine: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeDiffLine: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> DiffLine { + return + try DiffLine( + oldNumber: FfiConverterString.read(from: &buf), + newNumber: FfiConverterString.read(from: &buf), + text: FfiConverterString.read(from: &buf), + kind: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: DiffLine, into buf: inout [UInt8]) { + FfiConverterString.write(value.oldNumber, into: &buf) + FfiConverterString.write(value.newNumber, into: &buf) + FfiConverterString.write(value.text, into: &buf) + FfiConverterString.write(value.kind, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeDiffLine_lift(_ buf: RustBuffer) throws -> DiffLine { + return try FfiConverterTypeDiffLine.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeDiffLine_lower(_ value: DiffLine) -> RustBuffer { + return FfiConverterTypeDiffLine.lower(value) +} + + +public struct DiffPage: Equatable, Hashable { + public var title: String + public var columns: UInt32 + public var lines: [DiffLine] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(title: String, columns: UInt32, lines: [DiffLine]) { + self.title = title + self.columns = columns + self.lines = lines + } + + + + +} + +#if compiler(>=6) +extension DiffPage: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeDiffPage: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> DiffPage { + return + try DiffPage( + title: FfiConverterString.read(from: &buf), + columns: FfiConverterUInt32.read(from: &buf), + lines: FfiConverterSequenceTypeDiffLine.read(from: &buf) + ) + } + + public static func write(_ value: DiffPage, into buf: inout [UInt8]) { + FfiConverterString.write(value.title, into: &buf) + FfiConverterUInt32.write(value.columns, into: &buf) + FfiConverterSequenceTypeDiffLine.write(value.lines, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeDiffPage_lift(_ buf: RustBuffer) throws -> DiffPage { + return try FfiConverterTypeDiffPage.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeDiffPage_lower(_ value: DiffPage) -> RustBuffer { + return FfiConverterTypeDiffPage.lower(value) +} + + +public struct FileRow: Equatable, Hashable { + public var path: String + public var status: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(path: String, status: String) { + self.path = path + self.status = status + } + + + + +} + +#if compiler(>=6) +extension FileRow: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeFileRow: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> FileRow { + return + try FileRow( + path: FfiConverterString.read(from: &buf), + status: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: FileRow, into buf: inout [UInt8]) { + FfiConverterString.write(value.path, into: &buf) + FfiConverterString.write(value.status, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeFileRow_lift(_ buf: RustBuffer) throws -> FileRow { + return try FfiConverterTypeFileRow.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeFileRow_lower(_ value: FileRow) -> RustBuffer { + return FfiConverterTypeFileRow.lower(value) +} + + +public struct HeatCell: Equatable, Hashable { + public var week: UInt32 + public var day: UInt32 + public var level: UInt32 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(week: UInt32, day: UInt32, level: UInt32) { + self.week = week + self.day = day + self.level = level + } + + + + +} + +#if compiler(>=6) +extension HeatCell: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeHeatCell: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> HeatCell { + return + try HeatCell( + week: FfiConverterUInt32.read(from: &buf), + day: FfiConverterUInt32.read(from: &buf), + level: FfiConverterUInt32.read(from: &buf) + ) + } + + public static func write(_ value: HeatCell, into buf: inout [UInt8]) { + FfiConverterUInt32.write(value.week, into: &buf) + FfiConverterUInt32.write(value.day, into: &buf) + FfiConverterUInt32.write(value.level, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeHeatCell_lift(_ buf: RustBuffer) throws -> HeatCell { + return try FfiConverterTypeHeatCell.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeHeatCell_lower(_ value: HeatCell) -> RustBuffer { + return FfiConverterTypeHeatCell.lower(value) +} + + +public struct HomePage: Equatable, Hashable { + public var serverName: String + public var activities: [ActivityRow] + public var heatCells: [HeatCell] + public var contributionCount: Int64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(serverName: String, activities: [ActivityRow], heatCells: [HeatCell], contributionCount: Int64) { + self.serverName = serverName + self.activities = activities + self.heatCells = heatCells + self.contributionCount = contributionCount + } + + + + +} + +#if compiler(>=6) +extension HomePage: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeHomePage: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> HomePage { + return + try HomePage( + serverName: FfiConverterString.read(from: &buf), + activities: FfiConverterSequenceTypeActivityRow.read(from: &buf), + heatCells: FfiConverterSequenceTypeHeatCell.read(from: &buf), + contributionCount: FfiConverterInt64.read(from: &buf) + ) + } + + public static func write(_ value: HomePage, into buf: inout [UInt8]) { + FfiConverterString.write(value.serverName, into: &buf) + FfiConverterSequenceTypeActivityRow.write(value.activities, into: &buf) + FfiConverterSequenceTypeHeatCell.write(value.heatCells, into: &buf) + FfiConverterInt64.write(value.contributionCount, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeHomePage_lift(_ buf: RustBuffer) throws -> HomePage { + return try FfiConverterTypeHomePage.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeHomePage_lower(_ value: HomePage) -> RustBuffer { + return FfiConverterTypeHomePage.lower(value) +} + + +public struct IssuePage: Equatable, Hashable { + public var title: String + public var meta: String + public var milestone: String + public var body: String + public var comments: [CommentRow] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(title: String, meta: String, milestone: String, body: String, comments: [CommentRow]) { + self.title = title + self.meta = meta + self.milestone = milestone + self.body = body + self.comments = comments + } + + + + +} + +#if compiler(>=6) +extension IssuePage: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeIssuePage: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> IssuePage { + return + try IssuePage( + title: FfiConverterString.read(from: &buf), + meta: FfiConverterString.read(from: &buf), + milestone: FfiConverterString.read(from: &buf), + body: FfiConverterString.read(from: &buf), + comments: FfiConverterSequenceTypeCommentRow.read(from: &buf) + ) + } + + public static func write(_ value: IssuePage, into buf: inout [UInt8]) { + FfiConverterString.write(value.title, into: &buf) + FfiConverterString.write(value.meta, into: &buf) + FfiConverterString.write(value.milestone, into: &buf) + FfiConverterString.write(value.body, into: &buf) + FfiConverterSequenceTypeCommentRow.write(value.comments, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeIssuePage_lift(_ buf: RustBuffer) throws -> IssuePage { + return try FfiConverterTypeIssuePage.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeIssuePage_lower(_ value: IssuePage) -> RustBuffer { + return FfiConverterTypeIssuePage.lower(value) +} + + +public struct IssueRow: Equatable, Hashable { + public var number: Int64 + public var title: String + public var summary: String + public var meta: String + public var milestone: String + public var labels: [LabelRow] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(number: Int64, title: String, summary: String, meta: String, milestone: String, labels: [LabelRow]) { + self.number = number + self.title = title + self.summary = summary + self.meta = meta + self.milestone = milestone + self.labels = labels + } + + + + +} + +#if compiler(>=6) +extension IssueRow: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeIssueRow: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> IssueRow { + return + try IssueRow( + number: FfiConverterInt64.read(from: &buf), + title: FfiConverterString.read(from: &buf), + summary: FfiConverterString.read(from: &buf), + meta: FfiConverterString.read(from: &buf), + milestone: FfiConverterString.read(from: &buf), + labels: FfiConverterSequenceTypeLabelRow.read(from: &buf) + ) + } + + public static func write(_ value: IssueRow, into buf: inout [UInt8]) { + FfiConverterInt64.write(value.number, into: &buf) + FfiConverterString.write(value.title, into: &buf) + FfiConverterString.write(value.summary, into: &buf) + FfiConverterString.write(value.meta, into: &buf) + FfiConverterString.write(value.milestone, into: &buf) + FfiConverterSequenceTypeLabelRow.write(value.labels, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeIssueRow_lift(_ buf: RustBuffer) throws -> IssueRow { + return try FfiConverterTypeIssueRow.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeIssueRow_lower(_ value: IssueRow) -> RustBuffer { + return FfiConverterTypeIssueRow.lower(value) +} + + +public struct LabelRow: Equatable, Hashable { + public var name: String + public var color: String + public var light: Bool + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(name: String, color: String, light: Bool) { + self.name = name + self.color = color + self.light = light + } + + + + +} + +#if compiler(>=6) +extension LabelRow: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeLabelRow: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> LabelRow { + return + try LabelRow( + name: FfiConverterString.read(from: &buf), + color: FfiConverterString.read(from: &buf), + light: FfiConverterBool.read(from: &buf) + ) + } + + public static func write(_ value: LabelRow, into buf: inout [UInt8]) { + FfiConverterString.write(value.name, into: &buf) + FfiConverterString.write(value.color, into: &buf) + FfiConverterBool.write(value.light, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeLabelRow_lift(_ buf: RustBuffer) throws -> LabelRow { + return try FfiConverterTypeLabelRow.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeLabelRow_lower(_ value: LabelRow) -> RustBuffer { + return FfiConverterTypeLabelRow.lower(value) +} + + +public struct MilestonePage: Equatable, Hashable { + public var milestone: MilestoneRow + public var issues: [IssueRow] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(milestone: MilestoneRow, issues: [IssueRow]) { + self.milestone = milestone + self.issues = issues + } + + + + +} + +#if compiler(>=6) +extension MilestonePage: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeMilestonePage: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MilestonePage { + return + try MilestonePage( + milestone: FfiConverterTypeMilestoneRow.read(from: &buf), + issues: FfiConverterSequenceTypeIssueRow.read(from: &buf) + ) + } + + public static func write(_ value: MilestonePage, into buf: inout [UInt8]) { + FfiConverterTypeMilestoneRow.write(value.milestone, into: &buf) + FfiConverterSequenceTypeIssueRow.write(value.issues, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMilestonePage_lift(_ buf: RustBuffer) throws -> MilestonePage { + return try FfiConverterTypeMilestonePage.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMilestonePage_lower(_ value: MilestonePage) -> RustBuffer { + return FfiConverterTypeMilestonePage.lower(value) +} + + +public struct MilestoneRow: Equatable, Hashable { + public var id: Int64 + public var title: String + public var description: String + public var meta: String + public var openIssues: Int64 + public var closedIssues: Int64 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(id: Int64, title: String, description: String, meta: String, openIssues: Int64, closedIssues: Int64) { + self.id = id + self.title = title + self.description = description + self.meta = meta + self.openIssues = openIssues + self.closedIssues = closedIssues + } + + + + +} + +#if compiler(>=6) +extension MilestoneRow: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeMilestoneRow: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MilestoneRow { + return + try MilestoneRow( + id: FfiConverterInt64.read(from: &buf), + title: FfiConverterString.read(from: &buf), + description: FfiConverterString.read(from: &buf), + meta: FfiConverterString.read(from: &buf), + openIssues: FfiConverterInt64.read(from: &buf), + closedIssues: FfiConverterInt64.read(from: &buf) + ) + } + + public static func write(_ value: MilestoneRow, into buf: inout [UInt8]) { + FfiConverterInt64.write(value.id, into: &buf) + FfiConverterString.write(value.title, into: &buf) + FfiConverterString.write(value.description, into: &buf) + FfiConverterString.write(value.meta, into: &buf) + FfiConverterInt64.write(value.openIssues, into: &buf) + FfiConverterInt64.write(value.closedIssues, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMilestoneRow_lift(_ buf: RustBuffer) throws -> MilestoneRow { + return try FfiConverterTypeMilestoneRow.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMilestoneRow_lower(_ value: MilestoneRow) -> RustBuffer { + return FfiConverterTypeMilestoneRow.lower(value) +} + + +public struct PullPage: Equatable, Hashable { + public var title: String + public var meta: String + public var body: String + public var filesRef: String + public var files: [FileRow] + public var comments: [CommentRow] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(title: String, meta: String, body: String, filesRef: String, files: [FileRow], comments: [CommentRow]) { + self.title = title + self.meta = meta + self.body = body + self.filesRef = filesRef + self.files = files + self.comments = comments + } + + + + +} + +#if compiler(>=6) +extension PullPage: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypePullPage: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PullPage { + return + try PullPage( + title: FfiConverterString.read(from: &buf), + meta: FfiConverterString.read(from: &buf), + body: FfiConverterString.read(from: &buf), + filesRef: FfiConverterString.read(from: &buf), + files: FfiConverterSequenceTypeFileRow.read(from: &buf), + comments: FfiConverterSequenceTypeCommentRow.read(from: &buf) + ) + } + + public static func write(_ value: PullPage, into buf: inout [UInt8]) { + FfiConverterString.write(value.title, into: &buf) + FfiConverterString.write(value.meta, into: &buf) + FfiConverterString.write(value.body, into: &buf) + FfiConverterString.write(value.filesRef, into: &buf) + FfiConverterSequenceTypeFileRow.write(value.files, into: &buf) + FfiConverterSequenceTypeCommentRow.write(value.comments, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypePullPage_lift(_ buf: RustBuffer) throws -> PullPage { + return try FfiConverterTypePullPage.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypePullPage_lower(_ value: PullPage) -> RustBuffer { + return FfiConverterTypePullPage.lower(value) +} + + +public struct PullRow: Equatable, Hashable { + public var number: Int64 + public var owner: String + public var repository: String + public var title: String + public var summary: String + public var meta: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(number: Int64, owner: String, repository: String, title: String, summary: String, meta: String) { + self.number = number + self.owner = owner + self.repository = repository + self.title = title + self.summary = summary + self.meta = meta + } + + + + +} + +#if compiler(>=6) +extension PullRow: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypePullRow: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PullRow { + return + try PullRow( + number: FfiConverterInt64.read(from: &buf), + owner: FfiConverterString.read(from: &buf), + repository: FfiConverterString.read(from: &buf), + title: FfiConverterString.read(from: &buf), + summary: FfiConverterString.read(from: &buf), + meta: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: PullRow, into buf: inout [UInt8]) { + FfiConverterInt64.write(value.number, into: &buf) + FfiConverterString.write(value.owner, into: &buf) + FfiConverterString.write(value.repository, into: &buf) + FfiConverterString.write(value.title, into: &buf) + FfiConverterString.write(value.summary, into: &buf) + FfiConverterString.write(value.meta, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypePullRow_lift(_ buf: RustBuffer) throws -> PullRow { + return try FfiConverterTypePullRow.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypePullRow_lower(_ value: PullRow) -> RustBuffer { + return FfiConverterTypePullRow.lower(value) +} + + +public struct RepositoryRow: Equatable, Hashable { + public var name: String + public var owner: String + public var description: String + public var meta: String + public var favorite: Bool + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(name: String, owner: String, description: String, meta: String, favorite: Bool) { + self.name = name + self.owner = owner + self.description = description + self.meta = meta + self.favorite = favorite + } + + + + +} + +#if compiler(>=6) +extension RepositoryRow: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeRepositoryRow: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RepositoryRow { + return + try RepositoryRow( + name: FfiConverterString.read(from: &buf), + owner: FfiConverterString.read(from: &buf), + description: FfiConverterString.read(from: &buf), + meta: FfiConverterString.read(from: &buf), + favorite: FfiConverterBool.read(from: &buf) + ) + } + + public static func write(_ value: RepositoryRow, into buf: inout [UInt8]) { + FfiConverterString.write(value.name, into: &buf) + FfiConverterString.write(value.owner, into: &buf) + FfiConverterString.write(value.description, into: &buf) + FfiConverterString.write(value.meta, into: &buf) + FfiConverterBool.write(value.favorite, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeRepositoryRow_lift(_ buf: RustBuffer) throws -> RepositoryRow { + return try FfiConverterTypeRepositoryRow.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeRepositoryRow_lower(_ value: RepositoryRow) -> RustBuffer { + return FfiConverterTypeRepositoryRow.lower(value) +} + + +public struct ServerRow: Equatable, Hashable { + public var name: String + public var url: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(name: String, url: String) { + self.name = name + self.url = url + } + + + + +} + +#if compiler(>=6) +extension ServerRow: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeServerRow: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ServerRow { + return + try ServerRow( + name: FfiConverterString.read(from: &buf), + url: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: ServerRow, into buf: inout [UInt8]) { + FfiConverterString.write(value.name, into: &buf) + FfiConverterString.write(value.url, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeServerRow_lift(_ buf: RustBuffer) throws -> ServerRow { + return try FfiConverterTypeServerRow.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeServerRow_lower(_ value: ServerRow) -> RustBuffer { + return FfiConverterTypeServerRow.lower(value) +} + + +public struct Settings: Equatable, Hashable { + public var issueStatus: String + public var pullStatus: String + public var appearance: UInt32 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(issueStatus: String, pullStatus: String, appearance: UInt32) { + self.issueStatus = issueStatus + self.pullStatus = pullStatus + self.appearance = appearance + } + + + + +} + +#if compiler(>=6) +extension Settings: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeSettings: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Settings { + return + try Settings( + issueStatus: FfiConverterString.read(from: &buf), + pullStatus: FfiConverterString.read(from: &buf), + appearance: FfiConverterUInt32.read(from: &buf) + ) + } + + public static func write(_ value: Settings, into buf: inout [UInt8]) { + FfiConverterString.write(value.issueStatus, into: &buf) + FfiConverterString.write(value.pullStatus, into: &buf) + FfiConverterUInt32.write(value.appearance, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSettings_lift(_ buf: RustBuffer) throws -> Settings { + return try FfiConverterTypeSettings.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSettings_lower(_ value: Settings) -> RustBuffer { + return FfiConverterTypeSettings.lower(value) +} + + +public +enum GotchaError: Swift.Error, Equatable, Hashable, Foundation.LocalizedError { + + + + case Message(message: String + ) + + + + + + + public var errorDescription: String? { + String(reflecting: self) + } + +} + +#if compiler(>=6) +extension GotchaError: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeGotchaError: FfiConverterRustBuffer { + typealias SwiftType = GotchaError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> GotchaError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .Message( + message: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: GotchaError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .Message(message): + writeInt(&buf, Int32(1)) + FfiConverterString.write(message, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeGotchaError_lift(_ buf: RustBuffer) throws -> GotchaError { + return try FfiConverterTypeGotchaError.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeGotchaError_lower(_ value: GotchaError) -> RustBuffer { + return FfiConverterTypeGotchaError.lower(value) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionUInt32: FfiConverterRustBuffer { + typealias SwiftType = UInt32? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterUInt32.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterUInt32.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionString: FfiConverterRustBuffer { + typealias SwiftType = String? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterString.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterString.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceUInt32: FfiConverterRustBuffer { + typealias SwiftType = [UInt32] + + public static func write(_ value: [UInt32], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterUInt32.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [UInt32] { + let len: Int32 = try readInt(&buf) + var seq = [UInt32]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterUInt32.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceString: FfiConverterRustBuffer { + typealias SwiftType = [String] + + public static func write(_ value: [String], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterString.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [String] { + let len: Int32 = try readInt(&buf) + var seq = [String]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterString.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeActivityRow: FfiConverterRustBuffer { + typealias SwiftType = [ActivityRow] + + public static func write(_ value: [ActivityRow], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeActivityRow.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [ActivityRow] { + let len: Int32 = try readInt(&buf) + var seq = [ActivityRow]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeActivityRow.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeCommentRow: FfiConverterRustBuffer { + typealias SwiftType = [CommentRow] + + public static func write(_ value: [CommentRow], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeCommentRow.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [CommentRow] { + let len: Int32 = try readInt(&buf) + var seq = [CommentRow]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeCommentRow.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeCommitRow: FfiConverterRustBuffer { + typealias SwiftType = [CommitRow] + + public static func write(_ value: [CommitRow], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeCommitRow.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [CommitRow] { + let len: Int32 = try readInt(&buf) + var seq = [CommitRow]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeCommitRow.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeDiffLine: FfiConverterRustBuffer { + typealias SwiftType = [DiffLine] + + public static func write(_ value: [DiffLine], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeDiffLine.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [DiffLine] { + let len: Int32 = try readInt(&buf) + var seq = [DiffLine]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeDiffLine.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeFileRow: FfiConverterRustBuffer { + typealias SwiftType = [FileRow] + + public static func write(_ value: [FileRow], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeFileRow.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [FileRow] { + let len: Int32 = try readInt(&buf) + var seq = [FileRow]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeFileRow.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeHeatCell: FfiConverterRustBuffer { + typealias SwiftType = [HeatCell] + + public static func write(_ value: [HeatCell], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeHeatCell.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [HeatCell] { + let len: Int32 = try readInt(&buf) + var seq = [HeatCell]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeHeatCell.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeIssueRow: FfiConverterRustBuffer { + typealias SwiftType = [IssueRow] + + public static func write(_ value: [IssueRow], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeIssueRow.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [IssueRow] { + let len: Int32 = try readInt(&buf) + var seq = [IssueRow]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeIssueRow.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeLabelRow: FfiConverterRustBuffer { + typealias SwiftType = [LabelRow] + + public static func write(_ value: [LabelRow], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeLabelRow.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [LabelRow] { + let len: Int32 = try readInt(&buf) + var seq = [LabelRow]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeLabelRow.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeMilestoneRow: FfiConverterRustBuffer { + typealias SwiftType = [MilestoneRow] + + public static func write(_ value: [MilestoneRow], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeMilestoneRow.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [MilestoneRow] { + let len: Int32 = try readInt(&buf) + var seq = [MilestoneRow]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeMilestoneRow.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypePullRow: FfiConverterRustBuffer { + typealias SwiftType = [PullRow] + + public static func write(_ value: [PullRow], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypePullRow.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [PullRow] { + let len: Int32 = try readInt(&buf) + var seq = [PullRow]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypePullRow.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeRepositoryRow: FfiConverterRustBuffer { + typealias SwiftType = [RepositoryRow] + + public static func write(_ value: [RepositoryRow], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeRepositoryRow.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [RepositoryRow] { + let len: Int32 = try readInt(&buf) + var seq = [RepositoryRow]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeRepositoryRow.read(from: &buf)) + } + return seq + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeServerRow: FfiConverterRustBuffer { + typealias SwiftType = [ServerRow] + + public static func write(_ value: [ServerRow], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeServerRow.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [ServerRow] { + let len: Int32 = try readInt(&buf) + var seq = [ServerRow]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeServerRow.read(from: &buf)) + } + return seq + } +} +private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 +private let UNIFFI_RUST_FUTURE_POLL_WAKE: Int8 = 1 + +fileprivate let uniffiContinuationHandleMap = UniffiHandleMap>() + +fileprivate func uniffiRustCallAsync( + rustFutureFunc: () -> UInt64, + pollFunc: (UInt64, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (), + completeFunc: (UInt64, UnsafeMutablePointer) -> F, + freeFunc: (UInt64) -> (), + liftFunc: (F) throws -> T, + errorHandler: ((RustBuffer) throws -> Swift.Error)? +) async throws -> T { + // Make sure to call the ensure init function since future creation doesn't have a + // RustCallStatus param, so doesn't use makeRustCall() + uniffiEnsureGotchaCoreInitialized() + let rustFuture = rustFutureFunc() + defer { + freeFunc(rustFuture) + } + var pollResult: Int8; + repeat { + pollResult = await withUnsafeContinuation { + pollFunc( + rustFuture, + { handle, pollResult in + uniffiFutureContinuationCallback(handle: handle, pollResult: pollResult) + }, + uniffiContinuationHandleMap.insert(obj: $0) + ) + } + } while pollResult != UNIFFI_RUST_FUTURE_POLL_READY + + return try liftFunc(makeRustCall( + { completeFunc(rustFuture, $0) }, + errorHandler: errorHandler + )) +} + +// Callback handlers for an async calls. These are invoked by Rust when the future is ready. They +// lift the return value or error and resume the suspended function. +fileprivate func uniffiFutureContinuationCallback(handle: UInt64, pollResult: Int8) { + if let continuation = try? uniffiContinuationHandleMap.remove(handle: handle) { + continuation.resume(returning: pollResult) + } else { + print("uniffiFutureContinuationCallback invalid handle") + } +} + +private enum InitializationResult { + case ok + case contractVersionMismatch + case apiChecksumMismatch +} +// Use a global variable to perform the versioning checks. Swift ensures that +// the code inside is only computed once. +private let initializationResult: InitializationResult = { + // Get the bindings contract version from our ComponentInterface + let bindings_contract_version = 30 + // Get the scaffolding contract version by calling the into the dylib + let scaffolding_contract_version = ffi_gotcha_core_uniffi_contract_version() + if bindings_contract_version != scaffolding_contract_version { + return InitializationResult.contractVersionMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_active_server_index() != 48940) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_active_server_name() != 62299) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_add_server() != 42705) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_commit_diff() != 56887) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_commit_files() != 43926) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_commits() != 472) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_home() != 5983) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_issue() != 56735) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_issues() != 1316) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_milestone() != 26979) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_milestones() != 27282) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_pull() != 46939) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_pull_diff() != 61384) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_pulls() != 13092) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_repositories() != 12256) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_select_server() != 22721) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_servers() != 2778) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_set_appearance() != 61293) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_set_issue_status() != 44445) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_set_pull_status() != 1356) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_settings() != 59418) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_startup_error() != 56765) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_method_gotchacore_toggle_favorite() != 26694) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_gotcha_core_checksum_constructor_gotchacore_new() != 35775) { + return InitializationResult.apiChecksumMismatch + } + + return InitializationResult.ok +}() + +// Make the ensure init function public so that other modules which have external type references to +// our types can call it. +public func uniffiEnsureGotchaCoreInitialized() { + switch initializationResult { + case .ok: + break + case .contractVersionMismatch: + fatalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + case .apiChecksumMismatch: + fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} + +// swiftlint:enable all \ No newline at end of file diff --git a/ios/Generated/gotcha_coreFFI.h b/ios/Generated/gotcha_coreFFI.h new file mode 100644 index 0000000..92a82b8 --- /dev/null +++ b/ios/Generated/gotcha_coreFFI.h @@ -0,0 +1,786 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + uint64_t capacity; + uint64_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H +#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK +typedef void (*UniffiForeignFutureDroppedCallback)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +typedef void (*UniffiCallbackInterfaceFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE +typedef uint64_t (*UniffiCallbackInterfaceClone)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT +typedef struct UniffiForeignFutureDroppedCallbackStruct { + uint64_t handle; + UniffiForeignFutureDroppedCallback _Nonnull free; +} UniffiForeignFutureDroppedCallbackStruct; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8 +typedef struct UniffiForeignFutureResultU8 { + uint8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultU8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureResultU8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8 +typedef struct UniffiForeignFutureResultI8 { + int8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultI8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureResultI8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16 +typedef struct UniffiForeignFutureResultU16 { + uint16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultU16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureResultU16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16 +typedef struct UniffiForeignFutureResultI16 { + int16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultI16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureResultI16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32 +typedef struct UniffiForeignFutureResultU32 { + uint32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultU32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureResultU32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32 +typedef struct UniffiForeignFutureResultI32 { + int32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultI32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureResultI32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64 +typedef struct UniffiForeignFutureResultU64 { + uint64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultU64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureResultU64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64 +typedef struct UniffiForeignFutureResultI64 { + int64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultI64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureResultI64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32 +typedef struct UniffiForeignFutureResultF32 { + float returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultF32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureResultF32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64 +typedef struct UniffiForeignFutureResultF64 { + double returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultF64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureResultF64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER +typedef struct UniffiForeignFutureResultRustBuffer { + RustBuffer returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureResultRustBuffer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureResultRustBuffer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID +typedef struct UniffiForeignFutureResultVoid { + RustCallStatus callStatus; +} UniffiForeignFutureResultVoid; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid + ); + +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_CLONE_GOTCHACORE +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_CLONE_GOTCHACORE +uint64_t uniffi_gotcha_core_fn_clone_gotchacore(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_FREE_GOTCHACORE +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_FREE_GOTCHACORE +void uniffi_gotcha_core_fn_free_gotchacore(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_CONSTRUCTOR_GOTCHACORE_NEW +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_CONSTRUCTOR_GOTCHACORE_NEW +uint64_t uniffi_gotcha_core_fn_constructor_gotchacore_new(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ACTIVE_SERVER_INDEX +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ACTIVE_SERVER_INDEX +RustBuffer uniffi_gotcha_core_fn_method_gotchacore_active_server_index(uint64_t ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ACTIVE_SERVER_NAME +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ACTIVE_SERVER_NAME +RustBuffer uniffi_gotcha_core_fn_method_gotchacore_active_server_name(uint64_t ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ADD_SERVER +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ADD_SERVER +uint64_t uniffi_gotcha_core_fn_method_gotchacore_add_server(uint64_t ptr, RustBuffer name, RustBuffer url, RustBuffer token +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMIT_DIFF +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMIT_DIFF +uint64_t uniffi_gotcha_core_fn_method_gotchacore_commit_diff(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustBuffer sha, RustBuffer path +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMIT_FILES +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMIT_FILES +uint64_t uniffi_gotcha_core_fn_method_gotchacore_commit_files(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustBuffer sha +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMITS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_COMMITS +uint64_t uniffi_gotcha_core_fn_method_gotchacore_commits(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustBuffer branch +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_HOME +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_HOME +uint64_t uniffi_gotcha_core_fn_method_gotchacore_home(uint64_t ptr +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUE +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUE +uint64_t uniffi_gotcha_core_fn_method_gotchacore_issue(uint64_t ptr, RustBuffer owner, RustBuffer repository, int64_t number +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUES +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_ISSUES +uint64_t uniffi_gotcha_core_fn_method_gotchacore_issues(uint64_t ptr, RustBuffer owner, RustBuffer repository +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONE +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONE +uint64_t uniffi_gotcha_core_fn_method_gotchacore_milestone(uint64_t ptr, RustBuffer owner, RustBuffer repository, int64_t id +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONES +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_MILESTONES +uint64_t uniffi_gotcha_core_fn_method_gotchacore_milestones(uint64_t ptr, RustBuffer owner, RustBuffer repository +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULL +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULL +uint64_t uniffi_gotcha_core_fn_method_gotchacore_pull(uint64_t ptr, RustBuffer owner, RustBuffer repository, int64_t number +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULL_DIFF +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULL_DIFF +uint64_t uniffi_gotcha_core_fn_method_gotchacore_pull_diff(uint64_t ptr, RustBuffer owner, RustBuffer repository, int64_t number, RustBuffer path +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULLS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_PULLS +uint64_t uniffi_gotcha_core_fn_method_gotchacore_pulls(uint64_t ptr +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_REPOSITORIES +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_REPOSITORIES +uint64_t uniffi_gotcha_core_fn_method_gotchacore_repositories(uint64_t ptr +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SELECT_SERVER +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SELECT_SERVER +void uniffi_gotcha_core_fn_method_gotchacore_select_server(uint64_t ptr, uint32_t index, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SERVERS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SERVERS +RustBuffer uniffi_gotcha_core_fn_method_gotchacore_servers(uint64_t ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_APPEARANCE +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_APPEARANCE +void uniffi_gotcha_core_fn_method_gotchacore_set_appearance(uint64_t ptr, uint32_t index, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_ISSUE_STATUS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_ISSUE_STATUS +void uniffi_gotcha_core_fn_method_gotchacore_set_issue_status(uint64_t ptr, RustBuffer status, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_PULL_STATUS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SET_PULL_STATUS +void uniffi_gotcha_core_fn_method_gotchacore_set_pull_status(uint64_t ptr, RustBuffer status, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SETTINGS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_SETTINGS +RustBuffer uniffi_gotcha_core_fn_method_gotchacore_settings(uint64_t ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_STARTUP_ERROR +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_STARTUP_ERROR +RustBuffer uniffi_gotcha_core_fn_method_gotchacore_startup_error(uint64_t ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_TOGGLE_FAVORITE +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_FN_METHOD_GOTCHACORE_TOGGLE_FAVORITE +RustBuffer uniffi_gotcha_core_fn_method_gotchacore_toggle_favorite(uint64_t ptr, RustBuffer owner, RustBuffer repository, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUSTBUFFER_ALLOC +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUSTBUFFER_ALLOC +RustBuffer ffi_gotcha_core_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUSTBUFFER_FROM_BYTES +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUSTBUFFER_FROM_BYTES +RustBuffer ffi_gotcha_core_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUSTBUFFER_FREE +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUSTBUFFER_FREE +void ffi_gotcha_core_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUSTBUFFER_RESERVE +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUSTBUFFER_RESERVE +RustBuffer ffi_gotcha_core_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_U8 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_U8 +void ffi_gotcha_core_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_U8 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_U8 +void ffi_gotcha_core_rust_future_cancel_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_U8 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_U8 +void ffi_gotcha_core_rust_future_free_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_U8 +uint8_t ffi_gotcha_core_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_I8 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_I8 +void ffi_gotcha_core_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_I8 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_I8 +void ffi_gotcha_core_rust_future_cancel_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_I8 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_I8 +void ffi_gotcha_core_rust_future_free_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_I8 +int8_t ffi_gotcha_core_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_U16 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_U16 +void ffi_gotcha_core_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_U16 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_U16 +void ffi_gotcha_core_rust_future_cancel_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_U16 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_U16 +void ffi_gotcha_core_rust_future_free_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_U16 +uint16_t ffi_gotcha_core_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_I16 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_I16 +void ffi_gotcha_core_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_I16 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_I16 +void ffi_gotcha_core_rust_future_cancel_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_I16 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_I16 +void ffi_gotcha_core_rust_future_free_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_I16 +int16_t ffi_gotcha_core_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_U32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_U32 +void ffi_gotcha_core_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_U32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_U32 +void ffi_gotcha_core_rust_future_cancel_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_U32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_U32 +void ffi_gotcha_core_rust_future_free_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_U32 +uint32_t ffi_gotcha_core_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_I32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_I32 +void ffi_gotcha_core_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_I32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_I32 +void ffi_gotcha_core_rust_future_cancel_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_I32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_I32 +void ffi_gotcha_core_rust_future_free_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_I32 +int32_t ffi_gotcha_core_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_U64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_U64 +void ffi_gotcha_core_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_U64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_U64 +void ffi_gotcha_core_rust_future_cancel_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_U64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_U64 +void ffi_gotcha_core_rust_future_free_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_U64 +uint64_t ffi_gotcha_core_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_I64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_I64 +void ffi_gotcha_core_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_I64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_I64 +void ffi_gotcha_core_rust_future_cancel_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_I64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_I64 +void ffi_gotcha_core_rust_future_free_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_I64 +int64_t ffi_gotcha_core_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_F32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_F32 +void ffi_gotcha_core_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_F32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_F32 +void ffi_gotcha_core_rust_future_cancel_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_F32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_F32 +void ffi_gotcha_core_rust_future_free_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_F32 +float ffi_gotcha_core_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_F64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_F64 +void ffi_gotcha_core_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_F64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_F64 +void ffi_gotcha_core_rust_future_cancel_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_F64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_F64 +void ffi_gotcha_core_rust_future_free_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_F64 +double ffi_gotcha_core_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_RUST_BUFFER +void ffi_gotcha_core_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +void ffi_gotcha_core_rust_future_cancel_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_RUST_BUFFER +void ffi_gotcha_core_rust_future_free_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +RustBuffer ffi_gotcha_core_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_VOID +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_POLL_VOID +void ffi_gotcha_core_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_VOID +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_CANCEL_VOID +void ffi_gotcha_core_rust_future_cancel_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_VOID +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_FREE_VOID +void ffi_gotcha_core_rust_future_free_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_RUST_FUTURE_COMPLETE_VOID +void ffi_gotcha_core_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ACTIVE_SERVER_INDEX +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ACTIVE_SERVER_INDEX +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_active_server_index(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ACTIVE_SERVER_NAME +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ACTIVE_SERVER_NAME +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_active_server_name(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ADD_SERVER +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ADD_SERVER +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_add_server(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_COMMIT_DIFF +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_COMMIT_DIFF +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_commit_diff(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_COMMIT_FILES +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_COMMIT_FILES +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_commit_files(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_COMMITS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_COMMITS +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_commits(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_HOME +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_HOME +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_home(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ISSUE +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ISSUE +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_issue(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ISSUES +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_ISSUES +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_issues(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_MILESTONE +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_MILESTONE +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_milestone(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_MILESTONES +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_MILESTONES +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_milestones(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_PULL +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_PULL +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_pull(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_PULL_DIFF +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_PULL_DIFF +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_pull_diff(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_PULLS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_PULLS +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_pulls(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_REPOSITORIES +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_REPOSITORIES +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_repositories(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SELECT_SERVER +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SELECT_SERVER +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_select_server(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SERVERS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SERVERS +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_servers(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SET_APPEARANCE +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SET_APPEARANCE +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_set_appearance(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SET_ISSUE_STATUS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SET_ISSUE_STATUS +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_set_issue_status(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SET_PULL_STATUS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SET_PULL_STATUS +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_set_pull_status(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SETTINGS +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_SETTINGS +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_settings(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_STARTUP_ERROR +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_STARTUP_ERROR +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_startup_error(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_TOGGLE_FAVORITE +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_METHOD_GOTCHACORE_TOGGLE_FAVORITE +uint16_t uniffi_gotcha_core_checksum_method_gotchacore_toggle_favorite(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_CONSTRUCTOR_GOTCHACORE_NEW +#define UNIFFI_FFIDEF_UNIFFI_GOTCHA_CORE_CHECKSUM_CONSTRUCTOR_GOTCHACORE_NEW +uint16_t uniffi_gotcha_core_checksum_constructor_gotchacore_new(void + +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_GOTCHA_CORE_UNIFFI_CONTRACT_VERSION +#define UNIFFI_FFIDEF_FFI_GOTCHA_CORE_UNIFFI_CONTRACT_VERSION +uint32_t ffi_gotcha_core_uniffi_contract_version(void + +); +#endif diff --git a/ios/Gotcha-Bridging-Header.h b/ios/Gotcha-Bridging-Header.h new file mode 100644 index 0000000..8940d69 --- /dev/null +++ b/ios/Gotcha-Bridging-Header.h @@ -0,0 +1 @@ +#include "Generated/gotcha_coreFFI.h" diff --git a/ios/Gotcha.xcodeproj/project.pbxproj b/ios/Gotcha.xcodeproj/project.pbxproj index 15d6e26..eaa6654 100644 --- a/ios/Gotcha.xcodeproj/project.pbxproj +++ b/ios/Gotcha.xcodeproj/project.pbxproj @@ -7,19 +7,59 @@ objects = { /* Begin PBXBuildFile section */ + 1AED04075E363FBC0FF55773 /* ContentScreens.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5E10787062D1175125BB4C /* ContentScreens.swift */; }; + 25BDBDED14B3B886E19F511C /* DetailScreens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EFB48F40AEB3016D3CF643 /* DetailScreens.swift */; }; + 374320EB00185A0C8E40D985 /* ListScreens.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC828923A1E11A58AE348A74 /* ListScreens.swift */; }; + 950C584D58E80106DF350A22 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0921C76676A14A024BA417 /* AppDelegate.swift */; }; + 96C4AFC206A1DBAE3D3E00CE /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F145F13B8ED83A5AB0009A4 /* SettingsViewController.swift */; }; + D59D3ED36ABCC1D8690A9088 /* gotcha_core.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE12480725C13293FAFDDA52 /* gotcha_core.swift */; }; E7AC0B140F5CFC5EF226D174 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDAABE6B13ADC6D08D9438AF /* Assets.xcassets */; }; + EC7F8B4703DDE37A0B10CD9B /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61515849F6AACD721FE915C /* AppContext.swift */; }; + F55A89489B2758D694F3B27D /* Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75B3E4FFB9C9992517C4D69 /* Support.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 13EFB48F40AEB3016D3CF643 /* DetailScreens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailScreens.swift; sourceTree = ""; }; 5FB3250A93766966A60A685E /* Gotcha.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Gotcha.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F145F13B8ED83A5AB0009A4 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; + 9C0921C76676A14A024BA417 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + AC828923A1E11A58AE348A74 /* ListScreens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListScreens.swift; sourceTree = ""; }; + CE5E10787062D1175125BB4C /* ContentScreens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentScreens.swift; sourceTree = ""; }; DDAABE6B13ADC6D08D9438AF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + F61515849F6AACD721FE915C /* AppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = ""; }; + F75B3E4FFB9C9992517C4D69 /* Support.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Support.swift; sourceTree = ""; }; + FE12480725C13293FAFDDA52 /* gotcha_core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = gotcha_core.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXGroup section */ + 710A50F51478401FC642E6E3 /* Sources */ = { + isa = PBXGroup; + children = ( + F61515849F6AACD721FE915C /* AppContext.swift */, + 9C0921C76676A14A024BA417 /* AppDelegate.swift */, + CE5E10787062D1175125BB4C /* ContentScreens.swift */, + 13EFB48F40AEB3016D3CF643 /* DetailScreens.swift */, + AC828923A1E11A58AE348A74 /* ListScreens.swift */, + 8F145F13B8ED83A5AB0009A4 /* SettingsViewController.swift */, + F75B3E4FFB9C9992517C4D69 /* Support.swift */, + ); + path = Sources; + sourceTree = ""; + }; + 94721140EFE7F8E7CD0F5C0B /* Generated */ = { + isa = PBXGroup; + children = ( + FE12480725C13293FAFDDA52 /* gotcha_core.swift */, + ); + path = Generated; + sourceTree = ""; + }; A8558BC8DD12191B80F52573 = { isa = PBXGroup; children = ( DDAABE6B13ADC6D08D9438AF /* Assets.xcassets */, + 94721140EFE7F8E7CD0F5C0B /* Generated */, + 710A50F51478401FC642E6E3 /* Sources */, F059299C038F3CAFCE470831 /* Products */, ); sourceTree = ""; @@ -39,8 +79,8 @@ isa = PBXNativeTarget; buildConfigurationList = 7E8DF3DDA64C8C998FF3BB1D /* Build configuration list for PBXNativeTarget "Gotcha" */; buildPhases = ( + E01FE2CE8A64C51D25060BE9 /* Build Rust core */, 409DBF67E9C2743801B8F8A4 /* Sources */, - 6334AD54CF86DAD2EC201EBC /* Build Rust app */, 9EDD4380917C29EE67EE344B /* Resources */, ); buildRules = ( @@ -63,6 +103,10 @@ BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; TargetAttributes = { + 60C9DF1AB4A7858833302BA5 = { + DevelopmentTeam = MU22FMRGK8; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 807BC8918EC1B0F31384F2B7 /* Build configuration list for PBXProject "Gotcha" */; @@ -96,7 +140,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 6334AD54CF86DAD2EC201EBC /* Build Rust app */ = { + E01FE2CE8A64C51D25060BE9 /* Build Rust core */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; @@ -106,15 +150,15 @@ ); inputPaths = ( ); - name = "Build Rust app"; + name = "Build Rust core"; outputFileListPaths = ( ); outputPaths = ( - "$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)", + "$(DERIVED_FILE_DIR)/rust/libgotcha_core.a", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "./build_for_ios_with_cargo.bash gotcha-app\n"; + shellScript = "./build_rust_core.bash\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -123,6 +167,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + EC7F8B4703DDE37A0B10CD9B /* AppContext.swift in Sources */, + 950C584D58E80106DF350A22 /* AppDelegate.swift in Sources */, + 1AED04075E363FBC0FF55773 /* ContentScreens.swift in Sources */, + 25BDBDED14B3B886E19F511C /* DetailScreens.swift in Sources */, + 374320EB00185A0C8E40D985 /* ListScreens.swift in Sources */, + 96C4AFC206A1DBAE3D3E00CE /* SettingsViewController.swift in Sources */, + F55A89489B2758D694F3B27D /* Support.swift in Sources */, + D59D3ED36ABCC1D8690A9088 /* gotcha_core.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -135,15 +187,21 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = Gotcha.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = MU22FMRGK8; INFOPLIST_FILE = Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = "$(inherited) $(DERIVED_FILE_DIR)/rust"; + OTHER_LDFLAGS = "$(inherited) -lgotcha_core"; PRODUCT_BUNDLE_IDENTIFIER = de.rfc1437.gotcha; SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; + SWIFT_OBJC_BRIDGING_HEADER = "Gotcha-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; @@ -153,15 +211,21 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = Gotcha.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = MU22FMRGK8; INFOPLIST_FILE = Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = "$(inherited) $(DERIVED_FILE_DIR)/rust"; + OTHER_LDFLAGS = "$(inherited) -lgotcha_core"; PRODUCT_BUNDLE_IDENTIFIER = de.rfc1437.gotcha; SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; + SWIFT_OBJC_BRIDGING_HEADER = "Gotcha-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; diff --git a/ios/Sources/AppContext.swift b/ios/Sources/AppContext.swift new file mode 100644 index 0000000..269283b --- /dev/null +++ b/ios/Sources/AppContext.swift @@ -0,0 +1,134 @@ +import UIKit + +@MainActor +final class AppContext { + let core = GotchaCore() + private let window: UIWindow + private(set) var tabs = UITabBarController() + private(set) var navigationControllers: [UINavigationController] = [] + + init(window: UIWindow) { + self.window = window + applyAppearance() + } + + func makeRootController() -> UIViewController { + let roots: [UIViewController] = [ + HomeViewController(context: self), + repositoryRoot(mode: .issues), + repositoryRoot(mode: .commits), + PullsViewController(context: self), + repositoryRoot(mode: .milestones), + ] + let items = [ + ("Home", "house", "house.fill"), + ("Issues", "exclamationmark.circle", "exclamationmark.circle.fill"), + ("Repos", "books.vertical", "books.vertical.fill"), + ("PRs", "arrow.triangle.pull", "arrow.triangle.pull"), + ("Milestones", "flag", "flag.fill"), + ] + navigationControllers = zip(roots, items).map { root, item in + let navigation = UINavigationController(rootViewController: root) + navigation.tabBarItem = UITabBarItem( + title: item.0, + image: UIImage(systemName: item.1), + selectedImage: UIImage(systemName: item.2) + ) + return navigation + } + tabs.viewControllers = navigationControllers + return tabs + } + + func showStartupErrorIfNeeded() { + guard let message = core.startupError() else { return } + tabs.present(errorAlert(message), animated: true) + } + + func selectServer(index: UInt32) throws { + try core.selectServer(index: index) + let replacements: [(Int, UIViewController)] = [ + (0, HomeViewController(context: self)), + (1, RepositoriesViewController(context: self, mode: .issues)), + (2, RepositoriesViewController(context: self, mode: .commits)), + (3, PullsViewController(context: self)), + (4, RepositoriesViewController(context: self, mode: .milestones)), + ] + for (index, root) in replacements { + navigationControllers[index].setViewControllers([root], animated: false) + } + } + + func didAddServer(index: UInt32) throws { + try selectServer(index: index) + } + + func applyAppearance() { + switch core.settings().appearance { + case 1: window.overrideUserInterfaceStyle = .light + case 2: window.overrideUserInterfaceStyle = .dark + default: window.overrideUserInterfaceStyle = .unspecified + } + } + + func route(_ activity: ActivityRow) { + switch activity.target { + case "repository": + tabs.selectedIndex = 1 + navigationControllers[1].pushViewController( + IssuesViewController( + context: self, + owner: activity.owner, + repository: activity.repository + ), + animated: true + ) + case "issue": + tabs.selectedIndex = 1 + navigationControllers[1].pushViewController( + IssueViewController( + context: self, + owner: activity.owner, + repository: activity.repository, + number: activity.number + ), + animated: true + ) + case "pull": + tabs.selectedIndex = 3 + navigationControllers[3].pushViewController( + PullViewController( + context: self, + owner: activity.owner, + repository: activity.repository, + number: activity.number + ), + animated: true + ) + case "commit": + tabs.selectedIndex = 2 + navigationControllers[2].pushViewController( + FilesViewController( + context: self, + owner: activity.owner, + repository: activity.repository, + sha: activity.sha + ), + animated: true + ) + default: + break + } + } + + func symbol(_ name: String) -> UIImage? { + UIImage(systemName: name) + } + + private func repositoryRoot(mode: RepositoryMode) -> UIViewController { + if core.activeServerIndex() == nil { + return ServersViewController(context: self, destination: mode) + } + return RepositoriesViewController(context: self, mode: mode) + } +} diff --git a/ios/Sources/AppDelegate.swift b/ios/Sources/AppDelegate.swift new file mode 100644 index 0000000..331693a --- /dev/null +++ b/ios/Sources/AppDelegate.swift @@ -0,0 +1,19 @@ +import UIKit + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + let window = UIWindow(frame: UIScreen.main.bounds) + let context = AppContext(window: window) + window.rootViewController = context.makeRootController() + window.makeKeyAndVisible() + self.window = window + context.showStartupErrorIfNeeded() + return true + } +} diff --git a/ios/Sources/ContentScreens.swift b/ios/Sources/ContentScreens.swift new file mode 100644 index 0000000..ed19562 --- /dev/null +++ b/ios/Sources/ContentScreens.swift @@ -0,0 +1,654 @@ +import UIKit + +@MainActor +final class IssuesViewController: RefreshingTableViewController { + private let context: AppContext + private let owner: String + private let repository: String + private var rows: [IssueRow] = [] + + init(context: AppContext, owner: String, repository: String) { + self.context = context + self.owner = owner + self.repository = repository + super.init() + title = repository + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + tableView.register(IssueCell.self, forCellReuseIdentifier: "issue") + updateFilterMenu() + loadContent(refreshing: false) + } + + override func loadContent(refreshing: Bool) { + beginLoading(refreshing: refreshing) + loadingTask?.cancel() + loadingTask = Task { + do { + rows = try await context.core.issues(owner: owner, repository: repository) + tableView.reloadData() + let status = context.core.settings().issueStatus + tableView.backgroundView = rows.isEmpty + ? EmptyBackgroundView( + title: "No \(status) issues", + detail: "No issues match the selected status." + ) + : nil + } catch { + if !Task.isCancelled { show(error: error) } + } + endLoading() + } + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + rows.count + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "issue", for: indexPath) as! IssueCell + cell.configure(rows[indexPath.row]) + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + navigationController?.pushViewController( + IssueViewController( + context: context, + owner: owner, + repository: repository, + number: rows[indexPath.row].number + ), + animated: true + ) + } + + private func updateFilterMenu() { + let current = context.core.settings().issueStatus + navigationItem.rightBarButtonItem = UIBarButtonItem( + image: context.symbol("line.3.horizontal.decrease.circle"), + menu: UIMenu(children: ["open", "closed"].map { status in + UIAction( + title: status.capitalized, + state: current == status ? .on : .off + ) { [weak self] _ in + guard let self else { return } + do { + try self.context.core.setIssueStatus(status: status) + self.updateFilterMenu() + self.loadContent(refreshing: false) + } catch { + self.show(error: error) + } + } + }) + ) + } +} + +final class IssueCell: UITableViewCell { + private let titleLabel = UILabel() + private let summaryLabel = UILabel() + private let labels = UIStackView() + private let metaLabel = UILabel() + private let milestoneLabel = UILabel() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + accessoryType = .disclosureIndicator + titleLabel.font = .preferredFont(forTextStyle: .headline) + titleLabel.numberOfLines = 2 + summaryLabel.font = .preferredFont(forTextStyle: .subheadline) + summaryLabel.textColor = .secondaryLabel + summaryLabel.numberOfLines = 2 + labels.axis = .horizontal + labels.spacing = 5 + metaLabel.font = .preferredFont(forTextStyle: .caption1) + metaLabel.textColor = .tertiaryLabel + metaLabel.numberOfLines = 2 + milestoneLabel.font = .preferredFont(forTextStyle: .caption1) + milestoneLabel.adjustsFontForContentSizeCategory = true + let stack = UIStackView( + arrangedSubviews: [titleLabel, summaryLabel, labels, metaLabel, milestoneLabel] + ) + stack.axis = .vertical + stack.spacing = 6 + stack.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(stack) + NSLayoutConstraint.activate([ + stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8), + stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10), + stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10), + ]) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + func configure(_ row: IssueRow) { + titleLabel.text = row.title + summaryLabel.text = row.summary + metaLabel.text = row.meta + milestoneLabel.isHidden = row.milestone.isEmpty + milestoneLabel.attributedText = symbolText( + "flag.fill", + text: row.milestone, + font: milestoneLabel.font, + color: .tertiaryLabel + ) + milestoneLabel.accessibilityLabel = row.milestone.isEmpty + ? nil + : "Milestone \(row.milestone)" + labels.arrangedSubviews.forEach { $0.removeFromSuperview() } + labels.isHidden = row.labels.isEmpty + for label in row.labels.prefix(3) { + let view = UILabel() + view.text = " \(label.name) " + view.font = .preferredFont(forTextStyle: .caption2) + view.textColor = label.light ? .black : .white + view.backgroundColor = UIColor(hex: label.color) + view.layer.cornerRadius = 9 + view.clipsToBounds = true + labels.addArrangedSubview(view) + } + labels.addArrangedSubview(UIView()) + } +} + +@MainActor +final class MilestonesViewController: RefreshingTableViewController { + private let context: AppContext + private let owner: String + private let repository: String + private var rows: [MilestoneRow] = [] + + init(context: AppContext, owner: String, repository: String) { + self.context = context + self.owner = owner + self.repository = repository + super.init() + title = repository + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + tableView.register(MilestoneCell.self, forCellReuseIdentifier: "milestone") + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 118 + loadContent(refreshing: false) + } + + override func loadContent(refreshing: Bool) { + beginLoading(refreshing: refreshing) + loadingTask?.cancel() + loadingTask = Task { + do { + rows = try await context.core.milestones(owner: owner, repository: repository) + tableView.reloadData() + tableView.backgroundView = rows.isEmpty + ? EmptyBackgroundView( + title: "No milestones", + detail: "This repository does not have any milestones." + ) + : nil + } catch { + if !Task.isCancelled { show(error: error) } + } + endLoading() + } + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + rows.count + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + let cell = tableView.dequeueReusableCell( + withIdentifier: "milestone", + for: indexPath + ) as! MilestoneCell + cell.configure(rows[indexPath.row], disclosure: true) + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + navigationController?.pushViewController( + MilestoneViewController( + context: context, + owner: owner, + repository: repository, + id: rows[indexPath.row].id + ), + animated: true + ) + } +} + +@MainActor +final class MilestoneViewController: RefreshingTableViewController { + private let context: AppContext + private let owner: String + private let repository: String + private let id: Int64 + private var page: MilestonePage? + + init(context: AppContext, owner: String, repository: String, id: Int64) { + self.context = context + self.owner = owner + self.repository = repository + self.id = id + super.init() + title = "Milestone" + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + tableView.register(MilestoneCell.self, forCellReuseIdentifier: "milestone") + tableView.register(IssueCell.self, forCellReuseIdentifier: "issue") + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 118 + loadContent(refreshing: false) + } + + override func loadContent(refreshing: Bool) { + beginLoading(refreshing: refreshing) + loadingTask?.cancel() + loadingTask = Task { + do { + page = try await context.core.milestone( + owner: owner, + repository: repository, + id: id + ) + title = page?.milestone.title + tableView.reloadData() + } catch { + if !Task.isCancelled { show(error: error) } + } + endLoading() + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { page == nil ? 0 : 2 } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + section == 0 ? 1 : page?.issues.count ?? 0 + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + section == 1 ? "Issues" : nil + } + + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + section == 1 && page?.issues.isEmpty == true ? "No issues are assigned to this milestone." : nil + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + guard let page else { return UITableViewCell() } + if indexPath.section == 0 { + let cell = tableView.dequeueReusableCell( + withIdentifier: "milestone", + for: indexPath + ) as! MilestoneCell + cell.configure(page.milestone, disclosure: false) + return cell + } + let cell = tableView.dequeueReusableCell(withIdentifier: "issue", for: indexPath) as! IssueCell + cell.configure(page.issues[indexPath.row]) + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard indexPath.section == 1, let issue = page?.issues[indexPath.row] else { return } + tableView.deselectRow(at: indexPath, animated: true) + navigationController?.pushViewController( + IssueViewController( + context: context, + owner: owner, + repository: repository, + number: issue.number + ), + animated: true + ) + } +} + +final class MilestoneCell: UITableViewCell { + private let titleLabel = UILabel() + private let descriptionLabel = UILabel() + private let metaLabel = UILabel() + private let progress = UIProgressView(progressViewStyle: .bar) + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + titleLabel.font = .preferredFont(forTextStyle: .headline) + titleLabel.numberOfLines = 2 + descriptionLabel.font = .preferredFont(forTextStyle: .subheadline) + descriptionLabel.textColor = .secondaryLabel + descriptionLabel.numberOfLines = 2 + metaLabel.font = .preferredFont(forTextStyle: .caption1) + metaLabel.textColor = .tertiaryLabel + metaLabel.numberOfLines = 2 + progress.progressTintColor = .systemGreen + let stack = UIStackView(arrangedSubviews: [titleLabel, descriptionLabel, progress, metaLabel]) + stack.axis = .vertical + stack.spacing = 7 + stack.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(stack) + NSLayoutConstraint.activate([ + stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), + stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), + stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12), + stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12), + ]) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + func configure(_ row: MilestoneRow, disclosure: Bool) { + accessoryType = disclosure ? .disclosureIndicator : .none + titleLabel.text = row.title + descriptionLabel.text = row.description + metaLabel.text = row.meta + let total = row.openIssues + row.closedIssues + progress.progress = total == 0 ? 0 : Float(row.closedIssues) / Float(total) + progress.trackTintColor = total == 0 ? .systemGray5 : .systemOrange + progress.accessibilityLabel = "Milestone progress" + progress.accessibilityValue = "\(row.closedIssues) closed, \(row.openIssues) open" + } +} + +@MainActor +final class PullsViewController: RefreshingTableViewController { + private let context: AppContext + private var rows: [PullRow] = [] + + init(context: AppContext) { + self.context = context + super.init() + title = "Pull Requests" + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + updateFilterMenu() + loadContent(refreshing: false) + } + + override func loadContent(refreshing: Bool) { + guard context.core.activeServerIndex() != nil else { + tableView.backgroundView = EmptyBackgroundView( + title: "No server selected", + detail: "Open Issues or Repos to select a server or add your first one." + ) + refreshControl?.endRefreshing() + return + } + beginLoading(refreshing: refreshing) + loadingTask?.cancel() + loadingTask = Task { + do { + rows = try await context.core.pulls() + tableView.reloadData() + let status = context.core.settings().pullStatus + tableView.backgroundView = rows.isEmpty + ? EmptyBackgroundView( + title: "No \(status) pull requests", + detail: "No pull requests match the selected status." + ) + : nil + } catch { + if !Task.isCancelled { show(error: error) } + } + endLoading() + } + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + rows.count + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "pull") + ?? UITableViewCell(style: .subtitle, reuseIdentifier: "pull") + let row = rows[indexPath.row] + configureTextCell( + cell, + title: "\(row.repository) #\(row.number)\n\(row.title)", + detail: "\(row.summary)\n\(row.meta)" + ) + cell.accessoryType = .disclosureIndicator + return cell + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + 116 + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let row = rows[indexPath.row] + navigationController?.pushViewController( + PullViewController( + context: context, + owner: row.owner, + repository: row.repository, + number: row.number + ), + animated: true + ) + } + + private func updateFilterMenu() { + let current = context.core.settings().pullStatus + navigationItem.rightBarButtonItem = UIBarButtonItem( + image: context.symbol("line.3.horizontal.decrease.circle"), + menu: UIMenu(children: ["open", "closed"].map { status in + UIAction(title: status.capitalized, state: current == status ? .on : .off) { + [weak self] _ in + guard let self else { return } + do { + try self.context.core.setPullStatus(status: status) + self.updateFilterMenu() + self.loadContent(refreshing: false) + } catch { + self.show(error: error) + } + } + }) + ) + } +} + +@MainActor +final class CommitsViewController: RefreshingTableViewController { + private let context: AppContext + private let owner: String + private let repository: String + private var page: CommitPage? + private var branch: String? + + init(context: AppContext, owner: String, repository: String) { + self.context = context + self.owner = owner + self.repository = repository + super.init() + title = repository + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + tableView.register(CommitCell.self, forCellReuseIdentifier: "commit") + updateBranchMenu() + loadContent(refreshing: false) + } + + override func loadContent(refreshing: Bool) { + beginLoading(refreshing: refreshing) + loadingTask?.cancel() + loadingTask = Task { + do { + page = try await context.core.commits( + owner: owner, + repository: repository, + branch: branch + ) + updateBranchMenu() + tableView.reloadData() + tableView.backgroundView = page?.commits.isEmpty == true + ? EmptyBackgroundView(title: "No commits", detail: "This repository has no commit history.") + : nil + } catch { + if !Task.isCancelled { show(error: error) } + } + endLoading() + } + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + page?.commits.count ?? 0 + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "commit", for: indexPath) as! CommitCell + if let page { cell.configure(page.commits[indexPath.row], laneCount: page.laneCount) } + return cell + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + 86 + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + guard let row = page?.commits[indexPath.row] else { return } + navigationController?.pushViewController( + FilesViewController( + context: context, + owner: owner, + repository: repository, + sha: row.sha + ), + animated: true + ) + } + + private func updateBranchMenu() { + let branches = page?.branches ?? [] + let choices: [String?] = [nil] + branches.map(Optional.some) + navigationItem.rightBarButtonItem = UIBarButtonItem( + title: branch ?? "All", + menu: UIMenu(children: choices.map { choice in + UIAction( + title: choice ?? "All", + state: choice == branch ? .on : .off + ) { [weak self] _ in + self?.branch = choice + self?.updateBranchMenu() + self?.loadContent(refreshing: false) + } + }) + ) + } +} + +final class CommitCell: UITableViewCell { + private var row: CommitRow? + private var laneCount: UInt32 = 0 + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + accessoryType = .disclosureIndicator + backgroundColor = .systemBackground + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + func configure(_ row: CommitRow, laneCount: UInt32) { + self.row = row + self.laneCount = laneCount + var content = defaultContentConfiguration() + content.directionalLayoutMargins.leading = laneCount == 0 ? 0 : min(72, CGFloat(laneCount) * 10 + 8) + content.text = row.title + content.secondaryText = row.refs.isEmpty + ? "\(row.meta)\n\(row.sha.prefix(8))" + : "\(row.refs)\n\(row.meta) · \(row.sha.prefix(8))" + content.secondaryTextProperties.numberOfLines = 2 + contentConfiguration = content + setNeedsDisplay() + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + guard let row, laneCount > 0, let context = UIGraphicsGetCurrentContext() else { return } + let spacing: CGFloat = 10 + let centerY = bounds.midY + for lane in 0.. 0 { + context.move(to: CGPoint(x: x - spacing, y: centerY)) + context.addLine(to: CGPoint(x: x, y: centerY)) + context.strokePath() + } + if row.nodeLane == lane { + context.setFillColor(color.cgColor) + context.fillEllipse(in: CGRect(x: x - 5, y: centerY - 5, width: 10, height: 10)) + } + } + } + + private func laneColor(_ lane: UInt32) -> UIColor { + UIColor(hue: CGFloat((Double(lane) * 137.508).truncatingRemainder(dividingBy: 360)) / 360, + saturation: 0.78, brightness: 0.78, alpha: 1) + } +} diff --git a/ios/Sources/DetailScreens.swift b/ios/Sources/DetailScreens.swift new file mode 100644 index 0000000..6a93fec --- /dev/null +++ b/ios/Sources/DetailScreens.swift @@ -0,0 +1,453 @@ +import UIKit + +@MainActor +class MarkdownPageViewController: UIViewController { + let context: AppContext + let scrollView = UIScrollView() + let stack = UIStackView() + private let spinner = UIActivityIndicatorView(style: .medium) + var loadingTask: Task? + + init(context: AppContext) { + self.context = context + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemGroupedBackground + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.alwaysBounceVertical = true + scrollView.refreshControl = UIRefreshControl() + scrollView.refreshControl?.addTarget(self, action: #selector(refreshRequested), for: .valueChanged) + view.addSubview(scrollView) + stack.axis = .vertical + stack.spacing = 12 + stack.translatesAutoresizingMaskIntoConstraints = false + scrollView.addSubview(stack) + NSLayoutConstraint.activate([ + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + scrollView.topAnchor.constraint(equalTo: view.topAnchor), + scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + stack.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 18), + stack.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -18), + stack.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 18), + stack.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -18), + stack.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: -36), + ]) + } + + deinit { loadingTask?.cancel() } + + func loadContent(refreshing: Bool) {} + + func beginLoading(refreshing: Bool) { + if !refreshing { beginNavigationLoading(spinner) } + } + + func endLoading() { + endNavigationLoading(spinner) + scrollView.refreshControl?.endRefreshing() + } + + func replaceContent(_ views: [UIView]) { + stack.arrangedSubviews.forEach { $0.removeFromSuperview() } + views.forEach(stack.addArrangedSubview) + } + + @objc private func refreshRequested() { + loadContent(refreshing: true) + } +} + +@MainActor +final class IssueViewController: MarkdownPageViewController { + private let owner: String + private let repository: String + private let number: Int64 + + init(context: AppContext, owner: String, repository: String, number: Int64) { + self.owner = owner + self.repository = repository + self.number = number + super.init(context: context) + title = "Issue" + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + loadContent(refreshing: false) + } + + override func loadContent(refreshing: Bool) { + beginLoading(refreshing: refreshing) + loadingTask?.cancel() + loadingTask = Task { + do { + let page = try await context.core.issue( + owner: owner, + repository: repository, + number: number + ) + replaceContent(detailViews( + title: page.title, + meta: page.meta, + body: page.body, + comments: page.comments, + milestone: page.milestone + )) + } catch { + if !Task.isCancelled { show(error: error) } + } + endLoading() + } + } +} + +@MainActor +final class PullViewController: MarkdownPageViewController { + private let owner: String + private let repository: String + private let number: Int64 + + init(context: AppContext, owner: String, repository: String, number: Int64) { + self.owner = owner + self.repository = repository + self.number = number + super.init(context: context) + title = "Pull Request" + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + loadContent(refreshing: false) + } + + override func loadContent(refreshing: Bool) { + beginLoading(refreshing: refreshing) + loadingTask?.cancel() + loadingTask = Task { + do { + let page = try await context.core.pull( + owner: owner, + repository: repository, + number: number + ) + var views = detailViews( + title: page.title, + meta: page.meta, + body: page.body, + comments: [] + ) + if !page.files.isEmpty { + views.append(sectionLabel(page.filesRef)) + views.append(contentsOf: page.files.map { file in + detailButton(title: file.path, detail: file.status) { [weak self] in + guard let self else { return } + self.navigationController?.pushViewController( + DiffViewController( + context: self.context, + source: .pull( + owner: self.owner, + repository: self.repository, + number: self.number, + path: file.path + ) + ), + animated: true + ) + } + }) + } + views.append(contentsOf: commentViews(page.comments)) + replaceContent(views) + } catch { + if !Task.isCancelled { show(error: error) } + } + endLoading() + } + } +} + +@MainActor +final class FilesViewController: RefreshingTableViewController { + private let context: AppContext + private let owner: String + private let repository: String + private let sha: String + private var rows: [FileRow] = [] + + init(context: AppContext, owner: String, repository: String, sha: String) { + self.context = context + self.owner = owner + self.repository = repository + self.sha = sha + super.init() + title = "Changed Files" + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + loadContent(refreshing: false) + } + + override func loadContent(refreshing: Bool) { + beginLoading(refreshing: refreshing) + loadingTask?.cancel() + loadingTask = Task { + do { + rows = try await context.core.commitFiles( + owner: owner, + repository: repository, + sha: sha + ) + tableView.reloadData() + tableView.backgroundView = rows.isEmpty + ? EmptyBackgroundView(title: "No changed files", detail: "This commit does not contain file changes.") + : nil + } catch { + if !Task.isCancelled { show(error: error) } + } + endLoading() + } + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + rows.count + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "file") + ?? UITableViewCell(style: .subtitle, reuseIdentifier: "file") + let row = rows[indexPath.row] + configureTextCell(cell, title: row.path, detail: row.status) + cell.accessoryType = .disclosureIndicator + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let row = rows[indexPath.row] + navigationController?.pushViewController( + DiffViewController( + context: context, + source: .commit( + owner: owner, + repository: repository, + sha: sha, + path: row.path + ) + ), + animated: true + ) + } +} + +enum DiffSource { + case commit(owner: String, repository: String, sha: String, path: String) + case pull(owner: String, repository: String, number: Int64, path: String) +} + +@MainActor +final class DiffViewController: UIViewController { + private let context: AppContext + private let source: DiffSource + private let textView = UITextView() + private let spinner = UIActivityIndicatorView(style: .medium) + private var loadingTask: Task? + + init(context: AppContext, source: DiffSource) { + self.context = context + self.source = source + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemBackground + textView.translatesAutoresizingMaskIntoConstraints = false + textView.isEditable = false + textView.isSelectable = true + textView.alwaysBounceVertical = true + textView.alwaysBounceHorizontal = true + textView.showsHorizontalScrollIndicator = true + textView.textContainer.widthTracksTextView = false + textView.textContainer.lineFragmentPadding = 8 + textView.refreshControl = UIRefreshControl() + textView.refreshControl?.addTarget(self, action: #selector(reload), for: .valueChanged) + view.addSubview(textView) + NSLayoutConstraint.activate([ + textView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + textView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + textView.topAnchor.constraint(equalTo: view.topAnchor), + textView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + beginNavigationLoading(spinner) + reload() + } + + deinit { loadingTask?.cancel() } + + @objc private func reload() { + loadingTask?.cancel() + loadingTask = Task { + do { + let page: DiffPage + switch source { + case let .commit(owner, repository, sha, path): + page = try await context.core.commitDiff( + owner: owner, + repository: repository, + sha: sha, + path: path + ) + case let .pull(owner, repository, number, path): + page = try await context.core.pullDiff( + owner: owner, + repository: repository, + number: number, + path: path + ) + } + title = page.title + textView.attributedText = diffText(page) + } catch { + if !Task.isCancelled { show(error: error) } + } + endNavigationLoading(spinner) + textView.refreshControl?.endRefreshing() + } + } + + private func diffText(_ page: DiffPage) -> NSAttributedString { + let output = NSMutableAttributedString() + let font = UIFont.monospacedSystemFont(ofSize: 12, weight: .regular) + for line in page.lines { + let text = String(format: "%4@ %4@ %@\n", line.oldNumber, line.newNumber, line.text) + let color: UIColor + switch line.kind { + case "addition": color = UIColor.systemGreen.withAlphaComponent(0.16) + case "removal": color = UIColor.systemRed.withAlphaComponent(0.16) + case "hunk": color = UIColor.systemBlue.withAlphaComponent(0.14) + case "header": color = UIColor.systemGray.withAlphaComponent(0.14) + default: color = .clear + } + output.append(NSAttributedString(string: text, attributes: [ + .font: font, + .foregroundColor: UIColor.label, + .backgroundColor: color, + ])) + } + return output + } +} + +private func detailViews( + title: String, + meta: String, + body: String, + comments: [CommentRow], + milestone: String = "" +) -> [UIView] { + let titleLabel = UILabel() + titleLabel.text = title + titleLabel.font = .preferredFont(forTextStyle: .title1) + titleLabel.numberOfLines = 0 + let metaLabel = UILabel() + metaLabel.text = meta + metaLabel.font = .preferredFont(forTextStyle: .subheadline) + metaLabel.textColor = .secondaryLabel + metaLabel.numberOfLines = 0 + let bodyView = markdownView(body) + var views: [UIView] = [titleLabel, metaLabel] + if !milestone.isEmpty { + let milestoneLabel = UILabel() + milestoneLabel.attributedText = symbolText( + "flag.fill", + text: milestone, + font: .preferredFont(forTextStyle: .subheadline), + color: .secondaryLabel + ) + milestoneLabel.accessibilityLabel = "Milestone \(milestone)" + views.append(milestoneLabel) + } + return views + [separator(), bodyView] + commentViews(comments) +} + +private func commentViews(_ comments: [CommentRow]) -> [UIView] { + guard !comments.isEmpty else { return [] } + var views: [UIView] = [sectionLabel("Comments")] + for comment in comments { + let author = UILabel() + author.text = comment.author + author.font = .preferredFont(forTextStyle: .headline) + let date = UILabel() + date.text = comment.meta + date.font = .preferredFont(forTextStyle: .caption1) + date.textColor = .tertiaryLabel + let stack = UIStackView(arrangedSubviews: [author, markdownView(comment.body), date, separator()]) + stack.axis = .vertical + stack.spacing = 6 + views.append(stack) + } + return views +} + +private func markdownView(_ source: String) -> UITextView { + let view = UITextView() + view.attributedText = markdown(source) + view.isEditable = false + view.isSelectable = true + view.isScrollEnabled = false + view.backgroundColor = .clear + view.textContainerInset = .zero + view.textContainer.lineFragmentPadding = 0 + view.adjustsFontForContentSizeCategory = true + return view +} + +private func sectionLabel(_ text: String) -> UILabel { + let label = UILabel() + label.text = text + label.font = .preferredFont(forTextStyle: .headline) + return label +} + +private func separator() -> UIView { + let line = UIView() + line.backgroundColor = .separator + line.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale).isActive = true + return line +} + +private func detailButton(title: String, detail: String, action: @escaping () -> Void) -> UIButton { + var configuration = UIButton.Configuration.plain() + configuration.title = title + configuration.subtitle = detail + configuration.image = UIImage(systemName: "chevron.right") + configuration.imagePlacement = .trailing + configuration.imagePadding = 8 + configuration.contentInsets = .init(top: 10, leading: 0, bottom: 10, trailing: 0) + let button = UIButton(configuration: configuration, primaryAction: UIAction { _ in action() }) + button.contentHorizontalAlignment = .fill + return button +} diff --git a/ios/Sources/ListScreens.swift b/ios/Sources/ListScreens.swift new file mode 100644 index 0000000..488ce10 --- /dev/null +++ b/ios/Sources/ListScreens.swift @@ -0,0 +1,454 @@ +import UIKit + +enum RepositoryMode { + case issues + case commits + case milestones +} + +@MainActor +final class ServersViewController: UITableViewController { + private let context: AppContext + private let destination: RepositoryMode + private var servers: [ServerRow] = [] + + init(context: AppContext, destination: RepositoryMode) { + self.context = context + self.destination = destination + super.init(style: .plain) + title = "Servers" + tableView.backgroundColor = .systemGroupedBackground + tableView.separatorInset = .zero + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + servers = context.core.servers() + tableView.reloadData() + tableView.backgroundView = servers.isEmpty + ? EmptyBackgroundView(title: "No servers", detail: "Add a Gitea server to get started.") + : nil + navigationItem.rightBarButtonItem = UIBarButtonItem( + systemItem: .add, + primaryAction: UIAction { [weak self] _ in self?.showAddServer() } + ) + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + servers.count + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "server") + ?? UITableViewCell(style: .subtitle, reuseIdentifier: "server") + let server = servers[indexPath.row] + configureTextCell(cell, title: server.name, detail: server.url) + cell.accessoryType = .disclosureIndicator + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + do { + try context.selectServer(index: UInt32(indexPath.row)) + navigationController?.setViewControllers([ + RepositoriesViewController(context: context, mode: destination), + ], animated: true) + } catch { + show(error: error) + } + } + + private func showAddServer() { + let controller = AddServerViewController(context: context) { [weak self] in + guard let self else { return } + self.servers = self.context.core.servers() + self.tableView.reloadData() + } + present(UINavigationController(rootViewController: controller), animated: true) + } +} + +@MainActor +final class AddServerViewController: UITableViewController, UITextFieldDelegate { + private let context: AppContext + private let completion: () -> Void + private let nameField = UITextField() + private let urlField = UITextField() + private let tokenField = UITextField() + private var saveButton: UIBarButtonItem! + + init(context: AppContext, completion: @escaping () -> Void) { + self.context = context + self.completion = completion + super.init(style: .insetGrouped) + title = "Add Server" + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.leftBarButtonItem = UIBarButtonItem( + systemItem: .cancel, + primaryAction: UIAction { [weak self] _ in self?.dismiss(animated: true) } + ) + saveButton = UIBarButtonItem( + title: "Add", + style: .done, + target: self, + action: #selector(save) + ) + navigationItem.rightBarButtonItem = saveButton + configure(nameField, placeholder: "Work", contentType: .name) + configure(urlField, placeholder: "https://gitea.example.com", contentType: .URL) + urlField.keyboardType = .URL + urlField.autocapitalizationType = .none + configure(tokenField, placeholder: "Access token", contentType: nil) + tokenField.isSecureTextEntry = true + tokenField.autocapitalizationType = .none + tokenField.returnKeyType = .done + } + + override func numberOfSections(in tableView: UITableView) -> Int { 3 } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 1 } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + ["Name", "Server URL", "Access token"][section] + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + let cell = UITableViewCell(style: .default, reuseIdentifier: nil) + let field = [nameField, urlField, tokenField][indexPath.section] + field.translatesAutoresizingMaskIntoConstraints = false + cell.contentView.addSubview(field) + NSLayoutConstraint.activate([ + field.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 16), + field.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -16), + field.topAnchor.constraint(equalTo: cell.contentView.topAnchor), + field.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor), + cell.contentView.heightAnchor.constraint(greaterThanOrEqualToConstant: 48), + ]) + return cell + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if textField === nameField { urlField.becomeFirstResponder() } + else if textField === urlField { tokenField.becomeFirstResponder() } + else { save() } + return true + } + + @objc private func save() { + view.endEditing(true) + saveButton.isEnabled = false + let spinner = UIActivityIndicatorView(style: .medium) + spinner.startAnimating() + navigationItem.rightBarButtonItem = UIBarButtonItem(customView: spinner) + Task { + do { + let index = try await context.core.addServer( + name: nameField.text ?? "", + url: urlField.text ?? "", + token: tokenField.text ?? "" + ) + try context.didAddServer(index: index) + completion() + dismiss(animated: true) + } catch { + navigationItem.rightBarButtonItem = saveButton + saveButton.isEnabled = true + show(error: error) + } + } + } + + private func configure( + _ field: UITextField, + placeholder: String, + contentType: UITextContentType? + ) { + field.placeholder = placeholder + field.textContentType = contentType + field.clearButtonMode = .whileEditing + field.delegate = self + field.returnKeyType = .next + field.adjustsFontForContentSizeCategory = true + field.font = .preferredFont(forTextStyle: .body) + } +} + +@MainActor +final class RepositoriesViewController: RefreshingTableViewController { + private let context: AppContext + private let mode: RepositoryMode + private var rows: [RepositoryRow] = [] + + init(context: AppContext, mode: RepositoryMode) { + self.context = context + self.mode = mode + super.init() + title = context.core.activeServerName() ?? "Repositories" + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.leftBarButtonItem = UIBarButtonItem( + image: context.symbol("server.rack"), + primaryAction: UIAction { [weak self] _ in + guard let self else { return } + self.navigationController?.pushViewController( + ServersViewController(context: self.context, destination: self.mode), + animated: true + ) + } + ) + loadContent(refreshing: false) + } + + override func loadContent(refreshing: Bool) { + beginLoading(refreshing: refreshing) + loadingTask?.cancel() + loadingTask = Task { + do { + rows = try await context.core.repositories() + tableView.reloadData() + tableView.backgroundView = rows.isEmpty + ? EmptyBackgroundView( + title: "No repositories", + detail: "This account does not own any repositories on this server." + ) + : nil + } catch { + if !Task.isCancelled { show(error: error) } + } + endLoading() + } + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + rows.count + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "repository") + ?? UITableViewCell(style: .subtitle, reuseIdentifier: "repository") + let row = rows[indexPath.row] + configureTextCell(cell, title: row.name, detail: "\(row.description)\n\(row.meta)") + let button = UIButton(type: .system, primaryAction: UIAction { [weak self] _ in + self?.toggleFavorite(row) + }) + button.setImage(context.symbol(row.favorite ? "star.fill" : "star"), for: .normal) + button.tintColor = row.favorite ? .systemYellow : .tertiaryLabel + button.frame.size = CGSize(width: 44, height: 44) + cell.accessoryView = button + return cell + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + 96 + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let row = rows[indexPath.row] + let destination: UIViewController + switch mode { + case .issues: + destination = IssuesViewController( + context: context, + owner: row.owner, + repository: row.name + ) + case .commits: + destination = CommitsViewController( + context: context, + owner: row.owner, + repository: row.name + ) + case .milestones: + destination = MilestonesViewController( + context: context, + owner: row.owner, + repository: row.name + ) + } + navigationController?.pushViewController(destination, animated: true) + } + + private func toggleFavorite(_ row: RepositoryRow) { + do { + rows = try context.core.toggleFavorite(owner: row.owner, repository: row.name) + tableView.reloadData() + } catch { + show(error: error) + } + } +} + +@MainActor +final class HomeViewController: RefreshingTableViewController { + private let context: AppContext + private var page: HomePage? + + init(context: AppContext) { + self.context = context + super.init() + title = "Home" + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + let settings = UIBarButtonItem( + image: context.symbol("gearshape"), + primaryAction: UIAction { [weak self] _ in + guard let self else { return } + self.navigationController?.pushViewController( + SettingsViewController(context: self.context), + animated: true + ) + } + ) + settings.accessibilityLabel = "Settings" + navigationItem.rightBarButtonItem = settings + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + guard page == nil else { return } + loadContent(refreshing: false) + } + + override func loadContent(refreshing: Bool) { + guard context.core.activeServerIndex() != nil else { + tableView.backgroundView = EmptyBackgroundView( + title: "No server selected", + detail: "Open Issues or Repos to select a server or add your first one." + ) + refreshControl?.endRefreshing() + return + } + beginLoading(refreshing: refreshing) + loadingTask?.cancel() + loadingTask = Task { + do { + page = try await context.core.home() + title = page?.serverName + tableView.tableHeaderView = page.map { HeatmapView(page: $0) } + tableView.reloadData() + tableView.backgroundView = nil + } catch { + if !Task.isCancelled { show(error: error) } + } + endLoading() + } + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + page?.activities.count ?? 0 + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "activity") + ?? UITableViewCell(style: .subtitle, reuseIdentifier: "activity") + guard let row = page?.activities[indexPath.row] else { return cell } + configureTextCell( + cell, + title: row.title, + detail: "\(row.detail)\n\(row.meta)", + image: context.symbol(symbolName(for: row.icon)) + ) + cell.accessoryType = row.target.isEmpty ? .none : .disclosureIndicator + return cell + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + 92 + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + if let row = page?.activities[indexPath.row] { context.route(row) } + } + + private func symbolName(for icon: String) -> String { + switch icon { + case let value where value.contains("pull"): return "arrow.triangle.pull" + case let value where value.contains("issue"): return "exclamationmark.circle" + case let value where value.contains("branch"): return "arrow.triangle.branch" + case let value where value.contains("tag"): return "tag" + case "push": return "arrow.up.circle" + case "release": return "shippingbox" + default: return "books.vertical" + } + } +} + +final class HeatmapView: UIView { + private let cells: [HeatCell] + + init(page: HomePage) { + cells = page.heatCells + super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 132)) + backgroundColor = .systemBackground + let title = UILabel(frame: CGRect(x: 16, y: 12, width: 300, height: 22)) + title.text = "Activity · last 12 months" + title.font = .preferredFont(forTextStyle: .subheadline) + title.textColor = .secondaryLabel + addSubview(title) + let total = UILabel(frame: CGRect(x: 16, y: 104, width: 300, height: 18)) + total.text = "\(page.contributionCount) contributions" + total.font = .preferredFont(forTextStyle: .caption1) + total.textColor = .tertiaryLabel + addSubview(total) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func draw(_ rect: CGRect) { + let width = max(4, min(6, (bounds.width - 32) / 55)) + let gap = width + 1 + for cell in cells { + let colors: [UIColor] = [ + .systemGray5, + UIColor(red: 0.72, green: 0.85, blue: 0.96, alpha: 1), + UIColor(red: 0.45, green: 0.71, blue: 0.91, alpha: 1), + UIColor(red: 0.15, green: 0.55, blue: 0.83, alpha: 1), + UIColor(red: 0.04, green: 0.41, blue: 0.72, alpha: 1), + ] + colors[Int(min(cell.level, 4))].setFill() + UIBezierPath( + roundedRect: CGRect( + x: 16 + CGFloat(cell.week) * gap, + y: 43 + CGFloat(cell.day) * gap, + width: width, + height: width + ), + cornerRadius: 1 + ).fill() + } + } +} diff --git a/ios/Sources/SettingsViewController.swift b/ios/Sources/SettingsViewController.swift new file mode 100644 index 0000000..d74f1c4 --- /dev/null +++ b/ios/Sources/SettingsViewController.swift @@ -0,0 +1,59 @@ +import UIKit + +@MainActor +final class SettingsViewController: UITableViewController { + private let context: AppContext + private let appearanceControl = UISegmentedControl(items: ["Auto", "Light", "Dark"]) + + init(context: AppContext) { + self.context = context + super.init(style: .insetGrouped) + title = "Settings" + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + override func viewDidLoad() { + super.viewDidLoad() + let settings = context.core.settings() + appearanceControl.selectedSegmentIndex = Int(settings.appearance) + appearanceControl.addTarget(self, action: #selector(appearanceChanged), for: .valueChanged) + } + + override func numberOfSections(in tableView: UITableView) -> Int { 1 } + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 1 } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + "Appearance" + } + + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + "Follow iOS automatically or choose a fixed appearance." + } + + override func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + let cell = UITableViewCell(style: .default, reuseIdentifier: nil) + appearanceControl.translatesAutoresizingMaskIntoConstraints = false + cell.contentView.addSubview(appearanceControl) + NSLayoutConstraint.activate([ + appearanceControl.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 16), + appearanceControl.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -16), + appearanceControl.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 8), + appearanceControl.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -8), + ]) + return cell + } + + @objc private func appearanceChanged() { + do { + try context.core.setAppearance(index: UInt32(appearanceControl.selectedSegmentIndex)) + context.applyAppearance() + } catch { + show(error: error) + } + } +} diff --git a/ios/Sources/Support.swift b/ios/Sources/Support.swift new file mode 100644 index 0000000..9b03e04 --- /dev/null +++ b/ios/Sources/Support.swift @@ -0,0 +1,140 @@ +import UIKit + +func errorAlert(_ message: String) -> UIAlertController { + let alert = UIAlertController(title: "Something went wrong", message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + return alert +} + +extension UIViewController { + func show(error: Error) { + present(errorAlert(error.localizedDescription), animated: true) + } + + func beginNavigationLoading(_ spinner: UIActivityIndicatorView) { + guard navigationItem.rightBarButtonItem == nil else { return } + spinner.startAnimating() + navigationItem.rightBarButtonItem = UIBarButtonItem(customView: spinner) + } + + func endNavigationLoading(_ spinner: UIActivityIndicatorView) { + spinner.stopAnimating() + if navigationItem.rightBarButtonItem?.customView === spinner { + navigationItem.rightBarButtonItem = nil + } + } +} + +@MainActor +class RefreshingTableViewController: UITableViewController { + private let spinner = UIActivityIndicatorView(style: .medium) + var loadingTask: Task? + + init() { + super.init(style: .plain) + tableView.backgroundColor = .systemGroupedBackground + tableView.separatorInset = .zero + tableView.refreshControl = UIRefreshControl() + tableView.refreshControl?.addTarget(self, action: #selector(refreshRequested), for: .valueChanged) + navigationItem.largeTitleDisplayMode = .never + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } + + deinit { loadingTask?.cancel() } + + func loadContent(refreshing: Bool) {} + + func beginLoading(refreshing: Bool) { + if !refreshing { beginNavigationLoading(spinner) } + } + + func endLoading() { + endNavigationLoading(spinner) + refreshControl?.endRefreshing() + } + + @objc private func refreshRequested() { + loadContent(refreshing: true) + } +} + +final class EmptyBackgroundView: UIView { + init(title: String, detail: String) { + super.init(frame: .zero) + let titleLabel = UILabel() + titleLabel.text = title + titleLabel.font = .preferredFont(forTextStyle: .title2) + titleLabel.textAlignment = .center + let detailLabel = UILabel() + detailLabel.text = detail + detailLabel.font = .preferredFont(forTextStyle: .body) + detailLabel.textColor = .secondaryLabel + detailLabel.textAlignment = .center + detailLabel.numberOfLines = 0 + let stack = UIStackView(arrangedSubviews: [titleLabel, detailLabel]) + stack.axis = .vertical + stack.spacing = 8 + stack.translatesAutoresizingMaskIntoConstraints = false + addSubview(stack) + NSLayoutConstraint.activate([ + stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 28), + stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -28), + stack.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -40), + ]) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") } +} + +extension UIColor { + convenience init?(hex: String) { + let value = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#")) + guard value.count == 6, let rgb = Int(value, radix: 16) else { return nil } + self.init( + red: CGFloat((rgb >> 16) & 0xff) / 255, + green: CGFloat((rgb >> 8) & 0xff) / 255, + blue: CGFloat(rgb & 0xff) / 255, + alpha: 1 + ) + } +} + +func markdown(_ source: String, textStyle: UIFont.TextStyle = .body) -> NSAttributedString { + let attributed = (try? AttributedString( + markdown: source, + options: .init(interpretedSyntax: .full) + )) ?? AttributedString(source) + var mutable = attributed + mutable.font = .preferredFont(forTextStyle: textStyle) + mutable.foregroundColor = UIColor.label + return NSAttributedString(mutable) +} + +func configureTextCell(_ cell: UITableViewCell, title: String, detail: String, image: UIImage? = nil) { + var content = cell.defaultContentConfiguration() + content.text = title + content.secondaryText = detail + content.secondaryTextProperties.numberOfLines = 2 + content.image = image + content.imageProperties.tintColor = .tintColor + cell.contentConfiguration = content + cell.backgroundColor = .systemBackground +} + +func symbolText(_ symbol: String, text: String, font: UIFont, color: UIColor) -> NSAttributedString { + let output = NSMutableAttributedString() + if let image = UIImage(systemName: symbol)?.withTintColor(color, renderingMode: .alwaysOriginal) { + let attachment = NSTextAttachment(image: image) + attachment.bounds = CGRect(x: 0, y: -2, width: font.pointSize, height: font.pointSize) + output.append(NSAttributedString(attachment: attachment)) + output.append(NSAttributedString(string: " ")) + } + output.append(NSAttributedString(string: text, attributes: [ + .font: font, + .foregroundColor: color, + ])) + return output +} diff --git a/ios/build_for_ios_with_cargo.bash b/ios/build_rust_core.bash similarity index 52% rename from ios/build_for_ios_with_cargo.bash rename to ios/build_rust_core.bash index 89768cd..bff9175 100755 --- a/ios/build_for_ios_with_cargo.bash +++ b/ios/build_rust_core.bash @@ -5,7 +5,6 @@ export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PAT export RUSTC="$(rustup which --toolchain stable rustc)" export RUSTDOC="$(rustup which --toolchain stable rustdoc)" export CARGO_TARGET_DIR="$DERIVED_FILE_DIR/cargo" -export CARGO_PROFILE_RELEASE_DEBUG="${CARGO_PROFILE_RELEASE_DEBUG:-1}" if [[ "$CONFIGURATION" == "Debug" ]]; then profile=debug @@ -13,14 +12,7 @@ else profile=release fi -if [[ "${LLVM_TARGET_TRIPLE_SUFFIX:-}" == "-simulator" ]]; then - separator=$'\x1f' - entitlement_flags="-Clink-arg=-Wl,-sectcreate,__TEXT,__entitlements,$LD_ENTITLEMENTS_SECTION" - entitlement_flags+="$separator-Clink-arg=-Wl,-sectcreate,__TEXT,__ents_der,$LD_ENTITLEMENTS_SECTION_DER" - export CARGO_ENCODED_RUSTFLAGS="${CARGO_ENCODED_RUSTFLAGS:+$CARGO_ENCODED_RUSTFLAGS$separator}$entitlement_flags" -fi - -executables=() +libraries=() for arch in $ARCHS; do if [[ "$arch" == "arm64" && "${LLVM_TARGET_TRIPLE_SUFFIX:-}" == "-simulator" ]]; then target=aarch64-apple-ios-sim @@ -34,15 +26,12 @@ for arch in $ARCHS; do fi if [[ "$profile" == "debug" ]]; then - rustup run stable cargo build --locked --target "$target" --bin "$1" + rustup run stable cargo build --locked --target "$target" --package gotcha-app --lib else - rustup run stable cargo build --locked --release --target "$target" --bin "$1" + rustup run stable cargo build --locked --release --target "$target" --package gotcha-app --lib fi - executables+=("$CARGO_TARGET_DIR/$target/$profile/$1") + libraries+=("$CARGO_TARGET_DIR/$target/$profile/libgotcha_core.a") done -lipo -create -output "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" "${executables[@]}" - -if [[ -n "${DWARF_DSYM_FOLDER_PATH:-}" && -n "${DWARF_DSYM_FILE_NAME:-}" ]]; then - dsymutil "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" -o "$DWARF_DSYM_FOLDER_PATH/$DWARF_DSYM_FILE_NAME" -fi +mkdir -p "$DERIVED_FILE_DIR/rust" +lipo -create -output "$DERIVED_FILE_DIR/rust/libgotcha_core.a" "${libraries[@]}" diff --git a/ios/project.yml b/ios/project.yml index 8d908d4..e1310a1 100644 --- a/ios/project.yml +++ b/ios/project.yml @@ -10,7 +10,14 @@ targets: deploymentTarget: "17.0" settings: PRODUCT_BUNDLE_IDENTIFIER: de.rfc1437.gotcha + DEVELOPMENT_TEAM: MU22FMRGK8 + CODE_SIGN_STYLE: Automatic + TARGETED_DEVICE_FAMILY: "1" CODE_SIGN_ENTITLEMENTS: Gotcha.entitlements + SWIFT_VERSION: "5.0" + SWIFT_OBJC_BRIDGING_HEADER: Gotcha-Bridging-Header.h + LIBRARY_SEARCH_PATHS: "$(inherited) $(DERIVED_FILE_DIR)/rust" + OTHER_LDFLAGS: "$(inherited) -lgotcha_core" info: path: Info.plist properties: @@ -20,10 +27,12 @@ targets: - UIInterfaceOrientationPortrait sources: - Assets.xcassets - postCompileScripts: - - name: Build Rust app + - Sources + - Generated/gotcha_core.swift + preBuildScripts: + - name: Build Rust core basedOnDependencyAnalysis: false script: | - ./build_for_ios_with_cargo.bash gotcha-app + ./build_rust_core.bash outputFiles: - - $(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH) + - $(DERIVED_FILE_DIR)/rust/libgotcha_core.a