1
0
mirror of git://jb55.com/damus synced 2024-09-29 16:30:44 +00:00

Compare commits

...

14 Commits

Author SHA1 Message Date
William Casarin
b31b917b70 Merge remote-tracking branch 'github/quote-reposts'
This adds quote repost listing support to Damus!

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 09:33:29 +00:00
William Casarin
c521998158 ui: add quoted reposts view to threads
This adds quote reposts as an additional detail view on threads. It will
list quoted reposts that have the `q` tag. Not all clients have updated
to this yet (like primal), but hopefully they will soon.

Changelog-Added: Show list of quoted reposts in threads
Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
3f1f257df2 model: upgrade EventsModel to support quote reposts queries
We also switch to using an eventholder because that is a bit nicer
when it comes to rendering quotes in timelines.

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
1339ec3ded note: add is_quote_repost helper
This will be used for excluding quote repost notes from threads

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
770a845b36 filters: add ContentFilters helper constructor
This is slightly faster for timeline code that needs default filters

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
68dd47130e eventsmodel: remove inheritence in Reactions/Reposts model
Simplify with new EventsModel constructors. This is slightly less
typesafe but its not a big deal, I hate inheritence more.

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
8cdbc84093 home: add quote repost counter and handler
This adds the initial support code for counting and handling
quote reposts.

Eventually we are going to replace all of the event counts by stats
within nostrdb, but we do this in the meantime now.

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-17 08:54:35 +00:00
William Casarin
6111e244de filter: add reposts query filter helper
Add a filter helper to easily query quote repost queries.

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-16 12:03:08 +00:00
William Casarin
0043f0059d strings: add pluralized quoted_repost_count string
We will be using this for translating "Quote{,s}" pluralization. For now
we add the english version.

Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-16 11:59:56 +00:00
alltheseas
4413ec0ec5
Update README.md
added reference to nip-04 encrypted DM support, including link to nostr-protocol nip-04 page
2024-03-13 10:06:01 -05:00
ericholguin
9a83872a22 ui: Add proxy view to selected events
This patch adds the proxy view to selected events.

Fixes: https://github.com/damus-io/damus/issues/2033
Changelog-Added: Proxy Tags are now viewable on Selected Events
Signed-off-by: ericholguin <ericholguin@apache.org>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-12 09:35:28 +00:00
ericholguin
988da17b06 Minor Fixes
This adds the recommended parameter to the relay view used in Wallet view.

Signed-off-by: ericholguin <ericholguin@apache.org>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-12 09:34:21 +00:00
ericholguin
5e530bfc9c ui: Wallet View redesign + Mutiny Wallet integration
This patch redesigns the wallet view to more closely match Rob's design.
In addition this patch allows users to connect to Mutiny Wallet by clicking a button.

iPhone SE (3rd generation) Dark Mode:
https://v.nostr.build/K9lk.mp4

iPhone 15 Pro Max Light Mode:
https://v.nostr.build/9mKA.mp4

Connected Alby Wallet:
https://i.nostr.build/kyd5.png

Changelog-Added: Connect to Mutiny Wallet Button
Changelog-Changed: Moved paste nwc button to main wallet view
Changelog-Changed: Errors with an NWC will show as an alert
Changelog-Fixed: Issue where NWC Scanner view would not dismiss after a failed scan/paste
Signed-off-by: ericholguin <ericholguin@apache.org>
Reviewed-by: William Casarin <jb55@jb55.com>
Link: 20240310223713.4541-1-ericholguin@apache.org
Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-11 10:12:05 +00:00
ericholguin
0719e94fbc ux: Relay View Improvements
This patch removes the Recommended Relay View and the old representation of recommended relays.
Adds a tab view style to the Relay Config View allowing the user to switch between their connected relays
and recommended relays. They can add and remove from the recommended view as well. For users logged in with
a pubkey the add button will no longer be displayed.

Testing
——
iPhone 15 Pro Max (17.0) Light Mode:
https://v.nostr.build/QGMZ.mp4

iPhone SE (3rd generation) (16.4) Dark Mode:
https://v.nostr.build/Wlw3.mp4
——

Changelog-Changed: Relay config view user interface

Signed-off-by: ericholguin <ericholguin@apache.org>
Link: 20240307152808.47929-1-ericholguin@apache.org
Signed-off-by: William Casarin <jb55@jb55.com>
2024-03-11 10:03:49 +00:00
40 changed files with 768 additions and 562 deletions

View File

@ -30,6 +30,7 @@ Damus has also graciously received donations or grants from hundreds of Damus us
damus implements the following [Nostr Implementation Possibilities][nips] damus implements the following [Nostr Implementation Possibilities][nips]
- [NIP-01: Basic protocol flow][nip01] - [NIP-01: Basic protocol flow][nip01]
- [NIP-04: Encrypted direct message][nip04]
- [NIP-08: Mentions][nip08] - [NIP-08: Mentions][nip08]
- [NIP-10: Reply conventions][nip10] - [NIP-10: Reply conventions][nip10]
- [NIP-12: Generic tag queries (hashtags)][nip12] - [NIP-12: Generic tag queries (hashtags)][nip12]
@ -41,6 +42,7 @@ damus implements the following [Nostr Implementation Possibilities][nips]
[nips]: https://github.com/nostr-protocol/nips [nips]: https://github.com/nostr-protocol/nips
[nip01]: https://github.com/nostr-protocol/nips/blob/master/01.md [nip01]: https://github.com/nostr-protocol/nips/blob/master/01.md
[nip04]: https://github.com/nostr-protocol/nips/blob/master/04.md
[nip08]: https://github.com/nostr-protocol/nips/blob/master/08.md [nip08]: https://github.com/nostr-protocol/nips/blob/master/08.md
[nip10]: https://github.com/nostr-protocol/nips/blob/master/10.md [nip10]: https://github.com/nostr-protocol/nips/blob/master/10.md
[nip12]: https://github.com/nostr-protocol/nips/blob/master/12.md [nip12]: https://github.com/nostr-protocol/nips/blob/master/12.md

View File

