From 58132d2cf5f1e9d96c10a13cc50b0cfd871aaec2 Mon Sep 17 00:00:00 2001 From: kieran Date: Thu, 31 Oct 2024 15:56:29 +0000 Subject: [PATCH] feat: native layer code feat: login kinds (nsec) feat: write chat messages --- Cargo.lock | 889 ++++++++++++++++++++++++++++++++++-- Cargo.toml | 14 +- src/android.rs | 90 ++++ src/app.rs | 39 +- src/bin/zap_stream_app.rs | 96 +++- src/lib.rs | 97 ++-- src/login.rs | 85 ++++ src/route/home.rs | 2 +- src/route/login.rs | 69 ++- src/route/mod.rs | 75 ++- src/route/stream.rs | 6 +- src/services/ndb_wrapper.rs | 4 + src/theme.rs | 4 +- src/widgets/button.rs | 4 +- src/widgets/chat.rs | 2 +- src/widgets/header.rs | 7 +- src/widgets/mod.rs | 4 +- src/widgets/stream_tile.rs | 4 +- src/widgets/stream_title.rs | 2 +- src/widgets/text_input.rs | 46 ++ src/widgets/write_chat.rs | 37 +- 21 files changed, 1396 insertions(+), 180 deletions(-) create mode 100644 src/android.rs create mode 100644 src/login.rs create mode 100644 src/widgets/text_input.rs diff --git a/Cargo.lock b/Cargo.lock index b17b16c..1ad9b35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,96 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "accesskit" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b76d84ee70e30a4a7e39ab9018e2b17a6a09e31084176cc7c0b2dec036ba45" + +[[package]] +name = "accesskit_atspi_common" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5393c75d4666f580f4cac0a968bc97c36076bb536a129f28210dac54ee127ed" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "serde", + "thiserror", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a12dc159d52233c43d9fe5415969433cbdd52c3d6e0df51bda7d447427b9986" +dependencies = [ + "accesskit", + "immutable-chunkmap", +] + +[[package]] +name = "accesskit_macos" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc6c1ecd82053d127961ad80a8beaa6004fb851a3a5b96506d7a6bd462403f6" +dependencies = [ + "accesskit", + "accesskit_consumer", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", +] + +[[package]] +name = "accesskit_unix" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be7f5cf6165be10a54b2655fa2e0e12b2509f38ed6fc43e11c31fdb7ee6230bb" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "974e96c347384d9133427167fb8a58c340cb0496988dacceebdc1ed27071023b" +dependencies = [ + "accesskit", + "accesskit_consumer", + "paste", + "static_assertions", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "accesskit_winit" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aea3522719f1c44564d03e9469a8e2f3a98b3a8a880bd66d0789c6b9c4a669dd" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -125,6 +215,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "android-ndk-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ec6ef28479835db378e2202b3911608e2269405e924fabb02ac3c8fb50284fe" + [[package]] name = "android-properties" version = "0.2.2" @@ -211,6 +307,138 @@ dependencies = [ "libloading", ] +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", +] + +[[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.85", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[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.83" @@ -219,7 +447,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -278,6 +506,57 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atspi" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be534b16650e35237bb1ed189ba2aab86ce65e88cc84c66f4935ba38575cecbf" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1909ed2dc01d0a17505d89311d192518507e8a056a48148e3598fef5e7bb6ba7" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-connection" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "430c5960624a4baaa511c9c0fcc2218e3b58f5dbcc47e6190cafee344b873333" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite", + "zbus", +] + +[[package]] +name = "atspi-proxies" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" +dependencies = [ + "atspi-common", + "serde", + "zbus", + "zvariant", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -346,7 +625,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.79", + "syn 2.0.85", "which", ] @@ -365,7 +644,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -509,6 +788,19 @@ dependencies = [ "objc2", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -532,7 +824,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -675,6 +967,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.6", ] @@ -914,6 +1207,41 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.85", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.85", +] + [[package]] name = "dasp_sample" version = "0.11.0" @@ -932,6 +1260,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "digest" version = "0.10.7" @@ -1013,14 +1351,12 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "pollster", "raw-window-handle", "static_assertions", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "web-time", - "wgpu", "winapi", "windows-sys 0.52.0", "winit", @@ -1032,6 +1368,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" dependencies = [ + "accesskit", "ahash", "emath", "epaint", @@ -1079,6 +1416,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a9c430f4f816340e8e8c1b20eec274186b1be6bc4c7dfc467ed50d57abc36c6" dependencies = [ + "accesskit_winit", "ahash", "arboard", "egui", @@ -1132,6 +1470,33 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + [[package]] name = "env_filter" version = "0.1.2" @@ -1200,6 +1565,33 @@ version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + [[package]] name = "fdeflate" version = "0.3.5" @@ -1289,7 +1681,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -1343,6 +1735,30 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -1364,6 +1780,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1582,6 +1999,12 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1630,6 +2053,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex-conservative" version = "0.1.2" @@ -1801,6 +2230,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1829,6 +2264,26 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" +[[package]] +name = "immutable-chunkmap" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -1837,6 +2292,7 @@ checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown 0.15.0", + "serde", ] [[package]] @@ -2160,7 +2616,7 @@ dependencies = [ "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap", + "indexmap 2.6.0", "log", "rustc-hash 1.1.0", "spirv", @@ -2234,6 +2690,19 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a88da9dd148bbcdce323dd6ac47d369b4769d4a3b78c6c52389b9269f77932" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", + "memoffset", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -2383,6 +2852,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.4.2" @@ -2391,7 +2866,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -2421,7 +2896,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -2709,6 +3184,16 @@ dependencies = [ "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 = "overload" version = "0.1.1" @@ -2724,6 +3209,12 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -2803,7 +3294,7 @@ checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -2818,6 +3309,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -2852,12 +3354,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "pollster" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" - [[package]] name = "poly1305" version = "0.8.0" @@ -2869,6 +3365,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2901,7 +3403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -2928,6 +3430,16 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quick-xml" version = "0.36.2" @@ -3386,22 +3898,22 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -3410,13 +3922,24 @@ version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ - "indexmap", + "indexmap 2.6.0", "itoa", "memchr", "ryu", "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3429,6 +3952,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.6.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.85", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3466,6 +4019,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -3602,6 +4164,12 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -3631,9 +4199,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", @@ -3649,6 +4217,19 @@ dependencies = [ "futures-core", ] +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -3675,7 +4256,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -3688,6 +4269,37 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -3753,7 +4365,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -3818,7 +4430,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap", + "indexmap 2.6.0", "toml_datetime", "winnow", ] @@ -3848,7 +4460,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -3933,6 +4545,17 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + [[package]] name = "unicode-bidi" version = "0.3.17" @@ -4102,7 +4725,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", "wasm-bindgen-shared", ] @@ -4136,7 +4759,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4240,7 +4863,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.36.2", "quote", ] @@ -4314,7 +4937,6 @@ dependencies = [ "document-features", "js-sys", "log", - "naga", "parking_lot", "profiling", "raw-window-handle", @@ -4339,7 +4961,7 @@ dependencies = [ "bitflags 2.6.0", "cfg_aliases 0.1.1", "document-features", - "indexmap", + "indexmap 2.6.0", "log", "naga", "once_cell", @@ -4363,7 +4985,6 @@ dependencies = [ "arrayvec", "ash", "bitflags 2.6.0", - "block", "cfg_aliases 0.1.1", "core-graphics-types", "glow 0.13.1", @@ -4475,6 +5096,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -4494,6 +5125,41 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + [[package]] name = "windows-registry" version = "0.2.0" @@ -4837,6 +5503,16 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "xkbcommon-dl" version = "0.4.2" @@ -4873,6 +5549,7 @@ name = "zap_stream_app" version = "0.1.0" dependencies = [ "android-activity", + "android-ndk-sys", "android_logger", "anyhow", "async-trait", @@ -4891,12 +5568,113 @@ dependencies = [ "pretty_env_logger", "reqwest", "resvg", + "serde", + "serde_with", "sha2", "tokio", "uuid", "winit", ] +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.85", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" +dependencies = [ + "quick-xml 0.30.0", + "serde", + "static_assertions", + "zbus_names", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -4915,7 +5693,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -4923,3 +5701,40 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.85", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] diff --git a/Cargo.toml b/Cargo.toml index a220966..ec93755 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ crate-type = ["lib", "cdylib"] [dependencies] tokio = { version = "1.40.0", features = ["fs", "rt-multi-thread", "rt"] } egui = { version = "0.29.1" } -eframe = { version = "0.29.1", default-features = false, features = ["glow", "wgpu", "wayland", "x11", "android-native-activity"] } nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", version = "0.3.4" } nostr-sdk = { version = "0.35.0", features = ["all-nips"] } log = "0.4.22" @@ -25,12 +24,19 @@ sha2 = "0.10.8" reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls-native-roots"] } itertools = "0.13.0" lru = "0.12.5" +resvg = { version = "0.44.0", default-features = false } egui-video = { git = "https://github.com/v0l/egui-video.git", rev = "ced65d0bb4d2d144b87c70518a04b767ba37c0c1" } -resvg = { version = "0.44.0", default-features = false } +serde = { version = "1.0.214", features = ["derive"] } +serde_with = { version = "3.11.0", features = ["hex"] } #egui-video = { path = "../egui-video" } +[target.'cfg(not(target_os = "android"))'.dependencies] +eframe = { version = "0.29.1" } + [target.'cfg(target_os = "android")'.dependencies] +eframe = { version = "0.29.1", features = ["android-native-activity"] } android_logger = "0.14.1" -android-activity = { version = "0.6.0", features = ["native-activity"] } -winit = { version = "0.30.5", features = ["android-native-activity"] } +android-activity = { version = "0.6.0" } +winit = { version = "0.30.5" } +android-ndk-sys = "0.2.0" diff --git a/src/android.rs b/src/android.rs new file mode 100644 index 0000000..b6bc65c --- /dev/null +++ b/src/android.rs @@ -0,0 +1,90 @@ +use crate::app::{NativeLayer, NativeSecureStorage, ZapStreamApp}; +use crate::av_log_redirect; +use eframe::Renderer; +use egui::{Margin, ViewportBuilder}; +use std::ops::Div; +use winit::platform::android::activity::AndroidApp; +use winit::platform::android::EventLoopBuilderExtAndroid; + +pub fn start_android(app: AndroidApp) { + std::env::set_var("RUST_BACKTRACE", "full"); + android_logger::init_once( + android_logger::Config::default().with_max_level(log::LevelFilter::Info), + ); + unsafe { + egui_video::ffmpeg_sys_the_third::av_log_set_callback(Some(av_log_redirect)); + } + + let mut options = eframe::NativeOptions::default(); + options.renderer = Renderer::Glow; + + options.viewport = ViewportBuilder::default() + .with_active(true) + .with_always_on_top() + .with_fullscreen(true); + + let app_clone_for_event_loop = app.clone(); + options.event_loop_builder = Some(Box::new(move |builder| { + builder.with_android_app(app_clone_for_event_loop); + })); + + let data_path = app + .external_data_path() + .expect("external data path") + .to_path_buf(); + + if let Err(e) = eframe::run_native( + "zap.stream", + options, + Box::new(move |cc| Ok(Box::new(ZapStreamApp::new(cc, data_path, Box::new(app))))), + ) { + eprintln!("{}", e); + } +} + +impl NativeLayer for AndroidApp { + fn frame_margin(&self) -> Margin { + if let Some(wd) = self.native_window() { + let (w, h) = (wd.width(), wd.height()); + let c_rect = self.content_rect(); + let dpi = self.config().density().unwrap_or(160); + let dpi_scale = dpi as f32 / 160.0; + // TODO: this calc is weird but seems to work on my phone + Margin { + bottom: (h - c_rect.bottom) as f32, + left: c_rect.left as f32, + right: (w - c_rect.right) as f32, + top: (c_rect.top - (h - c_rect.bottom)) as f32, + } + .div(dpi_scale) + } else { + Margin::ZERO + } + } + + fn show_keyboard(&self) { + self.show_soft_input(true); + } + + fn hide_keyboard(&self) { + self.hide_soft_input(true); + } + + fn secure_storage(&self) -> Box { + Box::new(self.clone()) + } +} + +impl NativeSecureStorage for AndroidApp { + fn get(&self, k: &str) -> Option { + None + } + + fn set(&mut self, k: &str, v: &str) -> bool { + false + } + + fn remove(&mut self, k: &str) -> bool { + false + } +} diff --git a/src/app.rs b/src/app.rs index 57e8254..6c0e130 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,18 +6,30 @@ use nostr_sdk::Client; use nostrdb::{Config, Ndb}; use std::path::PathBuf; -pub struct ZapStreamApp { +pub struct ZapStreamApp { client: Client, - router: Router, - config: T, + router: Router, + native_layer: T, } -/// Trait to wrap native configuration layers -pub trait AppConfig { +pub trait NativeLayerOps { + /// Get any display layout margins fn frame_margin(&self) -> Margin; + /// Show the keyboard on the screen + fn show_keyboard(&self); + /// Hide on screen keyboard + fn hide_keyboard(&self); + fn get(&self, k: &str) -> Option; + fn set(&mut self, k: &str, v: &str) -> bool; + fn remove(&mut self, k: &str) -> bool; + fn get_obj(&self, k: &str) -> Option; + fn set_obj(&mut self, k: &str, v: &T) -> bool; } -impl ZapStreamApp { +impl ZapStreamApp +where + T: NativeLayerOps + Clone, +{ pub fn new(cc: &CreationContext, data_path: PathBuf, config: T) -> Self { let client = Client::builder() .database(MemoryDatabase::with_opts(Default::default())) @@ -48,21 +60,28 @@ impl ZapStreamApp { let ndb = Ndb::new(ndb_path.to_str().unwrap(), &ndb_config).unwrap(); + let cfg = config.clone(); Self { client: client.clone(), - router: Router::new(data_path, cc.egui_ctx.clone(), client.clone(), ndb.clone()), - config, + router: Router::new( + data_path, + cc.egui_ctx.clone(), + client.clone(), + ndb.clone(), + cfg, + ), + native_layer: config, } } } impl App for ZapStreamApp where - T: AppConfig, + T: NativeLayerOps, { fn update(&mut self, ctx: &Context, frame: &mut Frame) { let mut app_frame = egui::containers::Frame::default(); - let margin = self.config.frame_margin(); + let margin = self.native_layer.frame_margin(); app_frame.inner_margin = margin; app_frame.stroke.color = Color32::BLACK; diff --git a/src/bin/zap_stream_app.rs b/src/bin/zap_stream_app.rs index 9858c29..e5075ec 100644 --- a/src/bin/zap_stream_app.rs +++ b/src/bin/zap_stream_app.rs @@ -1,20 +1,29 @@ use eframe::Renderer; use egui::{Margin, Vec2}; +use nostr_sdk::serde_json; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::collections::HashMap; +use std::io::{Read, Write}; +use std::ops::Deref; use std::path::PathBuf; -use zap_stream_app::app::{AppConfig, ZapStreamApp}; +use std::sync::{Arc, RwLock}; +use zap_stream_app::app::{NativeLayerOps, ZapStreamApp}; +use zap_stream_app::av_log_redirect; #[tokio::main] async fn main() { pretty_env_logger::init(); - // TODO: redirect FFMPEG logs to log file (noisy) - + unsafe { + egui_video::ffmpeg_sys_the_third::av_log_set_callback(Some(av_log_redirect)); + } let mut options = eframe::NativeOptions::default(); options.renderer = Renderer::Glow; options.viewport = options.viewport.with_inner_size(Vec2::new(360., 720.)); - let config = DesktopApp; let data_path = PathBuf::from("./.data"); + let config = DesktopApp::new(data_path.clone()); let _res = eframe::run_native( "zap.stream", options, @@ -22,10 +31,85 @@ async fn main() { ); } -struct DesktopApp; +#[derive(Clone)] +pub struct DesktopApp { + data_path: PathBuf, + data: Arc>>, +} -impl AppConfig for DesktopApp { +impl DesktopApp { + pub fn new(data_path: PathBuf) -> Self { + let mut r = Self { + data_path, + data: Arc::new(RwLock::new(HashMap::new())), + }; + r.load(); + r + } + + fn storage_file_path(&self) -> PathBuf { + self.data_path.join("kv.json") + } + + fn load(&mut self) { + let path = self.storage_file_path(); + if path.exists() { + let mut file = std::fs::File::open(path).unwrap(); + let mut data = Vec::new(); + file.read_to_end(&mut data).unwrap(); + if let Ok(d) = serde_json::from_slice(data.as_slice()) { + self.data = Arc::new(RwLock::new(d)); + } + } + } + + fn save(&self) { + let path = self.storage_file_path(); + let mut file = std::fs::File::create(path).unwrap(); + let json = serde_json::to_string_pretty(self.data.read().unwrap().deref()).unwrap(); + file.write_all(json.as_bytes()).unwrap(); + } +} + +impl NativeLayerOps for DesktopApp { fn frame_margin(&self) -> Margin { Margin::ZERO } + + fn show_keyboard(&self) { + // nothing to do + } + + fn hide_keyboard(&self) { + // nothing to do + } + fn get(&self, k: &str) -> Option { + self.data.read().unwrap().get(k).cloned() + } + + fn set(&mut self, k: &str, v: &str) -> bool { + self.data + .write() + .unwrap() + .insert(k.to_owned(), v.to_owned()) + .is_none() + } + + fn remove(&mut self, k: &str) -> bool { + self.data.write().unwrap().remove(k).is_some() + } + + fn get_obj(&self, k: &str) -> Option { + serde_json::from_str(self.get(k)?.as_str()).ok() + } + + fn set_obj(&mut self, k: &str, v: &T) -> bool { + self.set(k, serde_json::to_string(v).unwrap().as_str()) + } +} + +impl Drop for DesktopApp { + fn drop(&mut self) { + self.save(); + } } diff --git a/src/lib.rs b/src/lib.rs index 18a0540..2ab9fa4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ +#[cfg(target_os = "android")] +mod android; pub mod app; mod link; +mod login; mod note_store; mod note_util; mod route; @@ -8,65 +11,51 @@ mod stream_info; mod theme; mod widgets; -use crate::app::{AppConfig, ZapStreamApp}; -use eframe::Renderer; -use egui::{Margin, ViewportBuilder}; -use std::ops::{Div, Mul}; #[cfg(target_os = "android")] -use winit::platform::android::activity::AndroidApp; +use android_activity::AndroidApp; +use log::log; +use std::ffi::CStr; +use std::ptr; + +#[cfg(not(target_os = "android"))] +type VaList = *mut egui_video::ffmpeg_sys_the_third::__va_list_tag; #[cfg(target_os = "android")] -use winit::platform::android::EventLoopBuilderExtAndroid; +type VaList = [u64; 4]; + +#[no_mangle] +pub unsafe extern "C" fn av_log_redirect( + av_class: *mut libc::c_void, + level: libc::c_int, + fmt: *const libc::c_char, + args: VaList, +) { + use egui_video::ffmpeg_sys_the_third::*; + let log_level = match level { + AV_LOG_DEBUG => log::Level::Debug, + AV_LOG_WARNING => log::Level::Warn, + AV_LOG_INFO => log::Level::Info, + AV_LOG_ERROR => log::Level::Error, + AV_LOG_PANIC => log::Level::Error, + AV_LOG_FATAL => log::Level::Error, + _ => log::Level::Trace, + }; + let mut buf: [u8; 1024] = [0; 1024]; + let mut prefix: libc::c_int = 1; + av_log_format_line( + av_class, + level, + fmt, + args, + buf.as_mut_ptr() as *mut libc::c_char, + 1024, + ptr::addr_of_mut!(prefix), + ); + log!(target: "ffmpeg", log_level, "{}", CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_str().unwrap().trim()); +} #[cfg(target_os = "android")] #[no_mangle] #[tokio::main] pub async fn android_main(app: AndroidApp) { - std::env::set_var("RUST_BACKTRACE", "full"); - android_logger::init_once( - android_logger::Config::default().with_max_level(log::LevelFilter::Info), - ); - - let mut options = eframe::NativeOptions::default(); - options.renderer = Renderer::Glow; - - options.viewport = ViewportBuilder::default().with_fullscreen(true); - - let app_clone_for_event_loop = app.clone(); - options.event_loop_builder = Some(Box::new(move |builder| { - builder.with_android_app(app_clone_for_event_loop); - })); - - let data_path = app - .external_data_path() - .expect("external data path") - .to_path_buf(); - - let app = app.clone(); - let _res = eframe::run_native( - "zap.stream", - options, - Box::new(move |cc| Ok(Box::new(ZapStreamApp::new(cc, data_path, app)))), - ); -} - -#[cfg(target_os = "android")] -impl AppConfig for AndroidApp { - fn frame_margin(&self) -> Margin { - if let Some(wd) = self.native_window() { - let (w, h) = (wd.width(), wd.height()); - let c_rect = self.content_rect(); - let dpi = self.config().density().unwrap_or(160); - let dpi_scale = dpi as f32 / 160.0; - // TODO: this calc is weird but seems to work on my phone - Margin { - bottom: (h - c_rect.bottom) as f32, - left: c_rect.left as f32, - right: (w - c_rect.right) as f32, - top: (c_rect.top - (h - c_rect.bottom)) as f32, - } - .div(dpi_scale) - } else { - Margin::ZERO - } - } + android::start_android(app); } diff --git a/src/login.rs b/src/login.rs new file mode 100644 index 0000000..d0b41c7 --- /dev/null +++ b/src/login.rs @@ -0,0 +1,85 @@ +use crate::app::NativeLayerOps; +use crate::link::NostrLink; +use anyhow::Error; +use nostr_sdk::secp256k1::{Keypair, XOnlyPublicKey}; +use nostr_sdk::{Event, EventBuilder, Keys, Kind, SecretKey, Tag, UnsignedEvent}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use std::ops::Deref; + +#[serde_as] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum LoginKind { + PublicKey { + #[serde_as(as = "serde_with::hex::Hex")] + key: [u8; 32], + }, + PrivateKey { + #[serde_as(as = "serde_with::hex::Hex")] + key: [u8; 32], + }, + LoggedOut, +} + +pub struct Login { + kind: LoginKind, +} + +impl Login { + pub fn new() -> Self { + Self { + kind: LoginKind::LoggedOut, + } + } + + pub fn load(&mut self, storage: &T) { + if let Some(k) = storage.get_obj("login") { + self.kind = k; + } + } + + pub fn save(&mut self, storage: &mut T) { + storage.set_obj("login", &self.kind); + } + + pub fn login(&mut self, kind: LoginKind) { + self.kind = kind; + } + + pub fn is_logged_in(&self) -> bool { + !matches!(self.kind, LoginKind::LoggedOut) + } + + pub fn public_key(&self) -> Option<[u8; 32]> { + match self.kind { + LoginKind::PublicKey { key } => Some(key), + LoginKind::PrivateKey { key } => { + // TODO: wow this is annoying + let sk = Keypair::from_seckey_slice(nostr_sdk::SECP256K1.deref(), key.as_slice()) + .unwrap(); + Some(XOnlyPublicKey::from_keypair(&sk).0.serialize()) + } + _ => None, + } + } + + fn secret_key(&self) -> Result { + if let LoginKind::PrivateKey { key } = self.kind { + Ok(Keys::new(SecretKey::from_slice(key.as_slice())?)) + } else { + anyhow::bail!("No private key"); + } + } + + pub fn sign_event(&self, ev: UnsignedEvent) -> Result { + let secret = self.secret_key()?; + ev.sign(&secret).map_err(Error::new) + } + + pub fn write_live_chat_msg(&self, link: &NostrLink, msg: &str) -> Result { + let secret = self.secret_key()?; + EventBuilder::new(Kind::LiveEventMessage, msg, [Tag::parse(&link.to_tag())?]) + .to_event(&secret) + .map_err(Error::new) + } +} diff --git a/src/route/home.rs b/src/route/home.rs index 65b7230..0b9f42d 100644 --- a/src/route/home.rs +++ b/src/route/home.rs @@ -27,7 +27,7 @@ impl HomePage { } impl NostrWidget for HomePage { - fn render(&mut self, ui: &mut Ui, services: &RouteServices<'_>) -> Response { + fn render(&mut self, ui: &mut Ui, services: &mut RouteServices<'_>) -> Response { let new_notes = services.ndb.poll(&self.sub, 100); new_notes .iter() diff --git a/src/route/login.rs b/src/route/login.rs index 99bc19d..41e1564 100644 --- a/src/route/login.rs +++ b/src/route/login.rs @@ -1,6 +1,7 @@ -use crate::route::{RouteAction, RouteServices, Routes}; -use crate::widgets::{Button, NostrWidget}; -use egui::{Color32, Response, RichText, Ui}; +use crate::login::LoginKind; +use crate::route::{RouteServices, Routes}; +use crate::widgets::{Button, NativeTextInput, NostrWidget}; +use egui::{Color32, Frame, Margin, Response, RichText, Ui}; use nostr_sdk::util::hex; pub struct LoginPage { @@ -18,27 +19,49 @@ impl LoginPage { } impl NostrWidget for LoginPage { - fn render(&mut self, ui: &mut Ui, services: &RouteServices<'_>) -> Response { - ui.vertical_centered(|ui| { - ui.spacing_mut().item_spacing.y = 8.; + fn render(&mut self, ui: &mut Ui, services: &mut RouteServices<'_>) -> Response { + Frame::none() + .inner_margin(Margin::same(12.)) + .show(ui, |ui| { + ui.vertical(|ui| { + ui.spacing_mut().item_spacing.y = 8.; - ui.label(RichText::new("Login").size(32.)); - ui.label("Pubkey"); - ui.text_edit_singleline(&mut self.key); - if Button::new().show(ui, |ui| ui.label("Login")).clicked() { - if let Ok(pk) = hex::decode(&self.key) { - if let Ok(pk) = pk.as_slice().try_into() { - services.action(RouteAction::LoginPubkey(pk)); - services.navigate(Routes::HomePage); - return; + ui.label(RichText::new("Login").size(32.)); + let mut input = NativeTextInput::new(&mut self.key).with_hint_text("npub/nsec"); + input.render(ui, services); + + if Button::new().show(ui, |ui| ui.label("Login")).clicked() { + if let Ok((hrp, key)) = bech32::decode(&self.key) { + match hrp.to_lowercase().as_str() { + "nsec" => { + services.login.login(LoginKind::PrivateKey { + key: key.as_slice().try_into().unwrap(), + }); + services.navigate(Routes::HomePage); + } + "npub" | "nprofile" => { + services.login.login(LoginKind::PublicKey { + key: key.as_slice().try_into().unwrap(), + }); + services.navigate(Routes::HomePage); + } + _ => {} + } + } else if let Ok(pk) = hex::decode(&self.key) { + if let Ok(pk) = pk.as_slice().try_into() { + services.login.login(LoginKind::PublicKey { key: pk }); + services.navigate(Routes::HomePage); + return; + } + } + self.error = Some("Invalid pubkey".to_string()); } - } - self.error = Some("Invalid pubkey".to_string()); - } - if let Some(e) = &self.error { - ui.label(RichText::new(e).color(Color32::RED)); - } - }) - .response + if let Some(e) = &self.error { + ui.label(RichText::new(e).color(Color32::RED)); + } + }) + .response + }) + .inner } } diff --git a/src/route/mod.rs b/src/route/mod.rs index 18959ed..37bda12 100644 --- a/src/route/mod.rs +++ b/src/route/mod.rs @@ -1,4 +1,6 @@ +use crate::app::NativeLayerOps; use crate::link::NostrLink; +use crate::login::{Login, LoginKind}; use crate::note_util::OwnedNote; use crate::route::home::HomePage; use crate::route::login::LoginPage; @@ -9,7 +11,7 @@ use crate::widgets::{Header, NostrWidget}; use egui::{Context, Response, Ui}; use egui_inbox::{UiInbox, UiInboxSender}; use log::{info, warn}; -use nostr_sdk::Client; +use nostr_sdk::{Client, Event, JsonUtil}; use nostrdb::{Ndb, Transaction}; use std::path::PathBuf; @@ -36,24 +38,40 @@ pub enum Routes { #[derive(PartialEq)] pub enum RouteAction { - /// Login with public key - LoginPubkey([u8; 32]), + ShowKeyboard, + HideKeyboard, } -pub struct Router { +pub struct Router { current: Routes, current_widget: Option>, router: UiInbox, ctx: Context, ndb: NDBWrapper, - login: Option<[u8; 32]>, + login: Login, client: Client, image_cache: ImageCache, + native_layer: T, } -impl Router { - pub fn new(data_path: PathBuf, ctx: Context, client: Client, ndb: Ndb) -> Self { +impl Drop for Router { + fn drop(&mut self) { + self.login.save(&mut self.native_layer) + } +} + +impl Router { + pub fn new( + data_path: PathBuf, + ctx: Context, + client: Client, + ndb: Ndb, + native_layer: T, + ) -> Self { + let mut login = Login::new(); + login.load(&native_layer); + Self { current: Routes::HomePage, current_widget: None, @@ -61,8 +79,9 @@ impl Router { ctx: ctx.clone(), ndb: NDBWrapper::new(ctx.clone(), ndb.clone(), client.clone()), client, - login: None, + login, image_cache: ImageCache::new(data_path, ctx.clone()), + native_layer, } } @@ -91,9 +110,10 @@ impl Router { // handle app state changes let q = self.router.read(ui); for r in q { - if let Routes::Action(a) = &r { + if let Routes::Action(a) = r { match a { - RouteAction::LoginPubkey(k) => self.login = Some(*k), + RouteAction::ShowKeyboard => self.native_layer.show_keyboard(), + RouteAction::HideKeyboard => self.native_layer.hide_keyboard(), _ => info!("Not implemented"), } } else { @@ -106,20 +126,21 @@ impl Router { self.load_widget(Routes::HomePage, &tx); } - let svc = RouteServices { + let mut svc = RouteServices { context: self.ctx.clone(), router: self.router.sender(), + client: self.client.clone(), ndb: &self.ndb, tx: &tx, - login: &self.login, + login: &mut self.login, img_cache: &self.image_cache, }; // display app ui.vertical(|ui| { - Header::new().render(ui, &svc); + Header::new().render(ui, &mut svc); if let Some(w) = self.current_widget.as_mut() { - w.render(ui, &svc) + w.render(ui, &mut svc) } else { ui.label("No widget") } @@ -131,11 +152,12 @@ impl Router { pub struct RouteServices<'a> { pub context: Context, //cloned pub router: UiInboxSender, //cloned + pub client: Client, - pub ndb: &'a NDBWrapper, //ref - pub tx: &'a Transaction, //ref - pub login: &'a Option<[u8; 32]>, //ref - pub img_cache: &'a ImageCache, + pub ndb: &'a NDBWrapper, //ref + pub tx: &'a Transaction, //ref + pub login: &'a mut Login, //ref + pub img_cache: &'a ImageCache, //ref } impl<'a> RouteServices<'a> { @@ -150,4 +172,21 @@ impl<'a> RouteServices<'a> { warn!("Failed to navigate"); } } + + pub fn broadcast_event(&self, event: Event) { + let client = self.client.clone(); + + let ev_json = event.as_json(); + if let Err(e) = self.ndb.submit_event(&ev_json) { + warn!("Failed to submit event {}", e); + } + tokio::spawn(async move { + match client.send_event(event).await { + Ok(e) => { + info!("Broadcast event: {:?}", e) + } + Err(e) => warn!("Failed to broadcast event: {:?}", e), + } + }); + } } diff --git a/src/route/stream.rs b/src/route/stream.rs index 1a4b917..16e7c9d 100644 --- a/src/route/stream.rs +++ b/src/route/stream.rs @@ -23,18 +23,18 @@ impl StreamPage { let f = [f.limit_mut(1)]; let (sub, events) = ndb.subscribe_with_results("streams", &f, tx, 1); Self { - link, + link: link.clone(), sub, event: events.first().map(|n| OwnedNote(n.note_key.as_u64())), chat: None, player: None, - new_msg: WriteChat::new(), + new_msg: WriteChat::new(link), } } } impl NostrWidget for StreamPage { - fn render(&mut self, ui: &mut Ui, services: &RouteServices<'_>) -> Response { + fn render(&mut self, ui: &mut Ui, services: &mut RouteServices<'_>) -> Response { let poll = services.ndb.poll(&self.sub, 1); if let Some(k) = poll.first() { self.event = Some(OwnedNote(k.as_u64())) diff --git a/src/services/ndb_wrapper.rs b/src/services/ndb_wrapper.rs index 97a38d5..d81319a 100644 --- a/src/services/ndb_wrapper.rs +++ b/src/services/ndb_wrapper.rs @@ -154,4 +154,8 @@ impl NDBWrapper { let sub = None; (p, sub) } + + pub fn submit_event(&self, ev: &str) -> Result<(), Error> { + self.ndb.process_event(ev) + } } diff --git a/src/theme.rs b/src/theme.rs index 382675e..c441515 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,6 +1,8 @@ -use egui::Color32; +use egui::{Color32, Margin}; pub const FONT_SIZE: f32 = 13.0; +pub const ROUNDING_DEFAULT: f32 = 12.0; +pub const MARGIN_DEFAULT: Margin = Margin::symmetric(12., 6.); pub const PRIMARY: Color32 = Color32::from_rgb(248, 56, 217); pub const NEUTRAL_500: Color32 = Color32::from_rgb(115, 115, 115); pub const NEUTRAL_800: Color32 = Color32::from_rgb(38, 38, 38); diff --git a/src/widgets/button.rs b/src/widgets/button.rs index 37db2b5..c089edf 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -1,4 +1,4 @@ -use crate::theme::NEUTRAL_800; +use crate::theme::{NEUTRAL_800, ROUNDING_DEFAULT}; use egui::{Color32, CursorIcon, Frame, Margin, Response, Sense, Ui}; pub struct Button { @@ -17,7 +17,7 @@ impl Button { let r = Frame::none() .inner_margin(Margin::symmetric(12., 8.)) .fill(self.color) - .rounding(12.) + .rounding(ROUNDING_DEFAULT) .show(ui, add_contents); let id = r.response.id; diff --git a/src/widgets/chat.rs b/src/widgets/chat.rs index 741aa69..9ecc1e1 100644 --- a/src/widgets/chat.rs +++ b/src/widgets/chat.rs @@ -39,7 +39,7 @@ impl Chat { } impl NostrWidget for Chat { - fn render(&mut self, ui: &mut Ui, services: &RouteServices<'_>) -> Response { + fn render(&mut self, ui: &mut Ui, services: &mut RouteServices<'_>) -> Response { let poll = services.ndb.poll(&self.sub, 500); poll.iter() .for_each(|n| self.events.push(OwnedNote(n.as_u64()))); diff --git a/src/widgets/header.rs b/src/widgets/header.rs index b747974..520932c 100644 --- a/src/widgets/header.rs +++ b/src/widgets/header.rs @@ -1,3 +1,4 @@ +use crate::login::LoginKind; use crate::route::{RouteServices, Routes}; use crate::widgets::avatar::Avatar; use crate::widgets::{Button, NostrWidget}; @@ -14,7 +15,7 @@ impl Header { } impl NostrWidget for Header { - fn render(&mut self, ui: &mut Ui, services: &RouteServices<'_>) -> Response { + fn render(&mut self, ui: &mut Ui, services: &mut RouteServices<'_>) -> Response { let logo_bytes = include_bytes!("../resources/logo.svg"); Frame::none() .outer_margin(Margin::symmetric(16., 8.)) @@ -37,8 +38,8 @@ impl NostrWidget for Header { } ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if let Some(pk) = services.login { - ui.add(Avatar::pubkey(pk, services)); + if let Some(pk) = services.login.public_key() { + ui.add(Avatar::pubkey(&pk, services)); } else if Button::new().show(ui, |ui| ui.label("Login")).clicked() { services.navigate(Routes::LoginPage); } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 78051a7..776af10 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -8,6 +8,7 @@ mod stream_list; mod stream_player; mod stream_tile; mod stream_title; +mod text_input; mod username; mod video_placeholder; mod write_chat; @@ -16,7 +17,7 @@ use crate::route::RouteServices; use egui::{Response, Ui}; pub trait NostrWidget { - fn render(&mut self, ui: &mut Ui, services: &RouteServices<'_>) -> Response; + fn render(&mut self, ui: &mut Ui, services: &mut RouteServices<'_>) -> Response; } pub use self::avatar::Avatar; @@ -27,6 +28,7 @@ pub use self::profile::Profile; pub use self::stream_list::StreamList; pub use self::stream_player::StreamPlayer; pub use self::stream_title::StreamTitle; +pub use self::text_input::NativeTextInput; pub use self::username::Username; pub use self::video_placeholder::VideoPlaceholder; pub use self::write_chat::WriteChat; diff --git a/src/widgets/stream_tile.rs b/src/widgets/stream_tile.rs index 3f99c29..5e77f55 100644 --- a/src/widgets/stream_tile.rs +++ b/src/widgets/stream_tile.rs @@ -1,7 +1,7 @@ use crate::link::NostrLink; use crate::route::{RouteServices, Routes}; use crate::stream_info::{StreamInfo, StreamStatus}; -use crate::theme::{NEUTRAL_800, NEUTRAL_900, PRIMARY}; +use crate::theme::{NEUTRAL_800, NEUTRAL_900, PRIMARY, ROUNDING_DEFAULT}; use crate::widgets::avatar::Avatar; use eframe::epaint::{Rounding, Vec2}; use egui::epaint::RectShape; @@ -44,7 +44,7 @@ impl Widget for StreamEvent<'_> { Ok(TexturePoll::Ready { texture }) => { painter.add(RectShape { rect: response.rect, - rounding: Rounding::same(12.), + rounding: Rounding::same(ROUNDING_DEFAULT), fill: Color32::WHITE, stroke: Default::default(), blur_width: 0.0, diff --git a/src/widgets/stream_title.rs b/src/widgets/stream_title.rs index a958656..d8191f3 100644 --- a/src/widgets/stream_title.rs +++ b/src/widgets/stream_title.rs @@ -16,7 +16,7 @@ impl<'a> StreamTitle<'a> { } impl<'a> NostrWidget for StreamTitle<'a> { - fn render(&mut self, ui: &mut Ui, services: &RouteServices<'_>) -> Response { + fn render(&mut self, ui: &mut Ui, services: &mut RouteServices<'_>) -> Response { Frame::none() .outer_margin(Margin::symmetric(12., 8.)) .show(ui, |ui| { diff --git a/src/widgets/text_input.rs b/src/widgets/text_input.rs new file mode 100644 index 0000000..4ac87c7 --- /dev/null +++ b/src/widgets/text_input.rs @@ -0,0 +1,46 @@ +use crate::route::{RouteAction, RouteServices}; +use crate::theme::{MARGIN_DEFAULT, NEUTRAL_500, NEUTRAL_800, ROUNDING_DEFAULT}; +use crate::widgets::NostrWidget; +use egui::{Frame, Response, TextEdit, Ui}; + +/// Wrap the [TextEdit] widget to handle native keyboard +pub struct NativeTextInput<'a> { + pub text: &'a mut String, + hint_text: Option<&'a str>, +} + +impl<'a> NativeTextInput<'a> { + pub fn new(text: &'a mut String) -> Self { + Self { + text, + hint_text: None, + } + } + + pub fn with_hint_text(mut self, hint_text: &'a str) -> Self { + self.hint_text = Some(hint_text); + self + } +} + +impl<'a> NostrWidget for NativeTextInput<'a> { + fn render(&mut self, ui: &mut Ui, services: &mut RouteServices<'_>) -> Response { + let mut editor = TextEdit::singleline(self.text).frame(false); + if let Some(hint_text) = self.hint_text { + editor = editor.hint_text(egui::RichText::new(hint_text).color(NEUTRAL_500)); + } + let response = Frame::none() + .inner_margin(MARGIN_DEFAULT) + .fill(NEUTRAL_800) + .rounding(ROUNDING_DEFAULT) + .show(ui, |ui| ui.add(editor)) + .inner; + if response.lost_focus() { + services.action(RouteAction::HideKeyboard); + } + if response.gained_focus() { + services.action(RouteAction::ShowKeyboard); + } + response + } +} diff --git a/src/widgets/write_chat.rs b/src/widgets/write_chat.rs index 06f0b50..9184c45 100644 --- a/src/widgets/write_chat.rs +++ b/src/widgets/write_chat.rs @@ -1,32 +1,36 @@ -use crate::route::RouteServices; -use crate::theme::NEUTRAL_900; -use crate::widgets::NostrWidget; +use crate::link::NostrLink; +use crate::route::{RouteAction, RouteServices}; +use crate::theme::{MARGIN_DEFAULT, NEUTRAL_900, ROUNDING_DEFAULT}; +use crate::widgets::{NativeTextInput, NostrWidget}; use eframe::emath::Align; use egui::{Frame, Image, Layout, Margin, Response, Rounding, Sense, Stroke, TextEdit, Ui, Widget}; use log::info; pub struct WriteChat { + link: NostrLink, msg: String, } impl WriteChat { - pub fn new() -> Self { - Self { msg: String::new() } + pub fn new(link: NostrLink) -> Self { + Self { + link, + msg: String::new(), + } } } impl NostrWidget for WriteChat { - fn render(&mut self, ui: &mut Ui, services: &RouteServices<'_>) -> Response { - let size = ui.available_size(); + fn render(&mut self, ui: &mut Ui, services: &mut RouteServices<'_>) -> Response { let logo_bytes = include_bytes!("../resources/send-03.svg"); Frame::none() - .inner_margin(Margin::symmetric(12., 6.)) + .inner_margin(MARGIN_DEFAULT) .stroke(Stroke::new(1.0, NEUTRAL_900)) .show(ui, |ui| { Frame::none() .fill(NEUTRAL_900) - .rounding(Rounding::same(12.0)) - .inner_margin(Margin::symmetric(12., 10.)) + .rounding(ROUNDING_DEFAULT) + .inner_margin(MARGIN_DEFAULT) .show(ui, |ui| { ui.horizontal(|ui| { if services @@ -36,12 +40,19 @@ impl NostrWidget for WriteChat { .ui(ui) .clicked() { - info!("Sending: {}", self.msg); + if let Ok(ev) = + services.login.write_live_chat_msg(&self.link, &self.msg) + { + info!("Sending: {:?}", ev); + services.broadcast_event(ev); + } self.msg.clear(); } - let editor = TextEdit::singleline(&mut self.msg).frame(false); - ui.add_sized(ui.available_size(), editor); + ui.allocate_ui(ui.available_size(), |ui| { + let mut editor = NativeTextInput::new(&mut self.msg); + editor.render(ui, services); + }); }); }) })