diff --git a/Cargo.lock b/Cargo.lock index 2b06fc1..6baf487 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,31 +443,6 @@ dependencies = [ "arrayvec", ] -[[package]] -name = "aws-lc-rs" -version = "1.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b7ddaa2c56a367ad27a094ad8ef4faacf8a617c2575acb2ba88949df999ca" -dependencies = [ - "aws-lc-sys", - "paste", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ac4f13dad353b209b34cbec082338202cbc01c8f00336b55c750c13ac91f8f" -dependencies = [ - "bindgen 0.69.5", - "cc", - "cmake", - "dunce", - "fs_extra", - "paste", -] - [[package]] name = "backtrace" version = "0.3.74" @@ -501,29 +476,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags 2.8.0", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.98", - "which", -] - [[package]] name = "bindgen" version = "0.70.1" @@ -887,12 +839,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cexpr" version = "0.6.0" @@ -1006,9 +952,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.29" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" +checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" dependencies = [ "clap_builder", "clap_derive", @@ -1016,9 +962,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.29" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -1044,15 +990,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" -[[package]] -name = "cmake" -version = "0.1.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" -dependencies = [ - "cc", -] - [[package]] name = "cocoa" version = "0.25.0" @@ -1126,7 +1063,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "indexmap", "rustc-hash 2.1.1", @@ -1144,16 +1081,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "common" version = "0.1.0" @@ -1453,7 +1380,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "proc-macro2", "quote", @@ -1734,9 +1661,9 @@ dependencies = [ [[package]] name = "filedescriptor" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" dependencies = [ "libc", "thiserror 1.0.69", @@ -1914,12 +1841,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "futf" version = "0.1.5" @@ -2184,13 +2105,13 @@ dependencies = [ [[package]] name = "gpui" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "anyhow", "as-raw-xcb-connection", "ashpd", "async-task", - "bindgen 0.70.1", + "bindgen", "blade-graphics", "blade-macros", "blade-util", @@ -2271,7 +2192,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "proc-macro2", "quote", @@ -2281,7 +2202,7 @@ dependencies = [ [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "gpui", "tokio", @@ -2449,15 +2370,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "html-escape" version = "0.2.13" @@ -2504,7 +2416,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "anyhow", "bytes", @@ -2512,8 +2424,6 @@ dependencies = [ "futures", "http", "log", - "rustls", - "rustls-platform-verifier", "serde", "serde_json", "url", @@ -2907,28 +2817,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - [[package]] name = "jobserver" version = "0.1.32" @@ -2989,12 +2877,6 @@ dependencies = [ "spin", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "lebe" version = "0.5.2" @@ -3192,10 +3074,10 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "anyhow", - "bindgen 0.70.1", + "bindgen", "core-foundation 0.9.4", "ctor", "foreign-types", @@ -3272,9 +3154,9 @@ checksum = "6367d84fb54d4242af283086402907277715b8fe46976963af5ebf173f8efba3" [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", "simd-adler32", @@ -3377,7 +3259,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "nostr" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#b390bcf7bf15aaab4b8417c410779b059842c364" +source = "git+https://github.com/rust-nostr/nostr#6caf231eca951f2c6909382c46f43edffbd6e730" dependencies = [ "aes", "base64", @@ -3405,7 +3287,7 @@ dependencies = [ [[package]] name = "nostr-connect" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#b390bcf7bf15aaab4b8417c410779b059842c364" +source = "git+https://github.com/rust-nostr/nostr#6caf231eca951f2c6909382c46f43edffbd6e730" dependencies = [ "async-utility", "nostr", @@ -3417,7 +3299,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#b390bcf7bf15aaab4b8417c410779b059842c364" +source = "git+https://github.com/rust-nostr/nostr#6caf231eca951f2c6909382c46f43edffbd6e730" dependencies = [ "flatbuffers", "lru", @@ -3428,7 +3310,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#b390bcf7bf15aaab4b8417c410779b059842c364" +source = "git+https://github.com/rust-nostr/nostr#6caf231eca951f2c6909382c46f43edffbd6e730" dependencies = [ "async-utility", "heed", @@ -3441,7 +3323,7 @@ dependencies = [ [[package]] name = "nostr-relay-pool" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#b390bcf7bf15aaab4b8417c410779b059842c364" +source = "git+https://github.com/rust-nostr/nostr#6caf231eca951f2c6909382c46f43edffbd6e730" dependencies = [ "async-utility", "async-wsocket", @@ -3458,7 +3340,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.39.0" -source = "git+https://github.com/rust-nostr/nostr#b390bcf7bf15aaab4b8417c410779b059842c364" +source = "git+https://github.com/rust-nostr/nostr#6caf231eca951f2c6909382c46f43edffbd6e730" dependencies = [ "async-utility", "nostr", @@ -4495,7 +4377,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "derive_refineable", ] @@ -4628,7 +4510,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "anyhow", "bytes", @@ -4762,12 +4644,10 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ - "aws-lc-rs", - "log", "once_cell", "ring", "rustls-pki-types", @@ -4806,40 +4686,12 @@ dependencies = [ "web-time", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e012c45844a1790332c9386ed4ca3a06def221092eda277e6f079728f8ea99da" -dependencies = [ - "core-foundation 0.10.0", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -5008,7 +4860,7 @@ checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] name = "semantic_version" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "anyhow", "serde", @@ -5333,7 +5185,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "arrayvec", "log", @@ -6203,7 +6055,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#7ba1492f0af820eb8d558ba4c6b3c4f780be0de0" +source = "git+https://github.com/zed-industries/zed#c1f162abc6cb41a0c765aa76c3af3e83a994ce18" dependencies = [ "anyhow", "async-fs", @@ -6551,15 +6403,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-root-certs" -version = "0.26.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webpki-roots" version = "0.26.8" @@ -6575,18 +6418,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "winapi" version = "0.3.9" @@ -6702,15 +6533,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -6738,21 +6560,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -6784,12 +6591,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -6802,12 +6603,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -6820,12 +6615,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -6844,12 +6633,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -6862,12 +6645,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -6880,12 +6657,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -6898,12 +6669,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/crates/app/src/views/chat.rs b/crates/app/src/views/chat.rs index 86874b0..6ca5f3c 100644 --- a/crates/app/src/views/chat.rs +++ b/crates/app/src/views/chat.rs @@ -8,8 +8,8 @@ use common::{ utils::{compare, nip96_upload}, }; use gpui::{ - div, img, list, prelude::FluentBuilder, px, white, AnyElement, App, AppContext, Context, - Element, Entity, EventEmitter, Flatten, FocusHandle, Focusable, InteractiveElement, + div, img, list, prelude::FluentBuilder, px, relative, svg, white, AnyElement, App, AppContext, + Context, Element, Entity, EventEmitter, Flatten, FocusHandle, Focusable, InteractiveElement, IntoElement, ListAlignment, ListState, ObjectFit, ParentElement, PathPromptOptions, Render, SharedString, StatefulInteractiveElement, Styled, StyledImage, Subscription, WeakEntity, Window, @@ -24,11 +24,15 @@ use ui::{ button::{Button, ButtonRounded, ButtonVariants}, dock_area::panel::{Panel, PanelEvent}, input::{InputEvent, TextInput}, + notification::Notification, popup_menu::PopupMenu, theme::{scale::ColorScaleStep, ActiveTheme}, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt, }; +const ALERT: &str = + "This conversation is private. Only members of this chat can see each other's messages."; + pub fn init( id: &u64, window: &mut Window, @@ -45,31 +49,34 @@ pub fn init( } } -struct Message { +#[derive(PartialEq, Eq)] +struct ChatItem { profile: NostrProfile, content: SharedString, ago: SharedString, } -impl PartialEq for Message { - fn eq(&self, other: &Self) -> bool { - let content = self.content == other.content; - let member = self.profile == other.profile; - let ago = self.ago == other.ago; - - content && member && ago - } +#[derive(PartialEq, Eq)] +enum Message { + Item(Box), + System(SharedString), + Placeholder, } impl Message { - pub fn new(profile: NostrProfile, content: SharedString, ago: SharedString) -> Self { - Self { - profile, - content, - ago, - } + pub fn new(chat_message: ChatItem) -> Self { + Self::Item(Box::new(chat_message)) + } + + pub fn system(content: SharedString) -> Self { + Self::System(content) + } + + pub fn placeholder() -> Self { + Self::Placeholder } } + pub struct Chat { // Panel id: SharedString, @@ -95,7 +102,7 @@ impl Chat { let new_messages = model.read(cx).new_messages.downgrade(); cx.new(|cx| { - let messages = cx.new(|_| Vec::new()); + let messages = cx.new(|_| vec![Message::placeholder()]); let attaches = cx.new(|_| None); let input = cx.new(|cx| { @@ -140,8 +147,12 @@ impl Chat { subscriptions, }; + // Verify messaging relays of all members + this.verify_messaging_relays(cx); + // Load all messages from database this.load_messages(cx); + // Subscribe and load new messages this.load_new_messages(cx); @@ -149,6 +160,62 @@ impl Chat { }) } + fn verify_messaging_relays(&self, cx: &mut Context) { + let Some(model) = self.room.upgrade() else { + return; + }; + + let room = model.read(cx); + let pubkeys: Vec = room.members.iter().map(|m| m.public_key()).collect(); + let client = get_client(); + let (tx, rx) = oneshot::channel::>(); + + cx.background_spawn(async move { + let mut result = Vec::new(); + + for pubkey in pubkeys.into_iter() { + let filter = Filter::new() + .kind(Kind::InboxRelays) + .author(pubkey) + .limit(1); + + let is_ready = if let Ok(events) = client.database().query(filter).await { + events.first_owned().is_some() + } else { + false + }; + + result.push((pubkey, is_ready)); + } + + _ = tx.send(result); + }) + .detach(); + + cx.spawn(|this, cx| async move { + if let Ok(result) = rx.await { + _ = cx.update(|cx| { + _ = this.update(cx, |this, cx| { + for item in result.into_iter() { + if !item.1 { + let name = this + .room + .read_with(cx, |this, _| this.name()) + .unwrap_or("Unnamed".into()); + + this.push_system_message( + format!("{} has not set up Messaging (DM) Relays, so they will NOT receive your messages.", name), + cx, + ); + } + } + }); + }); + } + }) + .detach(); + } + fn load_messages(&self, cx: &mut Context) { let Some(model) = self.room.upgrade() else { return; @@ -199,11 +266,55 @@ impl Chat { .detach(); } + fn push_system_message(&self, content: String, cx: &mut Context) { + let old_len = self.messages.read(cx).len(); + let message = Message::system(content.into()); + + cx.update_entity(&self.messages, |this, cx| { + this.extend(vec![message]); + cx.notify(); + }); + + self.list_state.splice(old_len..old_len, 1); + } + + fn push_message(&self, content: String, window: &mut Window, cx: &mut Context) { + let Some(model) = self.room.upgrade() else { + return; + }; + + let old_len = self.messages.read(cx).len(); + let room = model.read(cx); + let ago = LastSeen(Timestamp::now()).human_readable(); + let message = Message::new(ChatItem { + profile: room.owner.clone(), + content: content.into(), + ago, + }); + + // Update message list + cx.update_entity(&self.messages, |this, cx| { + this.extend(vec![message]); + cx.notify(); + }); + + // Reset message input + cx.update_entity(&self.input, |this, cx| { + this.set_loading(false, window, cx); + this.set_disabled(false, window, cx); + this.set_text("", window, cx); + cx.notify(); + }); + + self.list_state.splice(old_len..old_len, 1); + } + fn push_messages(&self, events: Events, cx: &mut Context) { let Some(model) = self.room.upgrade() else { return; }; + let old_len = self.messages.read(cx).len(); let room = model.read(cx); let pubkeys = room.pubkeys(); @@ -224,11 +335,11 @@ impl Chat { room.owner.to_owned() }; - Some(Message::new( - member, - ev.content.into(), - LastSeen(ev.created_at).human_readable(), - )) + Some(Message::new(ChatItem { + profile: member, + content: ev.content.into(), + ago: LastSeen(ev.created_at).human_readable(), + })) } else { None } @@ -244,7 +355,7 @@ impl Chat { cx.notify(); }); - self.list_state.reset(total); + self.list_state.splice(old_len..old_len, total); } fn load_new_messages(&mut self, cx: &mut Context) { @@ -266,11 +377,11 @@ impl Chat { .iter() .filter_map(|event| { if let Some(profile) = room.member(&event.pubkey) { - let message = Message::new( + let message = Message::new(ChatItem { profile, - event.content.clone().into(), - LastSeen(event.created_at).human_readable(), - ); + content: event.content.clone().into(), + ago: LastSeen(event.created_at).human_readable(), + }); if !old_messages.iter().any(|old| old == &message) { Some(message) @@ -339,6 +450,7 @@ impl Chat { let client = get_client(); let window_handle = window.window_handle(); + let (tx, rx) = oneshot::channel::>(); let room = model.read(cx); let pubkeys = room.pubkeys(); @@ -357,55 +469,43 @@ impl Chat { // Send message to all pubkeys cx.background_spawn(async move { + let mut errors = Vec::new(); + for pubkey in pubkeys.iter() { - if let Err(_e) = client + if let Err(e) = client .send_private_msg(*pubkey, &async_content, tags.clone()) .await { - // TODO: handle error + errors.push(e); } } + + _ = tx.send(errors); }) .detach(); cx.spawn(|this, mut cx| async move { _ = cx.update_window(window_handle, |_, window, cx| { _ = this.update(cx, |this, cx| { - this.force_push_message(content.clone(), window, cx); + this.push_message(content.clone(), window, cx); }); }); + + if let Ok(errors) = rx.await { + _ = cx.update_window(window_handle, |_, window, cx| { + for error in errors.into_iter() { + window.push_notification( + Notification::error(error.to_string()).title("Message Failed to Send"), + cx, + ); + } + }); + } }) .detach(); } - fn force_push_message(&self, content: String, window: &mut Window, cx: &mut Context) { - let Some(model) = self.room.upgrade() else { - return; - }; - - let room = model.read(cx); - let ago = LastSeen(Timestamp::now()).human_readable(); - let message = Message::new(room.owner.clone(), content.into(), ago); - let old_len = self.messages.read(cx).len(); - - // Update message list - cx.update_entity(&self.messages, |this, cx| { - this.extend(vec![message]); - cx.notify(); - }); - - // Reset message input - cx.update_entity(&self.input, |this, cx| { - this.set_loading(false, window, cx); - this.set_disabled(false, window, cx); - this.set_text("", window, cx); - cx.notify(); - }); - - self.list_state.splice(old_len..old_len, 1); - } - - fn upload(&mut self, window: &mut Window, cx: &mut Context) { + fn upload_media(&mut self, window: &mut Window, cx: &mut Context) { let window_handle = window.window_handle(); let paths = cx.prompt_for_paths(PathPromptOptions { @@ -467,7 +567,7 @@ impl Chat { .detach(); } - fn remove(&mut self, url: &Url, _window: &mut Window, cx: &mut Context) { + fn remove_media(&mut self, url: &Url, _window: &mut Window, cx: &mut Context) { self.attaches.update(cx, |model, cx| { if let Some(urls) = model.as_mut() { let ix = urls.iter().position(|x| x == url).unwrap(); @@ -496,46 +596,86 @@ impl Chat { .gap_3() .w_full() .p_2() - .hover(|this| this.bg(cx.theme().accent.step(cx, ColorScaleStep::ONE))) - .child( - div() - .absolute() - .left_0() - .top_0() - .w(px(2.)) - .h_full() - .bg(cx.theme().transparent) - .group_hover("", |this| { - this.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE)) - }), - ) - .child( - img(message.profile.avatar()) - .size_8() - .rounded_full() - .flex_shrink_0(), - ) - .child( - div() - .flex() - .flex_col() - .flex_initial() - .overflow_hidden() + .map(|this| match message { + Message::Item(item) => this + .hover(|this| this.bg(cx.theme().accent.step(cx, ColorScaleStep::ONE))) + .child( + div() + .absolute() + .left_0() + .top_0() + .w(px(2.)) + .h_full() + .bg(cx.theme().transparent) + .group_hover("", |this| { + this.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE)) + }), + ) + .child( + img(item.profile.avatar()) + .size_8() + .rounded_full() + .flex_shrink_0(), + ) .child( div() .flex() - .items_baseline() - .gap_2() - .text_xs() - .child(div().font_semibold().child(message.profile.name())) + .flex_col() + .flex_initial() + .overflow_hidden() .child( - div().child(message.ago.clone()).text_color( - cx.theme().base.step(cx, ColorScaleStep::ELEVEN), - ), - ), + div() + .flex() + .items_baseline() + .gap_2() + .text_xs() + .child(div().font_semibold().child(item.profile.name())) + .child(div().child(item.ago.clone()).text_color( + cx.theme().base.step(cx, ColorScaleStep::ELEVEN), + )), + ) + .child(div().text_sm().child(item.content.clone())), + ), + Message::System(content) => this + .items_center() + .child( + div() + .absolute() + .left_0() + .top_0() + .w(px(2.)) + .h_full() + .bg(cx.theme().transparent) + .group_hover("", |this| this.bg(cx.theme().danger)), ) - .child(div().text_sm().child(message.content.clone())), - ) + .child( + img("brand/avatar.png") + .size_8() + .rounded_full() + .flex_shrink_0(), + ) + .text_xs() + .text_color(cx.theme().danger) + .child(content.clone()), + Message::Placeholder => this + .w_full() + .h_32() + .flex() + .flex_col() + .items_center() + .justify_center() + .text_center() + .text_xs() + .text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)) + .line_height(relative(1.)) + .child( + svg() + .path("brand/coop.svg") + .size_8() + .text_color(cx.theme().base.step(cx, ColorScaleStep::THREE)), + ) + .child(ALERT), + }) } else { div() } @@ -651,7 +791,7 @@ impl Render for Chat { ), ) .on_click(cx.listener(move |this, _, window, cx| { - this.remove(&url, window, cx); + this.remove_media(&url, window, cx); })) })) }) @@ -667,7 +807,7 @@ impl Render for Chat { .icon(Icon::new(IconName::Upload)) .ghost() .on_click(cx.listener(move |this, _, window, cx| { - this.upload(window, cx); + this.upload_media(window, cx); })) .loading(self.is_uploading), ) diff --git a/crates/app/src/views/onboarding.rs b/crates/app/src/views/onboarding.rs index be14820..51e0253 100644 --- a/crates/app/src/views/onboarding.rs +++ b/crates/app/src/views/onboarding.rs @@ -1,7 +1,7 @@ use common::{profile::NostrProfile, qr::create_qr, utils::preload}; use gpui::{ div, img, prelude::FluentBuilder, relative, svg, App, AppContext, ClipboardItem, Context, Div, - Entity, IntoElement, ParentElement, Render, Styled, Window, + Entity, IntoElement, ParentElement, Render, Styled, Subscription, Window, }; use nostr_connect::prelude::*; use state::get_client; @@ -17,7 +17,8 @@ use ui::{ use super::app; -const ALPHA_MESSAGE: &str = "Coop is in the alpha stage; it doesn't store any credentials. You will need to log in again when you relaunch."; +const ALPHA_MESSAGE: &str = + "Coop is in the alpha stage of development; It may contain bugs, unfinished features, or unexpected behavior."; const JOIN_URL: &str = "https://start.njump.me/"; pub fn init(window: &mut Window, cx: &mut App) -> Entity { @@ -32,6 +33,8 @@ pub struct Onboarding { use_connect: bool, use_privkey: bool, is_loading: bool, + #[allow(dead_code)] + subscriptions: Vec, } impl Onboarding { @@ -55,7 +58,7 @@ impl Onboarding { cx.new(|cx| { // Handle Enter event for nsec input - cx.subscribe_in( + let subscriptions = vec![cx.subscribe_in( &nsec_input, window, move |this: &mut Self, _, input_event, window, cx| { @@ -63,8 +66,7 @@ impl Onboarding { this.privkey_login(window, cx); } }, - ) - .detach(); + )]; Self { app_keys, @@ -74,6 +76,7 @@ impl Onboarding { use_connect: false, use_privkey: false, is_loading: false, + subscriptions, } }) } diff --git a/crates/app/src/views/sidebar/compose.rs b/crates/app/src/views/sidebar/compose.rs index 43027f4..a090c48 100644 --- a/crates/app/src/views/sidebar/compose.rs +++ b/crates/app/src/views/sidebar/compose.rs @@ -1,18 +1,18 @@ -use chats::registry::ChatRegistry; +use chats::{registry::ChatRegistry, room::Room}; use common::{ - constants::FAKE_SIG, profile::NostrProfile, utils::{random_name, signer_public_key}, }; use gpui::{ div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App, AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement, - Render, SharedString, StatefulInteractiveElement, Styled, TextAlign, Window, + Render, SharedString, StatefulInteractiveElement, Styled, Subscription, TextAlign, Window, }; use nostr_sdk::prelude::*; use serde::Deserialize; +use smol::Timer; use state::get_client; -use std::{collections::HashSet, str::FromStr, time::Duration}; +use std::{collections::HashSet, time::Duration}; use tokio::sync::oneshot; use ui::{ button::{Button, ButtonRounded}, @@ -21,6 +21,9 @@ use ui::{ ContextModal, Icon, IconName, Sizable, Size, StyledExt, }; +const ALERT: &str = + "Start a conversation with someone using their npub or NIP-05 (like foo@bar.com)."; + #[derive(Clone, PartialEq, Eq, Deserialize)] struct SelectContact(PublicKey); @@ -28,26 +31,23 @@ impl_internal_actions!(contacts, [SelectContact]); pub struct Compose { title_input: Entity, - message_input: Entity, user_input: Entity, contacts: Entity>, selected: Entity>, focus_handle: FocusHandle, is_loading: bool, is_submitting: bool, + error_message: Entity>, + #[allow(dead_code)] + subscriptions: Vec, } impl Compose { pub fn new(window: &mut Window, cx: &mut Context<'_, Self>) -> Self { let contacts = cx.new(|_| Vec::new()); let selected = cx.new(|_| HashSet::new()); - - let user_input = cx.new(|cx| { - TextInput::new(window, cx) - .text_size(ui::Size::Small) - .small() - .placeholder("npub1...") - }); + let error_message = cx.new(|_| None); + let mut subscriptions = Vec::new(); let title_input = cx.new(|cx| { let name = random_name(2); @@ -60,15 +60,15 @@ impl Compose { input }); - let message_input = cx.new(|cx| { + let user_input = cx.new(|cx| { TextInput::new(window, cx) - .appearance(false) - .text_size(Size::XSmall) - .placeholder("Hello...") + .text_size(ui::Size::Small) + .small() + .placeholder("npub1...") }); - // Handle Enter event for message input - cx.subscribe_in( + // Handle Enter event for user input + subscriptions.push(cx.subscribe_in( &user_input, window, move |this, _, input_event, window, cx| { @@ -76,155 +76,118 @@ impl Compose { this.add(window, cx); } }, - ) + )); + + let client = get_client(); + let (tx, rx) = oneshot::channel::>(); + + cx.background_spawn(async move { + if let Ok(public_key) = signer_public_key(client).await { + if let Ok(profiles) = client.database().contacts(public_key).await { + let members: Vec = profiles + .into_iter() + .map(|profile| NostrProfile::new(profile.public_key(), profile.metadata())) + .collect(); + + _ = tx.send(members); + } + } + }) .detach(); - cx.spawn(|this, mut cx| async move { - let (tx, rx) = oneshot::channel::>(); - - cx.background_executor() - .spawn(async move { - let client = get_client(); - if let Ok(public_key) = signer_public_key(client).await { - if let Ok(profiles) = client.database().contacts(public_key).await { - let members: Vec = profiles - .into_iter() - .map(|profile| { - NostrProfile::new(profile.public_key(), profile.metadata()) - }) - .collect(); - - _ = tx.send(members); - } - } - }) - .detach(); - + cx.spawn(|this, cx| async move { if let Ok(contacts) = rx.await { - if let Some(view) = this.upgrade() { - _ = cx.update_entity(&view, |this, cx| { + _ = cx.update(|cx| { + this.update(cx, |this, cx| { this.contacts.update(cx, |this, cx| { this.extend(contacts); cx.notify(); }); - cx.notify(); - }); - } + }) + }); } }) .detach(); Self { title_input, - message_input, user_input, contacts, selected, + error_message, is_loading: false, is_submitting: false, focus_handle: cx.focus_handle(), + subscriptions, } } pub fn compose(&mut self, window: &mut Window, cx: &mut Context) { - let selected = self.selected.read(cx).to_owned(); - let message = self.message_input.read(cx).text(); - - if selected.is_empty() { - window.push_notification("You need to add at least 1 receiver", cx); - return; - } - - if message.is_empty() { - window.push_notification("Message is required", cx); + if self.selected.read(cx).is_empty() { + self.set_error(Some("You need to add at least 1 receiver".into()), cx); return; } // Show loading spinner self.set_submitting(true, cx); - // Get message from user's input - let content = message.to_string(); - - // Get room title from user's input - let title = Tag::custom( - TagKind::Subject, - vec![self.title_input.read(cx).text().to_string()], - ); - // Get all pubkeys - let mut pubkeys: Vec = selected.iter().copied().collect(); + let pubkeys: Vec = self.selected.read(cx).iter().copied().collect(); // Convert selected pubkeys into Nostr tags - let mut tag_list: Vec = selected.iter().map(|pk| Tag::public_key(*pk)).collect(); - tag_list.push(title); + let mut tag_list: Vec = pubkeys.iter().map(|pk| Tag::public_key(*pk)).collect(); + + // Add subject if it is present + if !self.title_input.read(cx).text().is_empty() { + tag_list.push(Tag::custom( + TagKind::Subject, + vec![self.title_input.read(cx).text().to_string()], + )); + } let tags = Tags::new(tag_list); + let client = get_client(); let window_handle = window.window_handle(); + let (tx, rx) = oneshot::channel::(); + + cx.background_spawn(async move { + let signer = client.signer().await.expect("Signer is required"); + // [IMPORTANT] + // Make sure this event is never send, + // this event existed just use for convert to Coop's Chat Room later. + if let Ok(event) = EventBuilder::private_msg_rumor(*pubkeys.last().unwrap(), "") + .tags(tags) + .sign(&signer) + .await + { + _ = tx.send(event) + }; + }) + .detach(); cx.spawn(|this, mut cx| async move { - let (tx, rx) = oneshot::channel::(); - - cx.background_spawn(async move { - let client = get_client(); - let public_key = signer_public_key(client).await.unwrap(); - let mut event: Option = None; - - pubkeys.push(public_key); - - for pubkey in pubkeys.iter() { - if let Ok(output) = client - .send_private_msg(*pubkey, &content, tags.clone()) - .await - { - if pubkey == &public_key && event.is_none() { - if let Ok(Some(ev)) = client.database().event_by_id(&output.val).await { - if let Ok(UnwrappedGift { mut rumor, .. }) = - client.unwrap_gift_wrap(&ev).await - { - // Compute event id if not exist - rumor.ensure_id(); - - if let Some(id) = rumor.id { - let ev = Event::new( - id, - rumor.pubkey, - rumor.created_at, - rumor.kind, - rumor.tags, - rumor.content, - Signature::from_str(FAKE_SIG).unwrap(), - ); - - event = Some(ev); - } - } - } - } - } - } - - if let Some(event) = event { - _ = tx.send(event); - } - }) - .detach(); - if let Ok(event) = rx.await { _ = cx.update_window(window_handle, |_, window, cx| { - if let Some(chats) = ChatRegistry::global(cx) { - chats.update(cx, |this, cx| { - this.push_message(event, cx); - }); - } - // Stop loading spinner _ = this.update(cx, |this, cx| { this.set_submitting(false, cx); }); - // Close modal - window.close_modal(cx); + if let Some(chats) = ChatRegistry::global(cx) { + let room = Room::parse(&event, cx); + + chats.update(cx, |state, cx| match state.new_room(room, cx) { + Ok(_) => { + // TODO: open chat panel + window.close_modal(cx); + } + Err(e) => { + _ = this.update(cx, |this, cx| { + this.set_error(Some(e.to_string().into()), cx); + }); + } + }); + } }); } }) @@ -303,10 +266,29 @@ impl Compose { .detach(); } else { self.set_loading(false, cx); - window.push_notification("Public Key is not valid", cx); + self.set_error(Some("Public Key is not valid".into()), cx); } } + fn set_error(&mut self, error: Option, cx: &mut Context) { + self.error_message.update(cx, |this, cx| { + *this = error; + cx.notify(); + }); + + // Dismiss error after 2 seconds + cx.spawn(|this, cx| async move { + Timer::after(Duration::from_secs(2)).await; + + _ = cx.update(|cx| { + this.update(cx, |this, cx| { + this.set_error(None, cx); + }) + }); + }) + .detach(); + } + fn set_loading(&mut self, status: bool, cx: &mut Context) { self.is_loading = status; cx.notify(); @@ -336,9 +318,6 @@ impl Compose { impl Render for Compose { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let msg = - "Start a conversation with someone using their npub or NIP-05 (like foo@bar.com)."; - div() .track_focus(&self.focus_handle) .on_action(cx.listener(Self::on_action_select)) @@ -350,36 +329,30 @@ impl Render for Compose { .px_2() .text_xs() .text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)) - .child(msg), + .child(ALERT), ) + .when_some(self.error_message.read(cx).as_ref(), |this, msg| { + this.child( + div() + .px_2() + .text_xs() + .text_color(cx.theme().danger) + .child(msg.clone()), + ) + }) .child( - div() - .flex() - .flex_col() - .child( - div() - .h_10() - .px_2() - .border_b_1() - .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE)) - .flex() - .items_center() - .gap_1() - .child(div().text_xs().font_semibold().child("Title:")) - .child(self.title_input.clone()), - ) - .child( - div() - .h_10() - .px_2() - .border_b_1() - .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE)) - .flex() - .items_center() - .gap_1() - .child(div().text_xs().font_semibold().child("Message:")) - .child(self.message_input.clone()), - ), + div().flex().flex_col().child( + div() + .h_10() + .px_2() + .border_b_1() + .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE)) + .flex() + .items_center() + .gap_1() + .child(div().text_xs().font_semibold().child("Title:")) + .child(self.title_input.clone()), + ), ) .child( div() diff --git a/crates/chats/src/registry.rs b/crates/chats/src/registry.rs index 8f761dc..96bcae8 100644 --- a/crates/chats/src/registry.rs +++ b/crates/chats/src/registry.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use async_utility::tokio::sync::oneshot; use common::utils::{compare, room_hash, signer_public_key}; use gpui::{App, AppContext, Context, Entity, Global}; @@ -151,6 +152,21 @@ impl ChatRegistry { .cloned() } + pub fn new_room(&mut self, room: Room, cx: &mut Context) -> Result<(), anyhow::Error> { + if !self + .rooms + .iter() + .any(|current| compare(¤t.read(cx).pubkeys(), &room.pubkeys())) + { + self.rooms.insert(0, cx.new(|_| room)); + cx.notify(); + + Ok(()) + } else { + Err(anyhow!("Room is existed")) + } + } + pub fn push_message(&mut self, event: Event, cx: &mut Context) { // Get all pubkeys from event's tags for comparision let mut pubkeys: Vec<_> = event.tags.public_keys().copied().collect(); diff --git a/crates/common/src/profile.rs b/crates/common/src/profile.rs index e80640a..44c04c5 100644 --- a/crates/common/src/profile.rs +++ b/crates/common/src/profile.rs @@ -19,6 +19,8 @@ impl AsRef for NostrProfile { } } +impl Eq for NostrProfile {} + impl PartialEq for NostrProfile { fn eq(&self, other: &Self) -> bool { self.public_key() == other.public_key() diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs index b66deac..719f020 100644 --- a/crates/ui/src/notification.rs +++ b/crates/ui/src/notification.rs @@ -97,6 +97,7 @@ impl From<(NotificationType, SharedString)> for Notification { } struct DefaultIdType; + impl Notification { /// Create a new notification with the given content. ///