@ -25,7 +25,6 @@
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; }; 3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
3A90B1812A4EA3AF00000D94 /* UserSearchCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90B1802A4EA3AF00000D94 /* UserSearchCache.swift */; }; 3A90B1812A4EA3AF00000D94 /* UserSearchCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90B1802A4EA3AF00000D94 /* UserSearchCache.swift */; };
3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90B1822A4EA3C600000D94 /* UserSearchCacheTests.swift */; }; 3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90B1822A4EA3C600000D94 /* UserSearchCacheTests.swift */; };
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FC297E3CFF0090C62D /* RepostsModel.swift */; };
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; }; 3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; }; 3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA59D1C2999B0400061C48E /* DraftsModel.swift */; }; 3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA59D1C2999B0400061C48E /* DraftsModel.swift */; };
@ -61,7 +60,6 @@
4C1253662A76D0FF0004F4B8 /* OnlyZapsNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253652A76D0FF0004F4B8 /* OnlyZapsNotify.swift */; }; 4C1253662A76D0FF0004F4B8 /* OnlyZapsNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253652A76D0FF0004F4B8 /* OnlyZapsNotify.swift */; };
4C1253682A76D2470004F4B8 /* MuteNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253672A76D2470004F4B8 /* MuteNotify.swift */; }; 4C1253682A76D2470004F4B8 /* MuteNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253672A76D2470004F4B8 /* MuteNotify.swift */; };
4C12536A2A76D3850004F4B8 /* RelaysChangedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253692A76D3850004F4B8 /* RelaysChangedNotify.swift */; }; 4C12536A2A76D3850004F4B8 /* RelaysChangedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253692A76D3850004F4B8 /* RelaysChangedNotify.swift */; };
4C12536C2A76D4B00004F4B8 /* RepostedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C12536B2A76D4B00004F4B8 /* RepostedNotify.swift */; };
4C15C7152A55DE7A00D0A0DB /* ReactionsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C15C7142A55DE7A00D0A0DB /* ReactionsSettingsView.swift */; }; 4C15C7152A55DE7A00D0A0DB /* ReactionsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C15C7142A55DE7A00D0A0DB /* ReactionsSettingsView.swift */; };
4C190F202A535FC200027FD5 /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */; }; 4C190F202A535FC200027FD5 /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */; };
4C190F252A547D2000027FD5 /* LoadScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F242A547D2000027FD5 /* LoadScript.swift */; }; 4C190F252A547D2000027FD5 /* LoadScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C190F242A547D2000027FD5 /* LoadScript.swift */; };
@ -255,6 +253,7 @@
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276E2A2A5D110098A105 /* wasm.c */; }; 4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276E2A2A5D110098A105 /* wasm.c */; };
4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */; }; 4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4F14A92A2A71AB0045A0B9 /* nostrscript.c */; };
4C9147002A2A891E00DDEA40 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C9146FF2A2A891E00DDEA40 /* error.c */; }; 4C9147002A2A891E00DDEA40 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C9146FF2A2A891E00DDEA40 /* error.c */; };
4C94D6432BA5AEFE00C26EFF /* QuoteRepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C94D6422BA5AEFE00C26EFF /* QuoteRepostsView.swift */; };
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; }; 4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */; }; 4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */; };
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.swift */; }; 4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.swift */; };
@ -288,14 +287,12 @@
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; }; 4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; }; 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; }; 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */; };
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */; }; 4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */; };
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838529656C8B00DC99E7 /* NIP05.swift */; }; 4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838529656C8B00DC99E7 /* NIP05.swift */; };
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */; }; 4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */; };
4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */; }; 4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */; };
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838C296F710400DC99E7 /* Reposted.swift */; }; 4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838C296F710400DC99E7 /* Reposted.swift */; };
4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838E296F781C00DC99E7 /* ReactionsView.swift */; }; 4CB8838F296F781C00DC99E7 /* ReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838E296F781C00DC99E7 /* ReactionsView.swift */; };
4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88392296F798300DC99E7 /* ReactionsModel.swift */; };
4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88395296F7F8B00DC99E7 /* ReactionView.swift */; }; 4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88395296F7F8B00DC99E7 /* ReactionView.swift */; };
4CB8839A297322D200DC99E7 /* DMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88399297322D200DC99E7 /* DMTests.swift */; }; 4CB8839A297322D200DC99E7 /* DMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB88399297322D200DC99E7 /* DMTests.swift */; };
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883A52975F83C00DC99E7 /* LNUrlPayRequest.swift */; }; 4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883A52975F83C00DC99E7 /* LNUrlPayRequest.swift */; };
@ -404,6 +401,8 @@
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; }; 5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; };
5C6E1DAF2A194075008FC15A /* PinkGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAE2A194075008FC15A /* PinkGradient.swift */; }; 5C6E1DAF2A194075008FC15A /* PinkGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAE2A194075008FC15A /* PinkGradient.swift */; };
5C7389B12B6EFA7100781E0A /* ProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B02B6EFA7100781E0A /* ProxyView.swift */; }; 5C7389B12B6EFA7100781E0A /* ProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B02B6EFA7100781E0A /* ProxyView.swift */; };
5C7389B72B9E692E00781E0A /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; };
5C7389B92B9E69ED00781E0A /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; };
5CC868DD2AA29B3200FB22BA /* NeutralButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */; }; 5CC868DD2AA29B3200FB22BA /* NeutralButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */; };
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */; }; 5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */; };
5CF2DCCE2AABE1A500984B8D /* DamusLightGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */; }; 5CF2DCCE2AABE1A500984B8D /* DamusLightGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */; };
@ -769,7 +768,6 @@
3A96D41A298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 3A96D41A298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3A96D41B298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; }; 3A96D41B298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
3A96D41C298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 3A96D41C298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepostsModel.swift; sourceTree = "<group>"; };
3AA247FE297E3D900090C62D /* RepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostsView.swift; sourceTree = "<group>"; }; 3AA247FE297E3D900090C62D /* RepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostsView.swift; sourceTree = "<group>"; };
3AA24801297E3DC20090C62D /* RepostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostView.swift; sourceTree = "<group>"; }; 3AA24801297E3DC20090C62D /* RepostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostView.swift; sourceTree = "<group>"; };
3AA59D1C2999B0400061C48E /* DraftsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsModel.swift; sourceTree = "<group>"; }; 3AA59D1C2999B0400061C48E /* DraftsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsModel.swift; sourceTree = "<group>"; };
@ -843,7 +841,6 @@
4C1253652A76D0FF0004F4B8 /* OnlyZapsNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlyZapsNotify.swift; sourceTree = "<group>"; }; 4C1253652A76D0FF0004F4B8 /* OnlyZapsNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlyZapsNotify.swift; sourceTree = "<group>"; };
4C1253672A76D2470004F4B8 /* MuteNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteNotify.swift; sourceTree = "<group>"; }; 4C1253672A76D2470004F4B8 /* MuteNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MuteNotify.swift; sourceTree = "<group>"; };
4C1253692A76D3850004F4B8 /* RelaysChangedNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaysChangedNotify.swift; sourceTree = "<group>"; }; 4C1253692A76D3850004F4B8 /* RelaysChangedNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaysChangedNotify.swift; sourceTree = "<group>"; };
4C12536B2A76D4B00004F4B8 /* RepostedNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostedNotify.swift; sourceTree = "<group>"; };
4C15C7142A55DE7A00D0A0DB /* ReactionsSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsSettingsView.swift; sourceTree = "<group>"; }; 4C15C7142A55DE7A00D0A0DB /* ReactionsSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsSettingsView.swift; sourceTree = "<group>"; };
4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = "<group>"; }; 4C190F1F2A535FC200027FD5 /* CustomizeZapModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = "<group>"; };
4C190F242A547D2000027FD5 /* LoadScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadScript.swift; sourceTree = "<group>"; }; 4C190F242A547D2000027FD5 /* LoadScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadScript.swift; sourceTree = "<group>"; };
@ -1165,6 +1162,7 @@
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; }; 4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; }; 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
4C9146FF2A2A891E00DDEA40 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = error.c; sourceTree = "<group>"; }; 4C9146FF2A2A891E00DDEA40 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = error.c; sourceTree = "<group>"; };
4C94D6422BA5AEFE00C26EFF /* QuoteRepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteRepostsView.swift; sourceTree = "<group>"; };
4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = "<group>"; }; 4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = "<group>"; };
4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusModel.swift; sourceTree = "<group>"; }; 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusModel.swift; sourceTree = "<group>"; };
4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttrStringTestExtensions.swift; sourceTree = "<group>"; }; 4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttrStringTestExtensions.swift; sourceTree = "<group>"; };
@ -1205,14 +1203,12 @@
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; }; 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; };
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; }; 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; }; 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedRelayView.swift; sourceTree = "<group>"; };
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRelaysView.swift; sourceTree = "<group>"; }; 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRelaysView.swift; sourceTree = "<group>"; };
4CB8838529656C8B00DC99E7 /* NIP05.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05.swift; sourceTree = "<group>"; }; 4CB8838529656C8B00DC99E7 /* NIP05.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05.swift; sourceTree = "<group>"; };
4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailBar.swift; sourceTree = "<group>"; }; 4CB88388296AF99A00DC99E7 /* EventDetailBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailBar.swift; sourceTree = "<group>"; };
4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05Badge.swift; sourceTree = "<group>"; }; 4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05Badge.swift; sourceTree = "<group>"; };
4CB8838C296F710400DC99E7 /* Reposted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reposted.swift; sourceTree = "<group>"; }; 4CB8838C296F710400DC99E7 /* Reposted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reposted.swift; sourceTree = "<group>"; };
4CB8838E296F781C00DC99E7 /* ReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsView.swift; sourceTree = "<group>"; }; 4CB8838E296F781C00DC99E7 /* ReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsView.swift; sourceTree = "<group>"; };
4CB88392296F798300DC99E7 /* ReactionsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsModel.swift; sourceTree = "<group>"; };
4CB88395296F7F8B00DC99E7 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = "<group>"; }; 4CB88395296F7F8B00DC99E7 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = "<group>"; };
4CB88399297322D200DC99E7 /* DMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMTests.swift; sourceTree = "<group>"; }; 4CB88399297322D200DC99E7 /* DMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMTests.swift; sourceTree = "<group>"; };
4CB883A52975F83C00DC99E7 /* LNUrlPayRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LNUrlPayRequest.swift; sourceTree = "<group>"; }; 4CB883A52975F83C00DC99E7 /* LNUrlPayRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LNUrlPayRequest.swift; sourceTree = "<group>"; };
@ -1326,6 +1322,8 @@
5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButtonStyle.swift; sourceTree = "<group>"; }; 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButtonStyle.swift; sourceTree = "<group>"; };
5C6E1DAE2A194075008FC15A /* PinkGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinkGradient.swift; sourceTree = "<group>"; }; 5C6E1DAE2A194075008FC15A /* PinkGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinkGradient.swift; sourceTree = "<group>"; };
5C7389B02B6EFA7100781E0A /* ProxyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyView.swift; sourceTree = "<group>"; }; 5C7389B02B6EFA7100781E0A /* ProxyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyView.swift; sourceTree = "<group>"; };
5C7389B62B9E692E00781E0A /* MutinyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyButton.swift; sourceTree = "<group>"; };
5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyGradient.swift; sourceTree = "<group>"; };
5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeutralButtonStyle.swift; sourceTree = "<group>"; }; 5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeutralButtonStyle.swift; sourceTree = "<group>"; };
5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPicView.swift; sourceTree = "<group>"; }; 5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPicView.swift; sourceTree = "<group>"; };
5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLightGradient.swift; sourceTree = "<group>"; }; 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLightGradient.swift; sourceTree = "<group>"; };
@ -1510,6 +1508,7 @@
children = ( children = (
3AA24801297E3DC20090C62D /* RepostView.swift */, 3AA24801297E3DC20090C62D /* RepostView.swift */,
4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */, 4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */,
4C94D6422BA5AEFE00C26EFF /* QuoteRepostsView.swift */,
); );
path = Reposts; path = Reposts;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1588,7 +1587,6 @@
BA3759882ABCCDE30018D73B /* Camera */, BA3759882ABCCDE30018D73B /* Camera */,
4C190F1E2A535FC200027FD5 /* Zaps */, 4C190F1E2A535FC200027FD5 /* Zaps */,
4C54AA0829A55416003E4487 /* Notifications */, 4C54AA0829A55416003E4487 /* Notifications */,
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */,
4C0A3F8E280F640A000448DE /* ThreadModel.swift */, 4C0A3F8E280F640A000448DE /* ThreadModel.swift */,
4C0A3F92280F66F5000448DE /* ReplyMap.swift */, 4C0A3F92280F66F5000448DE /* ReplyMap.swift */,
4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */, 4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */,
@ -1618,7 +1616,6 @@
4C216F372871EDE300040376 /* DirectMessageModel.swift */, 4C216F372871EDE300040376 /* DirectMessageModel.swift */,
BA693073295D649800ADDB87 /* UserSettingsStore.swift */, BA693073295D649800ADDB87 /* UserSettingsStore.swift */,
4FE60CDC295E1C5E00105A1F /* Wallet.swift */, 4FE60CDC295E1C5E00105A1F /* Wallet.swift */,
4CB88392296F798300DC99E7 /* ReactionsModel.swift */,
4CF0ABD32980996B00D66079 /* Report.swift */, 4CF0ABD32980996B00D66079 /* Report.swift */,
3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */, 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */,
3AAA95C9298DF87B00F3D526 /* TranslationService.swift */, 3AAA95C9298DF87B00F3D526 /* TranslationService.swift */,
@ -2112,6 +2109,7 @@
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */, 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */,
4C687C202A5F7ED00092C550 /* DamusBackground.swift */, 4C687C202A5F7ED00092C550 /* DamusBackground.swift */,
5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */, 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */,
5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */,
); );
path = Gradients; path = Gradients;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2182,6 +2180,7 @@
4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */, 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */,
F71694F32A6732B7001F4053 /* GradientFollowButton.swift */, F71694F32A6732B7001F4053 /* GradientFollowButton.swift */,
4C7D09652A0AE62100943473 /* AlbyButton.swift */, 4C7D09652A0AE62100943473 /* AlbyButton.swift */,
5C7389B62B9E692E00781E0A /* MutinyButton.swift */,
); );
path = Buttons; path = Buttons;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2258,7 +2257,6 @@
4C86F7C32A76C44C00EC0817 /* ZappingNotify.swift */, 4C86F7C32A76C44C00EC0817 /* ZappingNotify.swift */,
4C1253672A76D2470004F4B8 /* MuteNotify.swift */, 4C1253672A76D2470004F4B8 /* MuteNotify.swift */,
4C1253692A76D3850004F4B8 /* RelaysChangedNotify.swift */, 4C1253692A76D3850004F4B8 /* RelaysChangedNotify.swift */,
4C12536B2A76D4B00004F4B8 /* RepostedNotify.swift */,
4C4E137A2A76D5FB00BDD832 /* MuteThreadNotify.swift */, 4C4E137A2A76D5FB00BDD832 /* MuteThreadNotify.swift */,
4C4E137C2A76D63600BDD832 /* UnmuteThreadNotify.swift */, 4C4E137C2A76D63600BDD832 /* UnmuteThreadNotify.swift */,
B57B4C612B312BD700A232C0 /* ReconnectRelaysNotify.swift */, B57B4C612B312BD700A232C0 /* ReconnectRelaysNotify.swift */,
@ -2294,7 +2292,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4CE879532996BA0000F758CC /* Detail */, 4CE879532996BA0000F758CC /* Detail */,
4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */,
4C06670028FC7C5900038D2A /* RelayView.swift */, 4C06670028FC7C5900038D2A /* RelayView.swift */,
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */, 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */,
F7908E91298B0F0700AB113A /* RelayDetailView.swift */, F7908E91298B0F0700AB113A /* RelayDetailView.swift */,
@ -2741,14 +2738,6 @@
path = DamusNotificationService; path = DamusNotificationService;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E06336A72B7582D600A88E6B /* Assets */ = {
isa = PBXGroup;
children = (
E06336A82B7582E000A88E6B /* img_with_location.jpeg */,
);
path = Assets;
sourceTree = "<group>";
};
D7CBD1D22B8D21C100BFD889 /* Extensions */ = { D7CBD1D22B8D21C100BFD889 /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2757,6 +2746,14 @@
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E06336A72B7582D600A88E6B /* Assets */ = {
isa = PBXGroup;
children = (
E06336A82B7582E000A88E6B /* img_with_location.jpeg */,
);
path = Assets;
sourceTree = "<group>";
};
F71694E82A66221E001F4053 /* Onboarding */ = { F71694E82A66221E001F4053 /* Onboarding */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -3052,6 +3049,7 @@
4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */, 4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */,
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */, 4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
4CDD1AE22A6B3074001CD4DF /* NdbTagsIterator.swift in Sources */, 4CDD1AE22A6B3074001CD4DF /* NdbTagsIterator.swift in Sources */,
5C7389B72B9E692E00781E0A /* MutinyButton.swift in Sources */,
4C216F34286F5ACD00040376 /* DMView.swift in Sources */, 4C216F34286F5ACD00040376 /* DMView.swift in Sources */,
D7CB5D512B1174D100AD4105 /* FriendFilter.swift in Sources */, D7CB5D512B1174D100AD4105 /* FriendFilter.swift in Sources */,
D7CBD1D42B8D21DC00BFD889 /* DamusPurpleNotificationManagement.swift in Sources */, D7CBD1D42B8D21DC00BFD889 /* DamusPurpleNotificationManagement.swift in Sources */,
@ -3109,7 +3107,6 @@
4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */, 4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */,
4C7D09742A0AEF9000943473 /* AlbyGradient.swift in Sources */, 4C7D09742A0AEF9000943473 /* AlbyGradient.swift in Sources */,
4C687C272A6039500092C550 /* TestData.swift in Sources */, 4C687C272A6039500092C550 /* TestData.swift in Sources */,
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
50C3E08A2AA8E3F7006A4BC0 /* AVPlayer+Additions.swift in Sources */, 50C3E08A2AA8E3F7006A4BC0 /* AVPlayer+Additions.swift in Sources */,
4C198DF229F88C6B004C165C /* BlurHashDecode.swift in Sources */, 4C198DF229F88C6B004C165C /* BlurHashDecode.swift in Sources */,
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */, F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */,
@ -3254,6 +3251,7 @@
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */, 4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */, 4C06670128FC7C5900038D2A /* RelayView.swift in Sources */,
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */, 4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
4C94D6432BA5AEFE00C26EFF /* QuoteRepostsView.swift in Sources */,
D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */, D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */,
4CA352AE2A76C1AC003BB08B /* FollowedNotify.swift in Sources */, 4CA352AE2A76C1AC003BB08B /* FollowedNotify.swift in Sources */,
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */, 4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
@ -3325,7 +3323,6 @@
4C32B95E2A9AD44700DC3548 /* FlatBufferObject.swift in Sources */, 4C32B95E2A9AD44700DC3548 /* FlatBufferObject.swift in Sources */,
D783A63F2AD4E53D00658DDA /* SuggestedHashtagsView.swift in Sources */, D783A63F2AD4E53D00658DDA /* SuggestedHashtagsView.swift in Sources */,
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */, 4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */,
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */, 5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */,
4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */, 4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */,
50A16FFD2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift in Sources */, 50A16FFD2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift in Sources */,
@ -3344,6 +3341,7 @@
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */, 4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
F71694EA2A662232001F4053 /* OnboardingSuggestionsView.swift in Sources */, F71694EA2A662232001F4053 /* OnboardingSuggestionsView.swift in Sources */,
4C12536A2A76D3850004F4B8 /* RelaysChangedNotify.swift in Sources */, 4C12536A2A76D3850004F4B8 /* RelaysChangedNotify.swift in Sources */,
5C7389B92B9E69ED00781E0A /* MutinyGradient.swift in Sources */,
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */, 4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
D7373BAA2B68A65A00F7783D /* PurpleAccountUpdateNotify.swift in Sources */, D7373BAA2B68A65A00F7783D /* PurpleAccountUpdateNotify.swift in Sources */,
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */, 5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
@ -3416,8 +3414,6 @@
4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */, 4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */,
7527271E2A93FF0100214108 /* Block.swift in Sources */, 7527271E2A93FF0100214108 /* Block.swift in Sources */,
4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */, 4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */,
4C12536C2A76D4B00004F4B8 /* RepostedNotify.swift in Sources */,
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */, 4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */,
4C32B9592A9AD44700DC3548 /* Table.swift in Sources */, 4C32B9592A9AD44700DC3548 /* Table.swift in Sources */,
4C5D5C9D2A6B2CB40024563C /* AsciiCharacter.swift in Sources */, 4C5D5C9D2A6B2CB40024563C /* AsciiCharacter.swift in Sources */,

View File

@ -0,0 +1,20 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "mutiny.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,15 @@
//
// MutinyGradient.swift
// damus
//
// Created by eric on 3/9/24.
//
import SwiftUI
fileprivate let mutiny_grad_c1 = hex_col(r: 39, g: 95, b: 161)
fileprivate let mutiny_grad_c2 = hex_col(r: 13, g: 33, b: 56)
fileprivate let mutiny_grad = [mutiny_grad_c2, mutiny_grad_c1]
let MutinyGradient: LinearGradient =
LinearGradient(colors: mutiny_grad, startPoint: .top, endPoint: .bottom)

View File

@ -723,7 +723,8 @@ struct ContentView: View {
nav: self.navigationCoordinator, nav: self.navigationCoordinator,
music: MusicController(onChange: music_changed), music: MusicController(onChange: music_changed),
video: VideoController(), video: VideoController(),
ndb: ndb ndb: ndb,
quote_reposts: .init(our_pubkey: pubkey)
) )
home.damus_state = self.damus_state! home.damus_state = self.damus_state!

View File

@ -16,10 +16,12 @@ enum Zapped {
class ActionBarModel: ObservableObject { class ActionBarModel: ObservableObject {
@Published var our_like: NostrEvent? @Published var our_like: NostrEvent?
@Published var our_boost: NostrEvent? @Published var our_boost: NostrEvent?
@Published var our_quote_repost: NostrEvent?
@Published var our_reply: NostrEvent? @Published var our_reply: NostrEvent?
@Published var our_zap: Zapping? @Published var our_zap: Zapping?
@Published var likes: Int @Published var likes: Int
@Published var boosts: Int @Published var boosts: Int
@Published var quote_reposts: Int
@Published private(set) var zaps: Int @Published private(set) var zaps: Int
@Published var zap_total: Int64 @Published var zap_total: Int64
@Published var replies: Int @Published var replies: Int
@ -28,7 +30,7 @@ class ActionBarModel: ObservableObject {
return ActionBarModel(likes: 0, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: nil, our_boost: nil, our_zap: nil, our_reply: nil) return ActionBarModel(likes: 0, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: nil, our_boost: nil, our_zap: nil, our_reply: nil)
} }
init(likes: Int = 0, boosts: Int = 0, zaps: Int = 0, zap_total: Int64 = 0, replies: Int = 0, our_like: NostrEvent? = nil, our_boost: NostrEvent? = nil, our_zap: Zapping? = nil, our_reply: NostrEvent? = nil) { init(likes: Int = 0, boosts: Int = 0, zaps: Int = 0, zap_total: Int64 = 0, replies: Int = 0, our_like: NostrEvent? = nil, our_boost: NostrEvent? = nil, our_zap: Zapping? = nil, our_reply: NostrEvent? = nil, our_quote_repost: NostrEvent? = nil, quote_reposts: Int = 0) {
self.likes = likes self.likes = likes
self.boosts = boosts self.boosts = boosts
self.zaps = zaps self.zaps = zaps
@ -38,6 +40,8 @@ class ActionBarModel: ObservableObject {
self.our_boost = our_boost self.our_boost = our_boost
self.our_zap = our_zap self.our_zap = our_zap
self.our_reply = our_reply self.our_reply = our_reply
self.our_quote_repost = our_quote_repost
self.quote_reposts = quote_reposts
} }
func update(damus: DamusState, evid: NoteId) { func update(damus: DamusState, evid: NoteId) {
@ -45,11 +49,13 @@ class ActionBarModel: ObservableObject {
self.boosts = damus.boosts.counts[evid] ?? 0 self.boosts = damus.boosts.counts[evid] ?? 0
self.zaps = damus.zaps.event_counts[evid] ?? 0 self.zaps = damus.zaps.event_counts[evid] ?? 0
self.replies = damus.replies.get_replies(evid) self.replies = damus.replies.get_replies(evid)
self.quote_reposts = damus.quote_reposts.counts[evid] ?? 0
self.zap_total = damus.zaps.event_totals[evid] ?? 0 self.zap_total = damus.zaps.event_totals[evid] ?? 0
self.our_like = damus.likes.our_events[evid] self.our_like = damus.likes.our_events[evid]
self.our_boost = damus.boosts.our_events[evid] self.our_boost = damus.boosts.our_events[evid]
self.our_zap = damus.zaps.our_zaps[evid]?.first self.our_zap = damus.zaps.our_zaps[evid]?.first
self.our_reply = damus.replies.our_reply(evid) self.our_reply = damus.replies.our_reply(evid)
self.our_quote_repost = damus.quote_reposts.our_events[evid]
self.objectWillChange.send() self.objectWillChange.send()
} }
@ -68,4 +74,8 @@ class ActionBarModel: ObservableObject {
var boosted: Bool { var boosted: Bool {
return our_boost != nil return our_boost != nil
} }
var quoted: Bool {
return our_quote_repost != nil
}
} }

View File

@ -53,6 +53,10 @@ struct ContentFilters {
} }
extension ContentFilters { extension ContentFilters {
static func default_filters(damus_state: DamusState) -> ContentFilters {
return ContentFilters(filters: ContentFilters.defaults(damus_state: damus_state))
}
static func defaults(damus_state: DamusState) -> [(NostrEvent) -> Bool] { static func defaults(damus_state: DamusState) -> [(NostrEvent) -> Bool] {
var filters = Array<(NostrEvent) -> Bool>() var filters = Array<(NostrEvent) -> Bool>()
if damus_state.settings.hide_nsfw_tagged_content { if damus_state.settings.hide_nsfw_tagged_content {

View File

@ -13,6 +13,7 @@ class DamusState: HeadlessDamusState {
let keypair: Keypair let keypair: Keypair
let likes: EventCounter let likes: EventCounter
let boosts: EventCounter let boosts: EventCounter
let quote_reposts: EventCounter
let contacts: Contacts let contacts: Contacts
let mutelist_manager: MutelistManager let mutelist_manager: MutelistManager
let profiles: Profiles let profiles: Profiles
@ -36,7 +37,7 @@ class DamusState: HeadlessDamusState {
let ndb: Ndb let ndb: Ndb
var purple: DamusPurple var purple: DamusPurple
init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [String], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil) { init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [String], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) {
self.pool = pool self.pool = pool
self.keypair = keypair self.keypair = keypair
self.likes = likes self.likes = likes
@ -66,6 +67,7 @@ class DamusState: HeadlessDamusState {
settings: settings, settings: settings,
keypair: keypair keypair: keypair
) )
self.quote_reposts = quote_reposts
} }
@discardableResult @discardableResult
@ -129,7 +131,8 @@ class DamusState: HeadlessDamusState {
nav: NavigationCoordinator(), nav: NavigationCoordinator(),
music: nil, music: nil,
video: VideoController(), video: VideoController(),
ndb: .empty ndb: .empty,
quote_reposts: .init(our_pubkey: empty_pub)
) )
} }
} }

View File

@ -7,25 +7,62 @@
import Foundation import Foundation
class EventsModel: ObservableObject { class EventsModel: ObservableObject {
let state: DamusState let state: DamusState
let target: NoteId let target: NoteId
let kind: NostrKind let kind: QueryKind
let sub_id = UUID().uuidString let sub_id = UUID().uuidString
let profiles_id = UUID().uuidString let profiles_id = UUID().uuidString
var events: EventHolder
@Published var events: [NostrEvent] = [] @Published var loading: Bool
enum QueryKind {
case kind(NostrKind)
case quotes
}
init(state: DamusState, target: NoteId, kind: NostrKind) { init(state: DamusState, target: NoteId, kind: NostrKind) {
self.state = state self.state = state
self.target = target self.target = target
self.kind = kind self.kind = .kind(kind)
self.loading = true
self.events = EventHolder(on_queue: { ev in
preload_events(state: state, events: [ev])
})
} }
init(state: DamusState, target: NoteId, query: EventsModel.QueryKind) {
self.state = state
self.target = target
self.kind = query
self.loading = true
self.events = EventHolder(on_queue: { ev in
preload_events(state: state, events: [ev])
})
}
public static func quotes(state: DamusState, target: NoteId) -> EventsModel {
EventsModel(state: state, target: target, query: .quotes)
}
public static func reposts(state: DamusState, target: NoteId) -> EventsModel {
EventsModel(state: state, target: target, kind: .boost)
}
public static func likes(state: DamusState, target: NoteId) -> EventsModel {
EventsModel(state: state, target: target, kind: .like)
}
private func get_filter() -> NostrFilter { private func get_filter() -> NostrFilter {
var filter = NostrFilter(kinds: [kind]) var filter: NostrFilter
filter.referenced_ids = [target] switch kind {
case .kind(let k):
filter = NostrFilter(kinds: [k])
filter.referenced_ids = [target]
case .quotes:
filter = NostrFilter(kinds: [.text])
filter.quotes = [target]
}
filter.limit = 500 filter.limit = 500
return filter return filter
} }
@ -41,21 +78,17 @@ class EventsModel: ObservableObject {
} }
private func handle_event(relay_id: String, ev: NostrEvent) { private func handle_event(relay_id: String, ev: NostrEvent) {
guard ev.kind == kind.rawValue, if events.insert(ev) {
ev.referenced_ids.last == target else {
return
}
if insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { a, b in a.created_at < b.created_at } ) {
objectWillChange.send() objectWillChange.send()
} }
} }
func handle_nostr_event(relay_id: String, ev: NostrConnectionEvent) { func handle_nostr_event(relay_id: String, ev: NostrConnectionEvent) {
guard case .nostr_event(let nev) = ev else { guard case .nostr_event(let nev) = ev, nev.subid == self.sub_id
else {
return return
} }
switch nev { switch nev {
case .event(_, let ev): case .event(_, let ev):
handle_event(relay_id: relay_id, ev: ev) handle_event(relay_id: relay_id, ev: ev)
@ -66,10 +99,11 @@ class EventsModel: ObservableObject {
case .auth: case .auth:
break break
case .eose: case .eose:
self.loading = false
guard let txn = NdbTxn(ndb: self.state.ndb) else { guard let txn = NdbTxn(ndb: self.state.ndb) else {
return return
} }
load_profiles(context: "events_model", profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state, txn: txn) load_profiles(context: "events_model", profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events.all_events), damus_state: state, txn: txn)
} }
} }
} }

View File

@ -347,12 +347,19 @@ class HomeModel {
case .already_counted: case .already_counted:
break break
case .success(let n): case .success(let n):
let boosted = Counted(event: ev, id: e, total: n)
notify(.reposted(boosted))
notify(.update_stats(note_id: e)) notify(.update_stats(note_id: e))
} }
} }
func handle_quote_repost_event(_ ev: NostrEvent, target: NoteId) {
switch damus_state.quote_reposts.add_event(ev, target: target) {
case .already_counted:
break
case .success(let n):
notify(.update_stats(note_id: target))
}
}
func handle_like_event(_ ev: NostrEvent) { func handle_like_event(_ ev: NostrEvent) {
guard let e = ev.last_refid() else { guard let e = ev.last_refid() else {
// no id ref? invalid like event // no id ref? invalid like event
@ -672,6 +679,10 @@ class HomeModel {
damus_state.replies.count_replies(ev, keypair: self.damus_state.keypair) damus_state.replies.count_replies(ev, keypair: self.damus_state.keypair)
damus_state.events.insert(ev) damus_state.events.insert(ev)
if let quoted_event = ev.referenced_quote_ids.first {
handle_quote_repost_event(ev, target: quoted_event.note_id)
}
if sub_id == home_subid { if sub_id == home_subid {
insert_home_event(ev) insert_home_event(ev)
} else if sub_id == notifications_subid { } else if sub_id == notifications_subid {

View File

@ -1,16 +0,0 @@
//
// LikesModel.swift
// damus
//
// Created by William Casarin on 2023-01-11.
//
import Foundation
final class ReactionsModel: EventsModel {
init(state: DamusState, target: NoteId) {
super.init(state: state, target: target, kind: .like)
}
}

View File

@ -1,15 +0,0 @@
//
// RepostsModel.swift
// damus
//
// Created by Terry Yiu on 1/22/23.
//
import Foundation
final class RepostsModel: EventsModel {
init(state: DamusState, target: NoteId) {
super.init(state: state, target: target, kind: .boost)
}
}

View File

@ -56,6 +56,7 @@ class ThreadModel: ObservableObject {
func subscribe() { func subscribe() {
var meta_events = NostrFilter() var meta_events = NostrFilter()
var quote_events = NostrFilter()
var event_filter = NostrFilter() var event_filter = NostrFilter()
var ref_events = NostrFilter() var ref_events = NostrFilter()
@ -74,11 +75,14 @@ class ThreadModel: ObservableObject {
kinds.append(.like) kinds.append(.like)
} }
meta_events.kinds = kinds meta_events.kinds = kinds
meta_events.limit = 1000 meta_events.limit = 1000
quote_events.kinds = [.text]
quote_events.quotes = [event.id]
quote_events.limit = 1000
let base_filters = [event_filter, ref_events] let base_filters = [event_filter, ref_events]
let meta_filters = [meta_events] let meta_filters = [meta_events, quote_events]
print("subscribing to thread \(event.id) with sub_id \(base_subid)") print("subscribing to thread \(event.id) with sub_id \(base_subid)")
damus_state.pool.subscribe(sub_id: base_subid, filters: base_filters, handler: handle_event) damus_state.pool.subscribe(sub_id: base_subid, filters: base_filters, handler: handle_event)
@ -90,7 +94,7 @@ class ThreadModel: ObservableObject {
return return
} }
let the_ev = damus_state.events.upsert(ev) damus_state.events.upsert(ev)
damus_state.replies.count_replies(ev, keypair: keypair) damus_state.replies.count_replies(ev, keypair: keypair)
damus_state.events.add_replies(ev: ev, keypair: keypair) damus_state.events.add_replies(ev: ev, keypair: keypair)
@ -111,7 +115,13 @@ class ThreadModel: ObservableObject {
} }
} else if ev.is_textlike { } else if ev.is_textlike {
self.add_event(ev, keypair: damus_state.keypair) // handle thread quote reposts, we just count them instead of
// adding them to the thread
if let target = ev.is_quote_repost, target == self.event.id {
//let _ = self.damus_state.quote_reposts.add_event(ev, target: target)
} else {
self.add_event(ev, keypair: damus_state.keypair)
}
} }
} }

View File

@ -41,7 +41,7 @@ struct QuoteId: IdType, TagKey, TagConvertible {
self.id = data self.id = data
} }
/// Refer to this QuoteId as a NoteId /// The note id being quoted
var note_id: NoteId { var note_id: NoteId {
NoteId(self.id) NoteId(self.id)
} }

View File

@ -18,6 +18,7 @@ struct NostrFilter: Codable, Equatable {
var authors: [Pubkey]? var authors: [Pubkey]?
var hashtag: [String]? var hashtag: [String]?
var parameter: [String]? var parameter: [String]?
var quotes: [NoteId]?
private enum CodingKeys : String, CodingKey { private enum CodingKeys : String, CodingKey {
case ids case ids
@ -26,13 +27,14 @@ struct NostrFilter: Codable, Equatable {
case pubkeys = "#p" case pubkeys = "#p"
case hashtag = "#t" case hashtag = "#t"
case parameter = "#d" case parameter = "#d"
case quotes = "#q"
case since case since
case until case until
case authors case authors
case limit case limit
} }
init(ids: [NoteId]? = nil, kinds: [NostrKind]? = nil, referenced_ids: [NoteId]? = nil, pubkeys: [Pubkey]? = nil, since: UInt32? = nil, until: UInt32? = nil, limit: UInt32? = nil, authors: [Pubkey]? = nil, hashtag: [String]? = nil) { init(ids: [NoteId]? = nil, kinds: [NostrKind]? = nil, referenced_ids: [NoteId]? = nil, pubkeys: [Pubkey]? = nil, since: UInt32? = nil, until: UInt32? = nil, limit: UInt32? = nil, authors: [Pubkey]? = nil, hashtag: [String]? = nil, quotes: [NoteId]? = nil) {
self.ids = ids self.ids = ids
self.kinds = kinds self.kinds = kinds
self.referenced_ids = referenced_ids self.referenced_ids = referenced_ids
@ -42,6 +44,7 @@ struct NostrFilter: Codable, Equatable {
self.limit = limit self.limit = limit
self.authors = authors self.authors = authors
self.hashtag = hashtag self.hashtag = hashtag
self.quotes = quotes
} }
public static func copy(from: NostrFilter) -> NostrFilter { public static func copy(from: NostrFilter) -> NostrFilter {

View File

@ -1,26 +0,0 @@
//
// BoostedNotify.swift
// damus
//
// Created by William Casarin on 2023-07-30.
//
import Foundation
struct RepostedNotify: Notify {
typealias Payload = Counted
var payload: Payload
}
extension NotifyHandler {
static var reposted: NotifyHandler<RepostedNotify> {
.init()
}
}
extension Notifications {
static func reposted(_ counts: Counted) -> Notifications<RepostedNotify> {
.init(.init(payload: counts))
}
}

View File

@ -92,7 +92,9 @@ var test_damus_state: DamusState = ({
nav: .init(), nav: .init(),
music: .init(onChange: {_ in }), music: .init(onChange: {_ in }),
video: .init(), video: .init(),
ndb: ndb) ndb: ndb,
quote_reposts: .init(our_pubkey: our_pubkey)
)
/* /*
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io", damus_donation: nil) let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io", damus_donation: nil)

View File

@ -31,8 +31,9 @@ enum Route: Hashable {
case SearchSettings(settings: UserSettingsStore) case SearchSettings(settings: UserSettingsStore)
case DeveloperSettings(settings: UserSettingsStore) case DeveloperSettings(settings: UserSettingsStore)
case Thread(thread: ThreadModel) case Thread(thread: ThreadModel)
case Reposts(reposts: RepostsModel) case Reposts(reposts: EventsModel)
case Reactions(reactions: ReactionsModel) case QuoteReposts(quotes: EventsModel)
case Reactions(reactions: EventsModel)
case Zaps(target: ZapTarget) case Zaps(target: ZapTarget)
case Search(search: SearchModel) case Search(search: SearchModel)
case EULA case EULA
@ -53,7 +54,7 @@ enum Route: Hashable {
case .Followers(let followers): case .Followers(let followers):
FollowersView(damus_state: damusState, followers: followers) FollowersView(damus_state: damusState, followers: followers)
case .Relay(let relay, let showActionButtons): case .Relay(let relay, let showActionButtons):
RelayView(state: damusState, relay: relay, showActionButtons: showActionButtons) RelayView(state: damusState, relay: relay, showActionButtons: showActionButtons, recommended: false)
case .RelayDetail(let relay, let metadata): case .RelayDetail(let relay, let metadata):
RelayDetailView(state: damusState, relay: relay, nip11: metadata) RelayDetailView(state: damusState, relay: relay, nip11: metadata)
case .Following(let following): case .Following(let following):
@ -92,6 +93,8 @@ enum Route: Hashable {
ThreadView(state: damusState, thread: thread) ThreadView(state: damusState, thread: thread)
case .Reposts(let reposts): case .Reposts(let reposts):
RepostsView(damus_state: damusState, model: reposts) RepostsView(damus_state: damusState, model: reposts)
case .QuoteReposts(let quote_reposts):
QuoteRepostsView(damus_state: damusState, model: quote_reposts)
case .Reactions(let reactions): case .Reactions(let reactions):
ReactionsView(damus_state: damusState, model: reactions) ReactionsView(damus_state: damusState, model: reactions)
case .Zaps(let target): case .Zaps(let target):
@ -178,6 +181,9 @@ enum Route: Hashable {
case .Reposts(let reposts): case .Reposts(let reposts):
hasher.combine("reposts") hasher.combine("reposts")
hasher.combine(reposts.target) hasher.combine(reposts.target)
case .QuoteReposts(let evs_model):
hasher.combine("quote_reposts")
hasher.combine(evs_model.events.events.count)
case .Zaps(let target): case .Zaps(let target):
hasher.combine("zaps") hasher.combine("zaps")
hasher.combine(target.id) hasher.combine(target.id)

View File

@ -25,7 +25,7 @@ struct EventDetailBar: View {
var body: some View { var body: some View {
HStack { HStack {
if bar.boosts > 0 { if bar.boosts > 0 {
NavigationLink(value: Route.Reposts(reposts: RepostsModel(state: state, target: target))) { NavigationLink(value: Route.Reposts(reposts: .reposts(state: state, target: target))) {
let nounString = pluralizedString(key: "reposts_count", count: bar.boosts) let nounString = pluralizedString(key: "reposts_count", count: bar.boosts)
let noun = Text(nounString).foregroundColor(.gray) let noun = Text(nounString).foregroundColor(.gray)
Text("\(Text(verbatim: bar.boosts.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.") Text("\(Text(verbatim: bar.boosts.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
@ -33,8 +33,17 @@ struct EventDetailBar: View {
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
} }
if bar.quote_reposts > 0 {
NavigationLink(value: Route.QuoteReposts(quotes: .quotes(state: state, target: target))) {
let nounString = pluralizedString(key: "quoted_reposts_count", count: bar.quote_reposts)
let noun = Text(nounString).foregroundColor(.gray)
Text("\(Text(verbatim: bar.quote_reposts.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many quoted reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
}
.buttonStyle(PlainButtonStyle())
}
if bar.likes > 0 && !state.settings.onlyzaps_mode { if bar.likes > 0 && !state.settings.onlyzaps_mode {
NavigationLink(value: Route.Reactions(reactions: ReactionsModel(state: state, target: target))) { NavigationLink(value: Route.Reactions(reactions: .likes(state: state, target: target))) {
let nounString = pluralizedString(key: "reactions_count", count: bar.likes) let nounString = pluralizedString(key: "reactions_count", count: bar.likes)
let noun = Text(nounString).foregroundColor(.gray) let noun = Text(nounString).foregroundColor(.gray)
Text("\(Text(verbatim: bar.likes.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.") Text("\(Text(verbatim: bar.likes.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")

View File

@ -23,16 +23,15 @@ struct AlbyButton: View {
HStack { HStack {
Image("alby") Image("alby")
Text("Attach Alby Wallet", comment: "Button to attach an Alby Wallet, a service that provides a Lightning wallet for zapping sats. Alby is the name of the service and should not be translated.") Text("Connect to Alby Wallet", comment: "Button to attach an Alby Wallet, a service that provides a Lightning wallet for zapping sats. Alby is the name of the service and should not be translated.")
.padding()
} }
.offset(x: -25) .frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
.frame(minWidth: 300, maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .center)
.foregroundColor(DamusColors.black) .foregroundColor(DamusColors.black)
.background { .background {
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: 12)
.fill(AlbyGradient, strokeBorder: colorScheme == .light ? DamusColors.black : DamusColors.white, lineWidth: 2) .fill(AlbyGradient, strokeBorder: colorScheme == .light ? DamusColors.black.opacity(0.2) : DamusColors.white, lineWidth: 1)
} }
.padding(EdgeInsets(top: 10, leading: 50, bottom: 25, trailing: 50))
} }
} }
} }

View File

@ -0,0 +1,47 @@
//
// MutinyButton.swift
// damus
//
// Created by eric on 3/9/24.
//
import SwiftUI
struct MutinyButton: View {
let action: () -> ()
@Environment(\.colorScheme) var colorScheme
init(action: @escaping () -> ()) {
self.action = action
}
var body: some View {
Button(action: {
action()
}) {
HStack {
Image("mutiny")
.resizable()
.frame(width: 45, height: 45)
Text("Connect to Mutiny Wallet", comment: "Button to attach an Mutiny Wallet, a service that provides a Lightning wallet for zapping sats. Mutiny is the name of the service and should not be translated.")
.padding()
}
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
.foregroundColor(DamusColors.white)
.background {
RoundedRectangle(cornerRadius: 12)
.fill(MutinyGradient, strokeBorder: colorScheme == .light ? DamusColors.black.opacity(0.2) : DamusColors.white.opacity(0.2), lineWidth: 1)
}
}
}
}
struct MutinyButton_Previews: PreviewProvider {
static var previews: some View {
MutinyButton(action: {
print("mutiny button")
})
}
}

View File

@ -52,7 +52,11 @@ struct SelectedEventView: View {
ReplyDescription(event: event, replying_to: replying_to, ndb: damus.ndb) ReplyDescription(event: event, replying_to: replying_to, ndb: damus.ndb)
.padding(.horizontal) .padding(.horizontal)
} }
ProxyView(event: event)
.padding(.top, 5)
.padding(.horizontal)
EventBody(damus_state: damus, event: event, size: size, options: [.wide]) EventBody(damus_state: damus, event: event, size: size, options: [.wide])
Mention Mention

View File

@ -9,14 +9,14 @@ import SwiftUI
struct ReactionsView: View { struct ReactionsView: View {
let damus_state: DamusState let damus_state: DamusState
@StateObject var model: ReactionsModel @StateObject var model: EventsModel
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
var body: some View { var body: some View {
ScrollView { ScrollView {
LazyVStack { LazyVStack {
ForEach(model.events, id: \.id) { ev in ForEach(model.events.events, id: \.id) { ev in
ReactionView(damus_state: damus_state, reaction: ev) ReactionView(damus_state: damus_state, reaction: ev)
} }
} }
@ -38,6 +38,6 @@ struct ReactionsView: View {
struct ReactionsView_Previews: PreviewProvider { struct ReactionsView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let state = test_damus_state let state = test_damus_state
ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: test_note.id)) ReactionsView(damus_state: state, model: .likes(state: state, target: test_note.id))
} }
} }

View File

@ -1,131 +0,0 @@
//
// RecommendedRelayView.swift
// damus
//
// Created by William Casarin on 2022-12-29.
//
import SwiftUI
struct RecommendedRelayView: View {
let damus: DamusState
let relay: String
let add_button: Bool
let user_recommended: Bool
@ObservedObject private var model_cache: RelayModelCache
init(damus: DamusState, relay: String, add_button: Bool = true, user_recommended: Bool = false) {
self.damus = damus
self.relay = relay
self.add_button = add_button
self.user_recommended = user_recommended
self.model_cache = damus.relay_model_cache
}
var body: some View {
let meta = model_cache.model(with_relay_id: relay)?.metadata
if user_recommended {
HStack {
RelayPicView(relay: relay, icon: meta?.icon, size: 50, highlight: .none, disable_animation: false)
.padding(.horizontal, 5)
VStack(alignment: .leading) {
HStack {
Text(meta?.name ?? relay)
.font(.headline)
.padding(.bottom, 2)
RelayType(is_paid: damus.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false)
}
Text(relay)
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
if let keypair = damus.keypair.to_full() {
VStack(alignment: .center) {
if damus.pool.get_relay(relay) == nil {
AddButton(keypair: keypair)
} else {
Image(systemName: "checkmark.circle")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(DamusColors.success)
.padding(.trailing, 10)
}
}
.padding(.horizontal, 5)
}
}
} else {
VStack {
RelayPicView(relay: relay, icon: meta?.icon, size: 70, highlight: .none, disable_animation: false)
if let meta = damus.relay_model_cache.model(with_relay_id: relay)?.metadata {
NavigationLink(value: Route.RelayDetail(relay: relay, metadata: meta)){
EmptyView()
}
.opacity(0.0)
}
HStack {
Text(meta?.name ?? relay)
.lineLimit(1)
.frame(maxWidth: 150)
.padding(.vertical, 5)
}
.contextMenu {
CopyAction(relay: relay)
}
if let keypair = damus.keypair.to_full() {
AddButton(keypair: keypair)
}
}
}
}
func CopyAction(relay: String) -> some View {
Button {
UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text")
} label: {
Label(NSLocalizedString("Copy", comment: "Button to copy a relay server address."), image: "copy")
}
}
func AddButton(keypair: FullKeypair) -> some View {
Button(action: {
add_action(keypair: keypair)
}) {
Text(NSLocalizedString("Add", comment: "Button to add relay server to list."))
.padding(10)
}
.buttonStyle(NeutralButtonStyle())
}
func add_action(keypair: FullKeypair) {
guard let ev_before_add = damus.contacts.event else {
return
}
guard let relay_url = RelayURL(relay),
let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: damus.pool.our_descriptors, relay: relay_url, info: .rw) else {
return
}
process_contact_event(state: damus, ev: ev_after_add)
damus.postbox.send(ev_after_add)
if let relay_metadata = make_relay_metadata(relays: damus.pool.our_descriptors, keypair: keypair) {
damus.postbox.send(relay_metadata)
}
}
}
struct RecommendedRelayView_Previews: PreviewProvider {
static var previews: some View {
RecommendedRelayView(damus: test_damus_state, relay: "wss://relay.damus.io", user_recommended: true)
}
}

View File

@ -7,156 +7,83 @@
import SwiftUI import SwiftUI
enum RelayTab: Int, CaseIterable{
case myRelays = 0
case recommended
var title: String{
switch self {
case .myRelays:
return "My relays"
case .recommended:
return "Recommended"
}
}
}
struct RelayConfigView: View { struct RelayConfigView: View {
let state: DamusState let state: DamusState
@State var relays: [RelayDescriptor] @State var relays: [RelayDescriptor]
@State private var showActionButtons = false @State private var showActionButtons = false
@State var show_add_relay: Bool = false @State var show_add_relay: Bool = false
@SceneStorage("RelayConfigView.show_recommended") var show_recommended : Bool = true @State var selectedTab = 0
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
init(state: DamusState) { init(state: DamusState) {
self.state = state self.state = state
_relays = State(initialValue: state.pool.our_descriptors) _relays = State(initialValue: state.pool.our_descriptors)
UITabBar.appearance().isHidden = true
} }
var recommended: [RelayDescriptor] { var recommended: [RelayDescriptor] {
let rs: [RelayDescriptor] = [] let rs: [RelayDescriptor] = []
let recommended_relay_addresses = get_default_bootstrap_relays() let recommended_relay_addresses = get_default_bootstrap_relays()
return recommended_relay_addresses.reduce(into: rs) { xs, x in return recommended_relay_addresses.reduce(into: rs) { xs, x in
if state.pool.get_relay(x) == nil, let url = RelayURL(x) { if let url = RelayURL(x) {
xs.append(RelayDescriptor(url: url, info: .rw)) xs.append(RelayDescriptor(url: url, info: .rw))
} }
} }
} }
var body: some View { var body: some View {
MainContent NavigationView {
.onReceive(handle_notify(.relays_changed)) { _ in ZStack(alignment: .bottom){
self.relays = state.pool.our_descriptors TabView(selection: $selectedTab) {
} RelayList(title: "My Relays", relayList: relays, recommended: false)
.onReceive(handle_notify(.switched_timeline)) { _ in .tag(0)
dismiss()
}
}
var MainContent: some View {
VStack {
Divider()
if showActionButtons && !show_recommended {
VStack {
Button(action: {
withAnimation(.easeOut(duration: 0.2)) {
show_recommended.toggle()
}
}) {
Text("Show recommended relays", comment: "Button to show recommended relays.")
.foregroundStyle(DamusLightGradient.gradient)
.padding(10)
.background {
RoundedRectangle(cornerRadius: 15)
.stroke(DamusLightGradient.gradient)
}
}
.padding(.top, 10)
}
}
if recommended.count > 0 && show_recommended {
VStack {
HStack(alignment: .top) {
Spacer()
Button(action: {
withAnimation(.easeOut(duration: 0.2)) {
show_recommended.toggle()
}
}) {
Image(systemName: "xmark.circle")
.font(.system(size: 18))
.foregroundStyle(DamusLightGradient.gradient)
}
.padding([.top, .trailing], 8)
}
Text("Recommended relays", comment: "Title for view of recommended relays.")
.foregroundStyle(DamusLightGradient.gradient)
.padding(10)
.background {
RoundedRectangle(cornerRadius: 15)
.stroke(DamusLightGradient.gradient)
}
ScrollView(.horizontal) {
HStack(spacing: 20) {
ForEach(recommended, id: \.url) { r in
RecommendedRelayView(damus: state, relay: r.url.id)
}
}
.padding(.horizontal, 30)
.padding(.vertical, 5)
}
.scrollIndicators(.hidden)
.mask(
HStack(spacing: 0) {
LinearGradient(gradient: Gradient(colors: [Color.clear, Color.white]), startPoint: .leading, endPoint: .trailing)
.frame(width: 30)
Rectangle()
.fill(Color.white)
.frame(maxWidth: .infinity)
LinearGradient(gradient: Gradient(colors: [Color.white, Color.clear]), startPoint: .leading, endPoint: .trailing)
.frame(width: 30)
}
)
.padding()
}
.frame(minWidth: 250, maxWidth: .infinity, minHeight: 250, alignment: .center)
.background {
RoundedRectangle(cornerRadius: 12)
.fill(DamusLightGradient.gradient.opacity(0.15), strokeBorder: DamusLightGradient.gradient, lineWidth: 1)
}
.padding(.horizontal)
}
HStack {
Text(NSLocalizedString("My Relays", comment: "Section title for relay servers that the user is connected to."))
.font(.system(size: 32, weight: .bold))
Spacer() RelayList(title: "Recommended", relayList: recommended, recommended: true)
.tag(1)
Button(action: { }
show_add_relay.toggle() ZStack{
}) { HStack{
HStack { ForEach((RelayTab.allCases), id: \.self){ item in
Text(verbatim: "Add relay") Button{
.padding(10) selectedTab = item.rawValue
} label: {
CustomTabItem(title: item.title, isActive: (selectedTab == item.rawValue))
}
}
} }
} }
.buttonStyle(NeutralButtonStyle()) .frame(width: 235, height: 35)
.background(.damusNeutral3)
.cornerRadius(30)
.padding(.horizontal, 26)
} }
.padding(25)
List(Array(relays), id: \.url) { relay in
RelayView(state: state, relay: relay.url.id, showActionButtons: $showActionButtons)
}
.listStyle(PlainListStyle())
} }
.navigationTitle(NSLocalizedString("Relays", comment: "Title of relays view")) .navigationTitle(NSLocalizedString("Relays", comment: "Title of relays view"))
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: BackNav())
.sheet(isPresented: $show_add_relay, onDismiss: { self.show_add_relay = false }) { .sheet(isPresented: $show_add_relay, onDismiss: { self.show_add_relay = false }) {
if #available(iOS 16.0, *) { AddRelayView(state: state)
AddRelayView(state: state) .presentationDetents([.height(300)])
.presentationDetents([.height(300)]) .presentationDragIndicator(.visible)
.presentationDragIndicator(.visible)
} else {
AddRelayView(state: state)
}
} }
.toolbar { .toolbar {
if state.keypair.privkey != nil { if state.keypair.privkey != nil && selectedTab == 0 {
if showActionButtons { if showActionButtons {
Button("Done") { Button("Done") {
withAnimation { withAnimation {
@ -172,6 +99,65 @@ struct RelayConfigView: View {
} }
} }
} }
.onReceive(handle_notify(.relays_changed)) { _ in
self.relays = state.pool.our_descriptors
}
.onAppear {
notify(.display_tabbar(false))
}
.onDisappear {
notify(.display_tabbar(true))
}
.ignoresSafeArea(.all)
}
func RelayList(title: String, relayList: [RelayDescriptor], recommended: Bool) -> some View {
ScrollView(showsIndicators: false) {
HStack {
Text(NSLocalizedString(title, comment: "Section title for type of relay server list"))
.font(.system(size: 32, weight: .bold))
Spacer()
if state.keypair.privkey != nil {
Button(action: {
show_add_relay.toggle()
}) {
HStack {
Text(verbatim: "Add relay")
.padding(10)
}
}
.buttonStyle(NeutralButtonStyle())
}
}
.padding(.top, 5)
ForEach(relayList, id: \.url) { relay in
Group {
RelayView(state: state, relay: relay.url.id, showActionButtons: $showActionButtons, recommended: recommended)
Divider()
}
}
Spacer()
.padding(25)
}
.padding(.horizontal)
}
}
extension RelayConfigView{
func CustomTabItem(title: String, isActive: Bool) -> some View {
HStack {
Text(title)
.font(.system(size: 12, weight: isActive ? .bold : .regular))
.foregroundColor(isActive ? .damusAdaptableBlack : .damusAdaptableBlack.opacity(0.7))
}
.frame(width: 110, height: 30)
.background(isActive ? .damusAdaptableWhite.opacity(0.9) : .clear)
.cornerRadius(30)
} }
} }

View File

@ -66,97 +66,101 @@ struct RelayDetailView: View {
} }
var body: some View { var body: some View {
Group { NavigationView {
Form { ZStack {
if let keypair = state.keypair.to_full() { Group {
if check_connection() { Form {
RemoveRelayButton(keypair) if let keypair = state.keypair.to_full() {
} else { if check_connection() {
Button(action: { RemoveRelayButton(keypair)
guard let ev_before_add = state.contacts.event else { } else {
return Button(action: {
guard let ev_before_add = state.contacts.event else {
return
}
guard let relay_url = RelayURL(relay),
let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else {
return
}
process_contact_event(state: state, ev: ev_after_add)
state.postbox.send(ev_after_add)
if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) {
state.postbox.send(relay_metadata)
}
dismiss()
}) {
Text("Connect To Relay", comment: "Button to connect to the relay.")
}
} }
guard let relay_url = RelayURL(relay), }
let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else {
return if let authentication_state: RelayAuthenticationState = relay_object?.authentication_state,
authentication_state != .none {
Section(NSLocalizedString("Authentication", comment: "Header label to display authentication details for a given relay.")) {
RelayAuthenticationDetail(state: authentication_state)
}
}
if let pubkey = nip11?.pubkey {
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
UserViewRow(damus_state: state, pubkey: pubkey)
.onTapGesture {
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
}
}
}
if let relay_connection {
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
HStack {
Text(relay)
Spacer()
RelayStatusView(connection: relay_connection)
}
}
}
if let nip11 {
if nip11.is_paid {
Section(content: {
RelayPaidDetail(payments_url: nip11.payments_url)
}, header: {
Text("Paid Relay", comment: "Section header that indicates the relay server requires payment.")
}, footer: {
Text("This is a paid relay, you must pay for notes to be accepted.", comment: "Footer description that explains that the relay server requires payment to post.")
})
} }
process_contact_event(state: state, ev: ev_after_add)
state.postbox.send(ev_after_add)
if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) { Section(NSLocalizedString("Description", comment: "Label to display relay description.")) {
state.postbox.send(relay_metadata) FieldText(nip11.description)
} }
dismiss() Section(NSLocalizedString("Contact", comment: "Label to display relay contact information.")) {
}) { FieldText(nip11.contact)
Text("Connect To Relay", comment: "Button to connect to the relay.") }
} Section(NSLocalizedString("Software", comment: "Label to display relay software.")) {
} FieldText(nip11.software)
} }
Section(NSLocalizedString("Version", comment: "Label to display relay software version.")) {
if let authentication_state: RelayAuthenticationState = relay_object?.authentication_state, FieldText(nip11.version)
authentication_state != .none { }
Section(NSLocalizedString("Authentication", comment: "Header label to display authentication details for a given relay.")) { if let nips = nip11.supported_nips, nips.count > 0 {
RelayAuthenticationDetail(state: authentication_state) Section(NSLocalizedString("Supported NIPs", comment: "Label to display relay's supported NIPs.")) {
} Text(nipsList(nips: nips))
} }
if let pubkey = nip11?.pubkey {
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
UserViewRow(damus_state: state, pubkey: pubkey)
.onTapGesture {
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
} }
}
}
if let relay_connection {
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
HStack {
Text(relay)
Spacer()
RelayStatusView(connection: relay_connection)
} }
}
} if state.settings.developer_mode {
Section(NSLocalizedString("Log", comment: "Label to display developer mode logs.")) {
if let nip11 { Text(log.contents ?? NSLocalizedString("No logs to display", comment: "Label to indicate that there are no developer mode logs available to be displayed on the screen"))
if nip11.is_paid { .font(.system(size: 13))
Section(content: { .lineLimit(nil)
RelayPaidDetail(payments_url: nip11.payments_url) .fixedSize(horizontal: false, vertical: true)
}, header: { }
Text("Paid Relay", comment: "Section header that indicates the relay server requires payment.")
}, footer: {
Text("This is a paid relay, you must pay for notes to be accepted.", comment: "Footer description that explains that the relay server requires payment to post.")
})
}
Section(NSLocalizedString("Description", comment: "Label to display relay description.")) {
FieldText(nip11.description)
}
Section(NSLocalizedString("Contact", comment: "Label to display relay contact information.")) {
FieldText(nip11.contact)
}
Section(NSLocalizedString("Software", comment: "Label to display relay software.")) {
FieldText(nip11.software)
}
Section(NSLocalizedString("Version", comment: "Label to display relay software version.")) {
FieldText(nip11.version)
}
if let nips = nip11.supported_nips, nips.count > 0 {
Section(NSLocalizedString("Supported NIPs", comment: "Label to display relay's supported NIPs.")) {
Text(nipsList(nips: nips))
} }
} }
} }
if state.settings.developer_mode {
Section(NSLocalizedString("Log", comment: "Label to display developer mode logs.")) {
Text(log.contents ?? NSLocalizedString("No logs to display", comment: "Label to indicate that there are no developer mode logs available to be displayed on the screen"))
.font(.system(size: 13))
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
}
}
} }
} }
.onReceive(handle_notify(.switched_timeline)) { notif in .onReceive(handle_notify(.switched_timeline)) { notif in
@ -164,6 +168,9 @@ struct RelayDetailView: View {
} }
.navigationTitle(nip11?.name ?? relay) .navigationTitle(nip11?.name ?? relay)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: BackNav())
.ignoresSafeArea(.all)
} }
private func nipsList(nips: [Int]) -> AttributedString { private func nipsList(nips: [Int]) -> AttributedString {

View File

@ -61,7 +61,7 @@ struct InnerRelayPicView: View {
} }
.frame(width: size, height: size) .frame(width: size, height: size)
.clipShape(RoundedRectangle(cornerRadius: 15)) .clipShape(RoundedRectangle(cornerRadius: 15))
.overlay(RoundedRectangle(cornerRadius: 15).stroke(failedImage ? .gray : highlight_color(highlight), lineWidth: failedImage ? 1 : pfp_line_width(highlight))) .overlay(RoundedRectangle(cornerRadius: 15).stroke(.gray.opacity(0.5), lineWidth: 0.5))
} }
} }

View File

@ -51,7 +51,6 @@ struct RelayStatusView: View {
) )
} }
} }
.padding(.trailing, 20)
} }
} }

View File

@ -10,22 +10,31 @@ import SwiftUI
struct RelayView: View { struct RelayView: View {
let state: DamusState let state: DamusState
let relay: String let relay: String
let recommended: Bool
@ObservedObject private var model_cache: RelayModelCache @ObservedObject private var model_cache: RelayModelCache
@State var relay_state: Bool
@Binding var showActionButtons: Bool @Binding var showActionButtons: Bool
init(state: DamusState, relay: String, showActionButtons: Binding<Bool>) { init(state: DamusState, relay: String, showActionButtons: Binding<Bool>, recommended: Bool) {
self.state = state self.state = state
self.relay = relay self.relay = relay
self.recommended = recommended
self.model_cache = state.relay_model_cache self.model_cache = state.relay_model_cache
_showActionButtons = showActionButtons _showActionButtons = showActionButtons
let relay_state = RelayView.get_relay_state(pool: state.pool, relay: relay)
self._relay_state = State(initialValue: relay_state)
}
static func get_relay_state(pool: RelayPool, relay: String) -> Bool {
return pool.get_relay(relay) == nil
} }
var body: some View { var body: some View {
Group { Group {
HStack { HStack {
if let privkey = state.keypair.privkey { if let privkey = state.keypair.privkey {
if showActionButtons { if showActionButtons && !recommended {
RemoveButton(privkey: privkey, showText: false) RemoveButton(privkey: privkey, showText: false)
} }
} }
@ -39,39 +48,60 @@ struct RelayView: View {
Text(meta?.name ?? relay) Text(meta?.name ?? relay)
.font(.headline) .font(.headline)
.padding(.bottom, 2) .padding(.bottom, 2)
.lineLimit(1)
RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false) RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false)
} }
Text(relay) Text(relay)
.font(.subheadline) .font(.subheadline)
.foregroundColor(.gray) .foregroundColor(.gray)
.lineLimit(1)
.contextMenu {
CopyAction(relay: relay)
if let privkey = state.keypair.privkey {
RemoveButton(privkey: privkey, showText: true)
}
}
} }
Spacer() Spacer()
if recommended {
if let keypair = state.keypair.to_full() {
VStack(alignment: .center) {
if relay_state {
AddButton(keypair: keypair)
} else {
Button(action: {
remove_action(privkey: keypair.privkey)
}) {
Text(NSLocalizedString("Added", comment: "Button to show relay server is already added to list."))
.font(.caption)
}
.buttonStyle(NeutralButtonShape.capsule.style)
.opacity(0.5)
}
}
.padding(.horizontal, 5)
}
} else {
if let relay_connection {
RelayStatusView(connection: relay_connection)
}
if let relay_connection { Image("chevron-large-right")
RelayStatusView(connection: relay_connection) .resizable()
.background( .frame(width: 15, height: 15)
NavigationLink(value: Route.RelayDetail(relay: relay, metadata: meta), label: { .foregroundColor(.gray)
EmptyView()
})
.buttonStyle(.plain)
.disabled(showActionButtons)
)
} }
} }
.contentShape(Rectangle())
} }
.swipeActions { .onReceive(handle_notify(.relays_changed)) { _ in
if let privkey = state.keypair.privkey { self.relay_state = RelayView.get_relay_state(pool: state.pool, relay: self.relay)
RemoveButton(privkey: privkey, showText: false)
.tint(.red)
}
} }
.contextMenu { .onTapGesture {
CopyAction(relay: relay) state.nav.push(route: Route.RelayDetail(relay: relay, metadata: model_cache.model(with_relay_id: relay)?.metadata))
if let privkey = state.keypair.privkey {
RemoveButton(privkey: privkey, showText: true)
}
} }
} }
@ -79,6 +109,52 @@ struct RelayView: View {
state.pool.get_relay(relay)?.connection state.pool.get_relay(relay)?.connection
} }
func add_action(keypair: FullKeypair) {
guard let ev_before_add = state.contacts.event else {
return
}
guard let relay_url = RelayURL(relay),
let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else {
return
}
process_contact_event(state: state, ev: ev_after_add)
state.postbox.send(ev_after_add)
if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) {
state.postbox.send(relay_metadata)
}
}
func remove_action(privkey: Privkey) {
guard let ev = state.contacts.event else {
return
}
let descriptors = state.pool.our_descriptors
guard let keypair = state.keypair.to_full(),
let relay_url = RelayURL(relay),
let new_ev = remove_relay(ev: ev, current_relays: descriptors, keypair: keypair, relay: relay_url) else {
return
}
process_contact_event(state: state, ev: new_ev)
state.postbox.send(new_ev)
if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) {
state.postbox.send(relay_metadata)
}
}
func AddButton(keypair: FullKeypair) -> some View {
Button(action: {
add_action(keypair: keypair)
}) {
Text(NSLocalizedString("Add", comment: "Button to add relay server to list."))
.font(.caption)
}
.buttonStyle(NeutralButtonShape.capsule.style)
}
func CopyAction(relay: String) -> some View { func CopyAction(relay: String) -> some View {
Button { Button {
UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text") UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text")
@ -89,23 +165,7 @@ struct RelayView: View {
func RemoveButton(privkey: Privkey, showText: Bool) -> some View { func RemoveButton(privkey: Privkey, showText: Bool) -> some View {
Button(action: { Button(action: {
guard let ev = state.contacts.event else { remove_action(privkey: privkey)
return
}
let descriptors = state.pool.our_descriptors
guard let keypair = state.keypair.to_full(),
let relay_url = RelayURL(relay),
let new_ev = remove_relay(ev: ev, current_relays: descriptors, keypair: keypair, relay: relay_url) else {
return
}
process_contact_event(state: state, ev: new_ev)
state.postbox.send(new_ev)
if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) {
state.postbox.send(relay_metadata)
}
}) { }) {
if showText { if showText {
Text(NSLocalizedString("Disconnect", comment: "Button to disconnect from a relay server.")) Text(NSLocalizedString("Disconnect", comment: "Button to disconnect from a relay server."))
@ -122,6 +182,6 @@ struct RelayView: View {
struct RelayView_Previews: PreviewProvider { struct RelayView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
RelayView(state: test_damus_state, relay: "wss://relay.damus.io", showActionButtons: .constant(false)) RelayView(state: test_damus_state, relay: "wss://relay.damus.io", showActionButtons: .constant(false), recommended: false)
} }
} }

View File

@ -0,0 +1,31 @@
//
// QuoteRepostsView.swift
// damus
//
// Created by William Casarin on 2024-03-16.
//
import SwiftUI
struct QuoteRepostsView: View {
let damus_state: DamusState
@ObservedObject var model: EventsModel
var body: some View {
TimelineView<AnyView>(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: ContentFilters.default_filters(damus_state: damus_state).filter(ev:))
.navigationBarTitle(NSLocalizedString("Quotes", comment: "Navigation bar title for Quote Reposts view."))
.onAppear {
model.subscribe()
}
.onDisappear {
model.unsubscribe()
}
}
}
struct QuoteRepostsView_Previews: PreviewProvider {
static var previews: some View {
let state = test_damus_state
QuoteRepostsView(damus_state: state, model: .reposts(state: state, target: test_note.id))
}
}

View File

@ -9,12 +9,12 @@ import SwiftUI
struct RepostsView: View { struct RepostsView: View {
let damus_state: DamusState let damus_state: DamusState
@StateObject var model: RepostsModel @StateObject var model: EventsModel
var body: some View { var body: some View {
ScrollView { ScrollView {
LazyVStack { LazyVStack {
ForEach(model.events, id: \.id) { ev in ForEach(model.events.events, id: \.id) { ev in
RepostView(damus_state: damus_state, repost: ev) RepostView(damus_state: damus_state, repost: ev)
} }
} }
@ -33,6 +33,6 @@ struct RepostsView: View {
struct RepostsView_Previews: PreviewProvider { struct RepostsView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let state = test_damus_state let state = test_damus_state
RepostsView(damus_state: state, model: RepostsModel(state: state, target: test_note.id)) RepostsView(damus_state: state, model: .reposts(state: state, target: test_note.id))
} }
} }

View File

@ -28,12 +28,9 @@ struct UserRelaysView: View {
var body: some View { var body: some View {
List(relay_state, id: \.0) { (r, add) in List(relay_state, id: \.0) { (r, add) in
RecommendedRelayView(damus: state, relay: r, add_button: add, user_recommended: true) RelayView(state: state, relay: r, showActionButtons: .constant(true), recommended: true)
} }
.listStyle(PlainListStyle()) .listStyle(PlainListStyle())
.onReceive(handle_notify(.relays_changed)) { _ in
self.relay_state = UserRelaysView.make_relay_state(pool: state.pool, relays: self.relays)
}
.navigationBarTitle(NSLocalizedString("Relays", comment: "Navigation bar title that shows the list of relays for a user.")) .navigationBarTitle(NSLocalizedString("Relays", comment: "Navigation bar title that shows the list of relays for a user."))
} }
} }

View File

@ -12,14 +12,15 @@ struct ConnectWalletView: View {
@ObservedObject var model: WalletModel @ObservedObject var model: WalletModel
@State var scanning: Bool = false @State var scanning: Bool = false
@State private var showAlert = false
@State var error: String? = nil @State var error: String? = nil
@State var wallet_scan_result: WalletScanResult = .scanning @State var wallet_scan_result: WalletScanResult = .scanning
var nav: NavigationCoordinator var nav: NavigationCoordinator
var body: some View { var body: some View {
MainContent MainContent
.navigationTitle(NSLocalizedString("Attach a Wallet", comment: "Navigation title for attaching Nostr Wallet Connect lightning wallet.")) .navigationTitle(NSLocalizedString("Wallet", comment: "Navigation title for attaching Nostr Wallet Connect lightning wallet."))
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.inline)
.padding() .padding()
.onChange(of: wallet_scan_result) { res in .onChange(of: wallet_scan_result) { res in
scanning = false scanning = false
@ -30,18 +31,29 @@ struct ConnectWalletView: View {
self.model.new(url) self.model.new(url)
case .failed: case .failed:
error = NSLocalizedString("Invalid Nostr wallet connection string", comment: "Error message when an invalid Nostr wallet connection string is provided.") showAlert.toggle()
case .scanning: case .scanning:
error = nil error = nil
} }
} }
.alert(isPresented: $showAlert) {
Alert(
title: Text(NSLocalizedString("Invalid Nostr wallet connection string", comment: "Error message when an invalid Nostr wallet connection string is provided.")),
message: Text("Make sure the wallet you are connecting to supports NWC."),
dismissButton: .default(Text(NSLocalizedString("OK", comment: "Button label indicating user wants to proceed."))) {
wallet_scan_result = .scanning
}
)
}
} }
func AreYouSure(nwc: WalletConnectURL) -> some View { func AreYouSure(nwc: WalletConnectURL) -> some View {
VStack { VStack(spacing: 25) {
Text("Are you sure you want to attach this wallet?", comment: "Prompt to ask user if they want to attach their Nostr Wallet Connect lightning wallet.")
.font(.title) Text("Are you sure you want to connect this wallet?", comment: "Prompt to ask user if they want to attach their Nostr Wallet Connect lightning wallet.")
.fontWeight(.bold)
.multilineTextAlignment(.center)
Text(nwc.relay.id) Text(nwc.relay.id)
.font(.body) .font(.body)
@ -53,26 +65,73 @@ struct ConnectWalletView: View {
.foregroundColor(.gray) .foregroundColor(.gray)
} }
BigButton(NSLocalizedString("Attach", comment: "Text for button to attach Nostr Wallet Connect lightning wallet.")) { Button(action: {
model.connect(nwc) model.connect(nwc)
}) {
HStack {
Text("Connect", comment: "Text for button to conect to Nostr Wallet Connect lightning wallet.")
.fontWeight(.semibold)
}
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 18, alignment: .center)
} }
.buttonStyle(GradientButtonStyle())
BigButton(NSLocalizedString("Cancel", comment: "Text for button to cancel out of connecting Nostr Wallet Connect lightning ewallet.")) { Button(action: {
model.cancel() model.cancel()
}) {
HStack {
Text(NSLocalizedString("Cancel", comment: "Text for button to cancel out of connecting Nostr Wallet Connect lightning wallet."))
.padding()
}
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
} }
.buttonStyle(NeutralButtonStyle())
} }
} }
var ConnectWallet: some View { var ConnectWallet: some View {
VStack { VStack(spacing: 25) {
AlbyButton() { AlbyButton() {
openURL(URL(string:"https://nwc.getalby.com/apps/new?c=Damus")!) openURL(URL(string:"https://nwc.getalby.com/apps/new?c=Damus")!)
} }
BigButton(NSLocalizedString("Attach Wallet", comment: "Text for button to attach Nostr Wallet Connect lightning wallet.")) { MutinyButton() {
nav.push(route: Route.WalletScanner(result: $wallet_scan_result)) openURL(URL(string:"https://app.mutinywallet.com/settings/connections")!)
} }
Button(action: {
if let pasted_nwc = UIPasteboard.general.string {
guard let url = WalletConnectURL(str: pasted_nwc) else {
wallet_scan_result = .failed
return
}
wallet_scan_result = .success(url)
}
}) {
HStack {
Image("clipboard")
Text("Paste NWC Address", comment: "Text for button to connect a lightning wallet.")
.fontWeight(.semibold)
}
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 18, alignment: .center)
}
.buttonStyle(GradientButtonStyle())
Button(action: {
nav.push(route: Route.WalletScanner(result: $wallet_scan_result))
}) {
HStack {
Image("qr-code")
Text("Scan NWC Address", comment: "Text for button to connect a lightning wallet.")
.fontWeight(.semibold)
}
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 18, alignment: .center)
}
.buttonStyle(GradientButtonStyle())
if let err = self.error { if let err = self.error {
Text(err) Text(err)
.foregroundColor(.red) .foregroundColor(.red)
@ -80,14 +139,54 @@ struct ConnectWalletView: View {
} }
} }
var TopSection: some View {
HStack(spacing: 0) {
Button(action: {}, label: {
Image("damus-home")
.resizable()
.frame(width: 30, height: 30)
})
.buttonStyle(NeutralButtonStyle(padding: EdgeInsets(top: 15, leading: 15, bottom: 15, trailing: 15), cornerRadius: 9999))
.disabled(true)
.padding(.horizontal, 30)
Image("chevron-double-right")
.resizable()
.frame(width: 25, height: 25)
Button(action: {}, label: {
Image("wallet")
.resizable()
.frame(width: 30, height: 30)
.foregroundStyle(LINEAR_GRADIENT)
})
.buttonStyle(NeutralButtonStyle(padding: EdgeInsets(top: 15, leading: 15, bottom: 15, trailing: 15), cornerRadius: 9999))
.disabled(true)
.padding(.horizontal, 30)
}
}
var TitleSection: some View {
VStack(spacing: 25) {
Text("Damus Wallet")
.fontWeight(.bold)
Text("Securely connect your Damus app to your wallet\nusing Nostr Wallet Connect")
.font(.caption)
.multilineTextAlignment(.center)
}
}
var MainContent: some View { var MainContent: some View {
Group { Group {
TopSection
switch model.connect_state { switch model.connect_state {
case .new(let nwc): case .new(let nwc):
AreYouSure(nwc: nwc) AreYouSure(nwc: nwc)
case .existing: case .existing:
Text(verbatim: "Shouldn't happen") Text(verbatim: "Shouldn't happen")
case .none: case .none:
TitleSection
ConnectWallet ConnectWallet
} }
} }

View File

@ -45,41 +45,6 @@ enum WalletScanResult: Equatable {
case scanning case scanning
} }
struct NWCPaste: View {
@Binding var result: WalletScanResult
@Environment(\.colorScheme) var colorScheme
init(result: Binding<WalletScanResult>) {
self._result = result
}
var body: some View {
Button(action: {
if let pasted_nwc = UIPasteboard.general.string {
guard let url = WalletConnectURL(str: pasted_nwc) else {
self.result = .failed
return
}
self.result = .success(url)
}
}) {
HStack {
Image(systemName: "doc.on.clipboard")
Text("Paste", comment: "Button to paste a Nostr Wallet Connect string to connect the wallet for use in Damus for zaps.")
}
.frame(minWidth: 300, maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .center)
.foregroundColor(colorScheme == .light ? DamusColors.black : DamusColors.white)
.overlay {
RoundedRectangle(cornerRadius: 24)
.stroke(colorScheme == .light ? DamusColors.black : DamusColors.white, lineWidth: 2)
}
.padding(EdgeInsets(top: 10, leading: 50, bottom: 25, trailing: 50))
}
}
}
struct WalletScannerView: View { struct WalletScannerView: View {
@Binding var result: WalletScanResult @Binding var result: WalletScanResult
@ -92,6 +57,7 @@ struct WalletScannerView: View {
case .success(let success): case .success(let success):
guard let url = WalletConnectURL(str: success.string) else { guard let url = WalletConnectURL(str: success.string) else {
result = .failed result = .failed
dismiss()
return return
} }
@ -102,8 +68,6 @@ struct WalletScannerView: View {
dismiss() dismiss()
} }
NWCPaste(result: $result)
.padding(.vertical)
} }
} }
} }

View File

@ -26,19 +26,58 @@ struct WalletView: View {
Spacer() Spacer()
} }
Text(verbatim: nwc.relay.id) VStack(spacing: 5) {
VStack(spacing: 10) {
if let lud16 = nwc.lud16 { Text("Wallet Relay")
Text(verbatim: lud16) .fontWeight(.semibold)
.padding(.top)
Divider()
RelayView(state: damus_state, relay: nwc.relay.id, showActionButtons: .constant(false), recommended: false)
}
.frame(maxWidth: .infinity, minHeight: 125, alignment: .top)
.padding(.horizontal, 10)
.background(DamusColors.neutral1)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(DamusColors.neutral3, lineWidth: 1)
)
if let lud16 = nwc.lud16 {
VStack(spacing: 10) {
Text("Wallet Address")
.fontWeight(.semibold)
Divider()
Text(verbatim: lud16)
}
.frame(maxWidth: .infinity, minHeight: 75, alignment: .center)
.padding(.horizontal, 10)
.background(DamusColors.neutral1)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(DamusColors.neutral3, lineWidth: 1)
)
}
} }
BigButton(NSLocalizedString("Disconnect Wallet", comment: "Text for button to disconnect from Nostr Wallet Connect lightning wallet.")) { Button(action: {
self.model.disconnect() self.model.disconnect()
}) {
HStack {
Text(NSLocalizedString("Disconnect Wallet", comment: "Text for button to disconnect from Nostr Wallet Connect lightning wallet."))
}
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 18, alignment: .center)
} }
.buttonStyle(GradientButtonStyle())
} }
.navigationTitle(NSLocalizedString("Wallet", comment: "Navigation title for Wallet view")) .navigationTitle(NSLocalizedString("Wallet", comment: "Navigation title for Wallet view"))
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.inline)
.padding() .padding()
} }

View File

@ -226,6 +226,22 @@
<string>Reposts</string> <string>Reposts</string>
</dict> </dict>
</dict> </dict>
<key>quoted_reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@QUOTE_REPOSTS@</string>
<key>QUOTE_REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Quote</string>
<key>other</key>
<string>Quotes</string>
</dict>
</dict>
<key>sats</key> <key>sats</key>
<dict> <dict>
<key>NSStringLocalizedFormatKey</key> <key>NSStringLocalizedFormatKey</key>

View File

@ -49,7 +49,8 @@ func generate_test_damus_state(
nav: .init(), nav: .init(),
music: .init(onChange: {_ in }), music: .init(onChange: {_ in }),
video: .init(), video: .init(),
ndb: ndb) ndb: ndb,
quote_reposts: .init(our_pubkey: our_pubkey) )
return damus return damus
} }

View File

@ -280,6 +280,13 @@ extension NdbNote {
return kind == 1 || kind == 42 || kind == 30023 return kind == 1 || kind == 42 || kind == 30023
} }
var is_quote_repost: NoteId? {
guard kind == 1, let quoted_note_id = referenced_quote_ids.first else {
return nil
}
return quoted_note_id.note_id
}
var known_kind: NostrKind? { var known_kind: NostrKind? {
return NostrKind.init(rawValue: kind) return NostrKind.init(rawValue: kind)
} }