feat: zap modal

This commit is contained in:
kieran 2025-01-12 11:40:34 +00:00
parent 93e7ee18c1
commit 73d03ca0f1
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
14 changed files with 1146 additions and 54 deletions

656
Cargo.lock generated
View File

@ -137,6 +137,17 @@ dependencies = [
"generic-array",
]
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "ahash"
version = "0.8.11"
@ -886,6 +897,16 @@ version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
[[package]]
name = "calendrical_calculations"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27ca2b6e2f7d75f43e001ded6f25e79b80bded5abbe764cbdf78c25a3051f4b"
dependencies = [
"core_maths",
"displaydoc",
]
[[package]]
name = "calloop"
version = "0.13.0"
@ -1174,6 +1195,15 @@ dependencies = [
"libc",
]
[[package]]
name = "core_maths"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3"
dependencies = [
"libm",
]
[[package]]
name = "coreaudio-rs"
version = "0.11.3"
@ -1366,6 +1396,17 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "dlib"
version = "0.5.2"
@ -1459,6 +1500,15 @@ dependencies = [
"serde",
]
[[package]]
name = "egui-modal"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "297fa5697b1048198cc12f5f101312a6fcae42e55a637c861a316d6028797e42"
dependencies = [
"egui",
]
[[package]]
name = "egui-video"
version = "0.8.0"
@ -1567,6 +1617,15 @@ dependencies = [
"egui_extras",
]
[[package]]
name = "egui_qr"
version = "0.1.0"
source = "git+https://git.v0l.io/Kieran/egui_qr.git?rev=f9cf52b7eae353fa9e59ed0358151211d48824d1#f9cf52b7eae353fa9e59ed0358151211d48824d1"
dependencies = [
"egui",
"qrcode",
]
[[package]]
name = "egui_tabs"
version = "0.2.1"
@ -1620,6 +1679,15 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "email_address"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1019fa28f600f5b581b7a603d515c3f1635da041ca211b5055804788673abfe"
dependencies = [
"serde",
]
[[package]]
name = "emath"
version = "0.29.1"
@ -1869,6 +1937,17 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "fixed_decimal"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0febbeb1118a9ecdee6e4520ead6b54882e843dd0592ad233247dbee84c53db8"
dependencies = [
"displaydoc",
"smallvec",
"writeable",
]
[[package]]
name = "flatbuffers"
version = "23.5.26"
@ -2374,6 +2453,399 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "icu"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff5e3018d703f168b00dcefa540a65f1bbc50754ae32f3f5f0e43fe5ee51502"
dependencies = [
"icu_calendar",
"icu_casemap",
"icu_collator",
"icu_collections",
"icu_datetime",
"icu_decimal",
"icu_experimental",
"icu_list",
"icu_locid",
"icu_locid_transform",
"icu_normalizer",
"icu_plurals",
"icu_properties",
"icu_provider",
"icu_segmenter",
"icu_timezone",
]
[[package]]
name = "icu_calendar"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7265b2137f9a36f7634a308d91f984574bbdba8cfd95ceffe1c345552275a8ff"
dependencies = [
"calendrical_calculations",
"displaydoc",
"icu_calendar_data",
"icu_locid",
"icu_locid_transform",
"icu_provider",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_calendar_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0"
[[package]]
name = "icu_casemap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff0c8ae9f8d31b12e27fc385ff9ab1f3cd9b17417c665c49e4ec958c37da75f"
dependencies = [
"displaydoc",
"icu_casemap_data",
"icu_collections",
"icu_locid",
"icu_properties",
"icu_provider",
"writeable",
"zerovec",
]
[[package]]
name = "icu_casemap_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d57966d5ab748f74513be4046867f9a20e801e2775d41f91d04a0f560b61f08"
[[package]]
name = "icu_collator"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d370371887d31d56f361c3eaa15743e54f13bc677059c9191c77e099ed6966b2"
dependencies = [
"displaydoc",
"icu_collator_data",
"icu_collections",
"icu_locid_transform",
"icu_normalizer",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_collator_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee3f88741364b7d6269cce6827a3e6a8a2cf408a78f766c9224ab479d5e4ae5"
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_datetime"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d115efb85e08df3fd77e77f52e7e087545a783fffba8be80bfa2102f306b1780"
dependencies = [
"displaydoc",
"either",
"fixed_decimal",
"icu_calendar",
"icu_datetime_data",
"icu_decimal",
"icu_locid",
"icu_locid_transform",
"icu_plurals",
"icu_provider",
"icu_timezone",
"smallvec",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_datetime_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ba7e7f7a01269b9afb0a39eff4f8676f693b55f509b3120e43a0350a9f88bea"
[[package]]
name = "icu_decimal"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb8fd98f86ec0448d85e1edf8884e4e318bb2e121bd733ec929a05c0a5e8b0eb"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_decimal_data",
"icu_locid_transform",
"icu_provider",
"writeable",
]
[[package]]
name = "icu_decimal_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d424c994071c6f5644f999925fc868c85fec82295326e75ad5017bc94b41523"
[[package]]
name = "icu_experimental"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "844ad7b682a165c758065d694bc4d74ac67f176da1c499a04d85d492c0f193b7"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_collections",
"icu_decimal",
"icu_experimental_data",
"icu_locid",
"icu_locid_transform",
"icu_normalizer",
"icu_pattern",
"icu_plurals",
"icu_properties",
"icu_provider",
"litemap",
"num-bigint",
"num-rational",
"num-traits",
"smallvec",
"tinystr",
"writeable",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_experimental_data"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c178b9a34083fca5bd70d61f647575335e9c197d0f30c38e8ccd187babc69d0"
[[package]]
name = "icu_list"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfeda1d7775b6548edd4e8b7562304a559a91ed56ab56e18961a053f367c365"
dependencies = [
"displaydoc",
"icu_list_data",
"icu_locid_transform",
"icu_provider",
"regex-automata 0.2.0",
"writeable",
]
[[package]]
name = "icu_list_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1825170d2c6679cb20dbd96a589d034e49f698aed9a2ef4fafc9a0101ed298f"
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_pattern"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f36aafd098d6717de34e668a8120822275c1fba22b936e757b7de8a2fd7e4"
dependencies = [
"displaydoc",
"either",
"writeable",
"yoke",
"zerofrom",
]
[[package]]
name = "icu_plurals"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a70e7c025dbd5c501b0a5c188cd11666a424f0dadcd4f0a95b7dafde3b114"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_locid_transform",
"icu_plurals_data",
"icu_provider",
"zerovec",
]
[[package]]
name = "icu_plurals_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3e8f775b215d45838814a090a2227247a7431d74e9156407d9c37f6ef0f208"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "icu_segmenter"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de"
dependencies = [
"core_maths",
"displaydoc",
"icu_collections",
"icu_locid",
"icu_provider",
"icu_segmenter_data",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_segmenter_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df"
[[package]]
name = "icu_timezone"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa91ba6a585939a020c787235daa8aee856d9bceebd6355e283c0c310bc6de96"
dependencies = [
"displaydoc",
"icu_calendar",
"icu_provider",
"icu_timezone_data",
"tinystr",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_timezone_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c588878c508a3e2ace333b3c50296053e6483c6a7541251b546cc59dcd6ced8e"
[[package]]
name = "idna"
version = "0.5.0"
@ -2660,6 +3132,12 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "libm"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "libredox"
version = "0.1.3"
@ -2677,12 +3155,36 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "litemap"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "lnurl-rs"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41eacdd87b675792f7752f3dd0937a00241a504c3956c47f72986490662e1db4"
dependencies = [
"aes",
"anyhow",
"base64 0.22.1",
"bech32",
"bitcoin",
"cbc",
"email_address",
"serde",
"serde_json",
"url",
]
[[package]]
name = "lock_api"
version = "0.4.12"
@ -2993,6 +3495,7 @@ version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aad4b767bbed24ac5eb4465bfb83bc1210522eb99d67cf4e547ec2ec7e47786"
dependencies = [
"aes",
"async-trait",
"base64 0.22.1",
"bech32",
@ -3828,6 +4331,12 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "qrcode"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
[[package]]
name = "quick-error"
version = "2.0.1"
@ -4029,6 +4538,15 @@ dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-automata"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782"
dependencies = [
"memchr",
]
[[package]]
name = "regex-automata"
version = "0.4.8"
@ -4504,6 +5022,12 @@ dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -4576,6 +5100,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "system-deps"
version = "6.2.2"
@ -4735,6 +5270,16 @@ dependencies = [
"strict-num",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
@ -5099,6 +5644,18 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.11.0"
@ -5936,6 +6493,21 @@ dependencies = [
"memchr",
]
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
dependencies = [
"either",
]
[[package]]
name = "x11-dl"
version = "2.21.0"
@ -6015,6 +6587,30 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "yoke"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
"synstructure",
]
[[package]]
name = "zap_stream_app"
version = "0.1.0"
@ -6027,10 +6623,16 @@ dependencies = [
"directories",
"eframe",
"egui",
"egui-modal",
"egui-video",
"egui_qr",
"ehttp 0.5.0",
"enostr",
"fixed_decimal",
"icu",
"icu_decimal",
"itertools 0.14.0",
"lnurl-rs",
"log",
"nostr",
"nostrdb",
@ -6163,12 +6765,66 @@ dependencies = [
"syn 2.0.90",
]
[[package]]
name = "zerofrom"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zerotrie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "zune-core"
version = "0.4.12"

View File

@ -20,15 +20,21 @@ itertools = "0.14.0"
serde = { version = "1.0.214", features = ["derive"] }
directories = "5.0.1"
egui-video = { git = "https://github.com/v0l/egui-video.git", rev = "d2ea3b4db21eb870a207db19e4cd21c7d1d24836" }
egui_qr = { git = "https://git.v0l.io/Kieran/egui_qr.git", rev = "f9cf52b7eae353fa9e59ed0358151211d48824d1" }
# notedeck stuff
nostr = { version = "0.37.0", default-features = false, features = ["std", "nip49"] }
nostr = { version = "0.37.0", default-features = false, features = ["std", "nip49", "nip57"] }
nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "2111948b078b24a1659d0bd5d8570f370269c99b" }
notedeck-chrome = { git = "https://github.com/damus-io/notedeck", rev = "06417ff69e772f24ffd7fb2b025f879463d8c51f", package = "notedeck_chrome" }
notedeck = { git = "https://github.com/damus-io/notedeck", rev = "06417ff69e772f24ffd7fb2b025f879463d8c51f", package = "notedeck" }
enostr = { git = "https://github.com/damus-io/notedeck", rev = "06417ff69e772f24ffd7fb2b025f879463d8c51f", package = "enostr" }
poll-promise = "0.3.0"
ehttp = "0.5.0"
egui-modal = "0.5.0"
icu = "1.5.0"
icu_decimal = "1.5.0"
fixed_decimal = "0.5.6"
lnurl-rs = { version = "0.9.0", default-features = false }
[target.'cfg(not(target_os = "android"))'.dependencies]
eframe = { version = "0.29.1" }

View File

@ -8,6 +8,8 @@ use enostr::{PoolEvent, RelayEvent, RelayMessage};
use log::{error, info, warn};
use nostrdb::{Filter, Transaction};
use notedeck::AppContext;
use poll_promise::Promise;
use std::collections::HashMap;
use std::sync::mpsc;
pub struct ZapStreamApp {
@ -20,6 +22,7 @@ pub struct ZapStreamApp {
widget: Box<dyn NostrWidget>,
profiles: ProfileLoader,
fetch: HashMap<String, Promise<ehttp::Result<ehttp::Response>>>,
}
#[cfg(target_os = "android")]
@ -65,6 +68,7 @@ impl ZapStreamApp {
profiles: ProfileLoader::new(),
routes_tx: tx,
routes_rx: rx,
fetch: HashMap::new(),
}
}
}
@ -132,12 +136,13 @@ impl notedeck::App for ZapStreamApp {
let tx = Transaction::new(ctx.ndb).expect("transaction");
// display app
ui.vertical(|ui| {
let mut svc = RouteServices {
router: self.routes_tx.clone(),
tx: &tx,
egui: ui.ctx().clone(),
let mut svc = RouteServices::new(
ui.ctx().clone(),
&tx,
ctx,
};
self.routes_tx.clone(),
&mut self.fetch,
);
Header::new().render(ui, &mut svc, &tx);
if let Err(e) = self.widget.update(&mut svc) {
error!("{}", e);

View File

@ -1,10 +1,11 @@
use crate::note_util::NoteUtil;
use bech32::{Hrp, NoChecksum};
use nostr::prelude::hex;
use nostrdb::{Filter, Note};
use nostr::prelude::{hex, Coordinate};
use nostr::{Kind, PublicKey};
use nostrdb::{Filter, NdbStrVariant, Note};
use std::fmt::{Display, Formatter};
#[derive(Clone, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct NostrLink {
pub hrp: NostrLinkType,
pub id: IdOrStr,
@ -13,7 +14,7 @@ pub struct NostrLink {
pub relays: Vec<String>,
}
#[derive(Clone, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq, Hash)]
pub enum IdOrStr {
Id([u8; 32]),
Str(String),
@ -28,7 +29,7 @@ impl Display for IdOrStr {
}
}
#[derive(Clone, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq, Hash)]
pub enum NostrLinkType {
Note,
PublicKey,
@ -58,22 +59,16 @@ impl NostrLink {
}
pub fn from_note(note: &Note<'_>) -> Self {
if note.kind() >= 30_000
&& note.kind() < 40_000
&& note
.get_tag_value("d")
.and_then(|v| v.variant().str())
.is_some()
{
if note.kind() >= 30_000 && note.kind() < 40_000 {
Self {
hrp: NostrLinkType::Coordinate,
id: IdOrStr::Str(
note.get_tag_value("d")
.unwrap()
.variant()
.str()
.unwrap()
.to_string(),
.map(|t| match t.variant() {
NdbStrVariant::Id(s) => hex::encode(s),
NdbStrVariant::Str(s) => s.to_owned(),
})
.unwrap_or(String::from("")),
),
kind: Some(note.kind()),
author: Some(*note.pubkey()),
@ -171,3 +166,18 @@ impl Display for NostrLink {
}
}
}
impl TryInto<Coordinate> for NostrLink {
type Error = ();
fn try_into(self) -> Result<Coordinate, Self::Error> {
match self.hrp {
NostrLinkType::Coordinate => Ok(Coordinate::new(
Kind::from_u16(self.kind.unwrap() as u16),
PublicKey::from_slice(&self.author.unwrap()).unwrap(),
)
.identifier(self.id.to_string())),
_ => Err(()),
}
}
}

View File

@ -1,13 +1,20 @@
use crate::link::NostrLink;
use crate::services::ffmpeg_loader::FfmpegLoader;
use crate::PollOption;
use anyhow::{anyhow, bail};
use egui::load::SizedTexture;
use egui::{Context, Image, TextureHandle};
use egui::{Context, Id, Image, TextureHandle};
use ehttp::Response;
use enostr::EventClientMessage;
use lnurl::lightning_address::LightningAddress;
use lnurl::pay::PayResponse;
use lnurl::LnUrlResponse;
use log::{info, warn};
use nostr::{Event, EventBuilder, JsonUtil, Kind, Tag};
use nostr::{serde_json, Event, EventBuilder, JsonUtil, Keys, Kind, SecretKey, Tag};
use nostrdb::{NdbProfile, NoteKey, Transaction};
use notedeck::{AppContext, ImageCache};
use poll_promise::Promise;
use std::collections::HashMap;
use std::path::Path;
use std::sync::mpsc;
use std::task::Poll;
@ -46,13 +53,31 @@ pub enum RouteAction {
}
pub struct RouteServices<'a, 'ctx> {
pub router: mpsc::Sender<RouteType>,
pub egui: Context,
pub tx: &'a Transaction,
pub ctx: &'a mut AppContext<'ctx>,
router: mpsc::Sender<RouteType>,
fetch: &'a mut HashMap<String, Promise<ehttp::Result<Response>>>,
}
impl<'a> RouteServices<'a, '_> {
impl<'a, 'ctx> RouteServices<'a, 'ctx> {
pub fn new(
egui: Context,
tx: &'a Transaction,
ctx: &'a mut AppContext<'ctx>,
router: mpsc::Sender<RouteType>,
fetch: &'a mut HashMap<String, Promise<ehttp::Result<Response>>>,
) -> Self {
Self {
egui,
tx,
ctx,
router,
fetch,
}
}
pub fn navigate(&self, route: RouteType) {
self.router.send(route).expect("route send failed");
self.egui.request_repaint();
@ -103,23 +128,76 @@ impl<'a> RouteServices<'a, '_> {
Image::from_bytes(name, data)
}
/// Create a poll_promise fetch
pub fn fetch(&mut self, url: &str) -> Poll<&ehttp::Result<Response>> {
if !self.fetch.contains_key(url) {
let (sender, promise) = Promise::new();
let request = ehttp::Request::get(url);
let ctx = self.egui.clone();
ehttp::fetch(request, move |response| {
sender.send(response);
ctx.request_repaint();
});
info!("Fetching {}", url);
self.fetch.insert(url.to_string(), promise);
}
self.fetch.get(url).expect("fetch").poll()
}
pub fn fetch_lnurlp(&mut self, pubkey: &[u8; 32]) -> anyhow::Result<Poll<PayResponse>> {
let target = self
.profile(pubkey)
.and_then(|p| p.lud16())
.ok_or(anyhow!("No lightning address found"))?;
let addr = LightningAddress::new(target)?;
match self.fetch(&addr.lnurlp_url()) {
Poll::Ready(Ok(r)) => {
if r.ok {
let rsp: PayResponse = serde_json::from_slice(&r.bytes)?;
Ok(Poll::Ready(rsp))
} else {
bail!("Invalid response code {}", r.status);
}
}
Poll::Ready(Err(e)) => Err(anyhow!("{}", e)),
Poll::Pending => Ok(Poll::Pending),
}
}
pub fn write_live_chat_msg(&self, link: &NostrLink, msg: &str) -> Option<Event> {
if msg.is_empty() {
return None;
}
if let Some(acc) = self.ctx.accounts.get_selected_account() {
if let Some(key) = &acc.secret_key {
let nostr_key =
nostr::Keys::new(nostr::SecretKey::from_slice(key.as_secret_bytes()).unwrap());
return EventBuilder::new(Kind::LiveEventMessage, msg)
if let Some(key) = self.current_account_keys() {
EventBuilder::new(Kind::LiveEventMessage, msg)
.tag(Tag::parse(link.to_tag()).unwrap())
.sign_with_keys(&nostr_key)
.ok();
}
}
.sign_with_keys(&key)
.ok()
} else {
None
}
}
pub fn current_account_keys(&self) -> Option<Keys> {
self.ctx
.accounts
.get_selected_account()
.and_then(|acc| acc.secret_key.as_ref().map(|k| Keys::new(k.clone())))
}
/// Simple wrapper around egui temp data
pub fn get<T: Clone + 'static>(&self, k: &str) -> Option<T> {
let id = Id::new(k);
self.egui.data(|d| d.get_temp(id))
}
/// Simple wrapper around egui temp data
pub fn set<T: Clone + Send + Sync + 'static>(&mut self, k: &str, v: T) {
self.egui.data_mut(|d| d.insert_temp(Id::new(k), v));
}
}
const BLACK_PIXEL: [u8; 4] = [0, 0, 0, 0];
pub fn image_from_cache<'a>(img_cache: &mut ImageCache, ctx: &Context, url: &str) -> Image<'a> {
if let Some(promise) = img_cache.map().get(url) {

View File

@ -1,10 +1,12 @@
use egui::{Color32, Margin};
pub const FONT_SIZE: f32 = 13.0;
pub const FONT_SIZE_LG: f32 = FONT_SIZE * 1.5;
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_700: Color32 = Color32::from_rgb(64, 64, 64);
pub const NEUTRAL_800: Color32 = Color32::from_rgb(38, 38, 38);
pub const NEUTRAL_900: Color32 = Color32::from_rgb(23, 23, 23);
pub const ZAP: Color32 = Color32::from_rgb(255, 141, 43);

View File

@ -50,7 +50,7 @@ impl Avatar {
response
}
pub fn render(&self, ui: &mut Ui, img_cache: &mut ImageCache) -> Response {
pub fn render(self, ui: &mut Ui, img_cache: &mut ImageCache) -> Response {
let size_v = self.size.unwrap_or(40.);
let size = Vec2::new(size_v, size_v);
if !ui.is_visible() {

View File

@ -1,13 +1,35 @@
use crate::theme::{NEUTRAL_800, ROUNDING_DEFAULT};
use egui::{Color32, CursorIcon, Frame, Margin, Response, Sense, Ui};
use crate::theme::{MARGIN_DEFAULT, NEUTRAL_800, ROUNDING_DEFAULT};
use egui::{Color32, CursorIcon, Frame, Response, Sense, Ui, WidgetText};
pub struct Button {
color: Color32,
disabled: bool,
}
impl Button {
pub fn new() -> Self {
Self { color: NEUTRAL_800 }
Self {
color: NEUTRAL_800,
disabled: false,
}
}
pub fn with_color(mut self, color: impl Into<Color32>) -> Self {
self.color = color.into();
self
}
pub fn disabled(mut self, v: bool) -> Self {
self.disabled = v;
self
}
pub fn simple(ui: &mut Ui, content: &str) -> Response {
Button::new().show(ui, |ui| ui.label(content))
}
pub fn text(self, ui: &mut Ui, text: impl Into<WidgetText>) -> Response {
self.show(ui, |ui| ui.label(text))
}
pub fn show<F>(self, ui: &mut Ui, add_contents: F) -> Response
@ -15,15 +37,20 @@ impl Button {
F: FnOnce(&mut Ui) -> Response,
{
let r = Frame::none()
.inner_margin(Margin::symmetric(12., 8.))
.inner_margin(MARGIN_DEFAULT)
.fill(self.color)
.rounding(ROUNDING_DEFAULT)
.multiply_with_opacity(if self.disabled { 0.5 } else { 1.0 })
.show(ui, add_contents);
let id = r.response.id;
ui.interact(
r.response
.on_hover_and_drag_cursor(CursorIcon::PointingHand)
.on_hover_and_drag_cursor(if self.disabled {
CursorIcon::NotAllowed
} else {
CursorIcon::PointingHand
})
.rect,
id,
Sense::click(),

View File

@ -1,6 +1,6 @@
use crate::theme::{MARGIN_DEFAULT, ROUNDING_DEFAULT, ZAP};
use crate::widgets::Avatar;
use crate::zap::Zap;
use crate::zap::{format_sats, Zap};
use eframe::emath::Align;
use eframe::epaint::text::{LayoutJob, TextFormat, TextWrapMode};
use eframe::epaint::Color32;
@ -43,7 +43,7 @@ impl<'a> ChatZap<'a> {
job.append("zapped", 5.0, format.clone());
format.color = ZAP;
job.append(
(self.zap.amount / 1000).to_string().as_str(),
&format_sats((self.zap.amount / 1000) as f32),
5.0,
format.clone(),
);

View File

@ -13,6 +13,7 @@ mod stream_title;
mod text_input;
mod username;
mod write_chat;
mod zap;
use crate::note_ref::NoteRef;
use crate::route::RouteServices;

View File

@ -1,8 +1,10 @@
use crate::note_util::NoteUtil;
use crate::route::RouteServices;
use crate::stream_info::StreamInfo;
use crate::theme::MARGIN_DEFAULT;
use crate::widgets::zap::ZapButton;
use crate::widgets::Profile;
use egui::{Color32, Frame, Label, Margin, Response, RichText, TextWrapMode, Ui};
use egui::{Color32, Frame, Label, Response, RichText, TextWrapMode, Ui};
use nostrdb::Note;
pub struct StreamTitle<'a> {
@ -15,17 +17,19 @@ impl<'a> StreamTitle<'a> {
}
pub fn render(&mut self, ui: &mut Ui, services: &mut RouteServices<'_, '_>) -> Response {
Frame::none()
.outer_margin(Margin::symmetric(12., 8.))
.outer_margin(MARGIN_DEFAULT)
.show(ui, |ui| {
ui.style_mut().spacing.item_spacing.y = 8.;
let title = RichText::new(self.event.title().unwrap_or("Untitled"))
.size(20.)
.color(Color32::WHITE);
ui.add(Label::new(title.strong()).wrap_mode(TextWrapMode::Truncate));
ui.add(Label::new(title.strong()).wrap_mode(TextWrapMode::Wrap));
ui.horizontal(|ui| {
Profile::new(self.event.host())
.size(32.)
.render(ui, services);
ZapButton::event(self.event).render(ui, services);
});
if let Some(summary) = self
.event
@ -34,7 +38,7 @@ impl<'a> StreamTitle<'a> {
{
if !summary.is_empty() {
let summary = RichText::new(summary).color(Color32::WHITE);
ui.add(Label::new(summary).wrap_mode(TextWrapMode::Truncate));
ui.add(Label::new(summary).wrap_mode(TextWrapMode::Wrap));
}
}
})

286
src/widgets/zap.rs Normal file
View File

@ -0,0 +1,286 @@
use crate::link::NostrLink;
use crate::route::RouteServices;
use crate::stream_info::StreamInfo;
use crate::theme::{
FONT_SIZE_LG, MARGIN_DEFAULT, NEUTRAL_700, NEUTRAL_800, NEUTRAL_900, PRIMARY, ROUNDING_DEFAULT,
};
use crate::widgets::{Button, NativeTextInput};
use crate::zap::format_sats;
use anyhow::{anyhow, bail};
use egui::{vec2, Frame, Grid, Response, RichText, Stroke, Ui, Widget};
use egui_modal::Modal;
use egui_qr::QrCodeWidget;
use enostr::PoolRelay;
use itertools::Itertools;
use lnurl::pay::{LnURLPayInvoice, PayResponse};
use nostr::prelude::{hex, ZapRequestData};
use nostr::{serde_json, EventBuilder, JsonUtil, Kind, PublicKey, Tag, Url};
use nostrdb::Note;
use std::fmt::{Display, Formatter};
use std::task::Poll;
pub enum ZapTarget<'a> {
PublicKey { pubkey: [u8; 32] },
Event { event: &'a Note<'a> },
}
impl Display for ZapTarget<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ZapTarget::PublicKey { pubkey } => write!(f, "{}", hex::encode(pubkey)),
ZapTarget::Event { event } => write!(f, "{}", hex::encode(event.id())),
}
}
}
#[derive(Clone)]
pub enum ZapState {
NotStarted,
Ready { service: PayResponse },
FetchingInvoice { callback: String },
Invoice { invoice: LnURLPayInvoice },
Error(String),
}
pub struct ZapButton<'a> {
target: ZapTarget<'a>,
}
impl<'a> ZapButton<'a> {
pub fn pubkey(pubkey: [u8; 32]) -> Self {
Self {
target: ZapTarget::PublicKey { pubkey },
}
}
pub fn event(event: &'a Note<'a>) -> Self {
Self {
target: ZapTarget::Event { event },
}
}
pub fn render(self, ui: &mut Ui, services: &mut RouteServices) -> Response {
// TODO: fix id
let modal = Modal::new(ui.ctx(), format!("zapper-{}", 0)).with_close_on_outside_click(true);
let resp = Button::new().show(ui, |ui| ui.label("ZAP"));
if resp.clicked() {
modal.open();
}
ui.visuals_mut().window_rounding = ROUNDING_DEFAULT.into();
ui.visuals_mut().window_stroke = Stroke::NONE;
ui.visuals_mut().window_fill = NEUTRAL_900;
modal.show(|ui| {
Frame::none().inner_margin(MARGIN_DEFAULT).show(ui, |ui| {
ui.spacing_mut().item_spacing = vec2(8.0, 8.0);
let pubkey = match &self.target {
ZapTarget::PublicKey { pubkey } => pubkey,
ZapTarget::Event { event } => event.host(),
};
// zapping state machine
let zap_state = services.get("zap_state").unwrap_or(ZapState::NotStarted);
match &zap_state {
ZapState::NotStarted => match services.fetch_lnurlp(pubkey) {
Ok(Poll::Ready(r)) => {
services.set("zap_state", ZapState::Ready { service: r })
}
Err(e) => services.set("zap_state", ZapState::Error(e.to_string())),
_ => {}
},
ZapState::FetchingInvoice { callback } => {
match self.zap_get_invoice(callback, services) {
Ok(Poll::Ready(s)) => {
services.set("zap_state", ZapState::Invoice { invoice: s })
}
Err(e) => services.set("zap_state", ZapState::Error(e.to_string())),
_ => {}
}
}
_ => {}
}
// when ready state, show zap button
match &zap_state {
ZapState::Ready { service } => {
self.render_input(ui, services, pubkey, service);
}
ZapState::Invoice { invoice } => {
if let Ok(q) = QrCodeWidget::from_data(invoice.pr.as_bytes()) {
ui.add_sized(vec2(256., 256.), q);
let rt = RichText::new(&invoice.pr).code();
ui.label(rt);
}
}
ZapState::Error(e) => {
ui.label(e);
}
_ => {}
}
})
});
if modal.was_outside_clicked() {
services.set("zap_state", ZapState::NotStarted)
}
resp
}
fn zap_get_invoice(
&self,
callback: &str,
services: &mut RouteServices,
) -> anyhow::Result<Poll<LnURLPayInvoice>> {
match services.fetch(callback) {
Poll::Ready(Ok(r)) => {
if r.ok {
let inv: LnURLPayInvoice = serde_json::from_slice(&r.bytes)?;
Ok(Poll::Ready(inv))
} else {
bail!("Invalid response code {}", r.status);
}
}
Poll::Ready(Err(e)) => Err(anyhow!("{}", e)),
Poll::Pending => Ok(Poll::Pending),
}
}
fn render_input(
&self,
ui: &mut Ui,
services: &mut RouteServices,
pubkey: &[u8; 32],
service: &PayResponse,
) {
let target_name = match self.target {
ZapTarget::PublicKey { pubkey } => services.profile(&pubkey).and_then(|p| p.name()),
ZapTarget::Event { event } => {
let host = event.host();
services.profile(host).and_then(|p| p.name())
}
};
let fallback_name = self.target.to_string();
let target_name = target_name.unwrap_or(&fallback_name);
ui.label(RichText::new(format!("Zap {}", target_name)).size(FONT_SIZE_LG));
ui.label("Zap amount in sats");
// amount buttons
const SATS_AMOUNTS: &[u64] = &[
21, 69, 121, 420, 1_000, 2_100, 4_200, 10_000, 21_000, 42_000, 69_000, 100_000,
210_000, 500_000, 1_000_000,
];
const COLS: u32 = 5;
let selected_amount = services.get("zap_amount").unwrap_or(0);
Grid::new("zap_amounts_grid").show(ui, |ui| {
let mut ctr = 0;
for x in SATS_AMOUNTS {
if Button::new()
.with_color(if selected_amount == *x {
NEUTRAL_700
} else {
NEUTRAL_800
})
.text(ui, &format_sats(*x as f32))
.clicked()
{
services.set("zap_amount", *x);
}
ctr += 1;
if ctr % COLS == 0 {
ui.end_row();
}
}
});
// comment section
let mut zap_comment = services.get("zap_comment").unwrap_or(String::new());
ui.label(format!("Your comment for {}", target_name));
let old_len = zap_comment.len();
NativeTextInput::new(&mut zap_comment)
.with_frame(true)
.ui(ui);
if Button::new().with_color(PRIMARY).text(ui, "Zap!").clicked() {
// on-click setup callback URL and transition state
match self.zap_callback(
services,
pubkey,
&zap_comment,
selected_amount * 1_000,
&service,
) {
Ok(callback) => services.set(
"zap_state",
ZapState::FetchingInvoice {
callback: callback.to_string(),
},
),
Err(e) => services.set("zap_state", ZapState::Error(e.to_string())),
}
}
if zap_comment.len() != old_len {
services.set("zap_comment", zap_comment);
}
}
fn zap_callback(
&self,
services: &mut RouteServices,
pubkey: &[u8; 32],
zap_comment: &str,
amount: u64,
lnurlp: &PayResponse,
) -> anyhow::Result<Url> {
let relays: Vec<Url> = services
.ctx
.pool
.relays
.iter()
.filter_map(|r| match r {
PoolRelay::Websocket(w) => Url::parse(&w.relay.url).ok(),
_ => None,
})
.collect();
if relays.is_empty() {
bail!("No relays found");
}
let mut req = ZapRequestData::new(PublicKey::from_slice(pubkey)?, relays)
.message(zap_comment)
.amount(amount);
match &self.target {
ZapTarget::Event { event } => {
req.event_coordinate = Some(
NostrLink::from_note(event)
.try_into()
.map_err(|e| anyhow!("{:?}", e))?,
)
}
_ => {}
};
let req_tags: Vec<Tag> = req.into();
let keys = if let Some(k) = services.current_account_keys() {
k
} else {
bail!("Not logged in")
};
let req_ev = EventBuilder::new(Kind::ZapRequest, zap_comment)
.tags(req_tags)
.sign_with_keys(&keys)?;
let mut url = Url::parse(&lnurlp.callback)?;
url.query_pairs_mut()
.append_pair("amount", amount.to_string().as_str());
if lnurlp.nostr_pubkey.is_some() {
url.query_pairs_mut()
.append_pair("nostr", req_ev.as_json().as_str());
}
Ok(url)
}
}

View File

@ -1,5 +1,8 @@
use crate::note_util::NoteUtil;
use anyhow::{anyhow, bail, Result};
use fixed_decimal::FixedDecimal;
use icu::decimal::FixedDecimalFormatter;
use icu::locid::Locale;
use nostr::{Event, JsonUtil, Kind, TagStandard};
use nostrdb::Note;
@ -54,3 +57,17 @@ impl<'a> Zap<'a> {
})
}
}
pub fn format_sats(n: f32) -> String {
let (div_n, suffix) = if n >= 1_000. && n < 1_000_000. {
(1_000., "K")
} else if n >= 1_000_000. {
(1_000_000., "M")
} else {
(1., "")
};
let fmt = FixedDecimalFormatter::try_new(&Locale::UND.into(), Default::default()).expect("icu");
let d: FixedDecimal = (n / div_n).to_string().parse().expect("fixed decimal");
format!("{}{}", fmt.format_to_string(&d), suffix)
}