Compare commits

..

1 Commits

Author SHA1 Message Date
tyiu 180c5ce7ee Fix nostr URL scheme to open properly even if there's already a different view open
Changelog-Fixed: Fix nostr URL scheme to open properly even if there's already a different view open
2023-05-14 14:32:56 -04:00
225 changed files with 2628 additions and 7231 deletions
-50
View File
@@ -1,53 +1,3 @@
## [1.5-5] - 2023-06-24
### Fixed
- Remove note zaps to fit apples appstore guidelines
- Fix zap sheet popping (William Casarin)
- Fix CustomizeZapView from randomly disappearing (William Casarin)
- Fix "zapped your profile" strings to say "zapped you" (Terry Yiu)
- Fix reconnect loop issues on iOS17 (William Casarin)
- Fix some more thread jankiness (William Casarin)
- Fix spelling of Nostr to use Titlecase instead of lowercase (Terry Yiu)
- Rename all usages of the term Post as a noun to Note to conform to the Nostr spec (Terry Yiu)
- Fix text cutoff on login with npub (gladiusKatana)
- Fix hangs due to video player (William Casarin)
[1.5-5]: https://github.com/damus-io/damus/releases/tag/v1.5-5
## [1.5-2] - 2023-05-30
### Added
- Add new full-bleed video player (William Casarin)
- Add ability to show multiple posts per user in Universe (Ben Weeks)
- Custom iconography added for other areas of the app. (Ben Weeks)
- Custom iconography for the left navigation. (Ben Weeks)
- Custom iconography for the tab buttons. (Ben Weeks)
- Added dots under image carousel (Ben Weeks)
- Add profile caching (Bryan Montz)
- Add mention parsing and fine-grained text selection on description in ProfileView (Terry Yiu)
### Changed
- Redesign phase 1 (text, icons)
- Updated UI to use custom font (Ben Weeks)
### Fixed
- Fix side menu bug in landscape (OlegAba)
- Use "Follow me on nostr" text when looking at someone else's QR code (Ben Weeks)
- Fix issue where cursor dissapears when typing long message (gladiusKatana)
- Attempt fix for randomly broken animated gifs (William Casarin)
- Fix cursor jumping when pressing return (gladius)
- Fix side menu label size so that translations in longer languages fit without wrapping (Terry Yiu)
- Fix reaction notification title to be consistent with ReactionView (Terry Yiu)
- Fix nostr URL scheme to open properly even if there's already a different view open (Terry Yiu)
- Fix crash related to preloading events (Bryan Montz)
## v1.4.3 - 2023-05-08 ## v1.4.3 - 2023-05-08
### Added ### Added
+1 -24
View File
@@ -94,32 +94,9 @@ damus implements the following [Nostr Implementation Possibilities][nips]
Contributors welcome! Start by examining known issues: https://github.com/damus-io/damus/issues. Contributors welcome! Start by examining known issues: https://github.com/damus-io/damus/issues.
### Mailing lists
We have a few mailing lists that anyone can join to get involved in damus development:
- [dev][dev-list] - development discussions
- [patches][patches-list] - code submission and review
- [product][product-list] - product discussions
- [design][design-list] - design discussions
[dev-list]: https://damus.io/list/dev
[patches-list]: https://damus.io/list/patches
[product-list]: https://damus.io/list/product
[design-list]: https://damus.io/list/design
### Code ### Code
[Email patches][git-send-email] to patches@damus.io are preferred, but I accept PRs on GitHub as well. Patches sent via email may include a bolt11 lightning invoice, choosing the price you think the patch is worth, and I will pay it once the patch is accepted and if I think the price isn't unreasonable. You can also send an any-amount invoice and I will pay what I think it's worth if you prefer not to choose. You can include the bolt11 in the commit body or email so that it can be paid once it is applied. [Email patches][git-send-email] to jb55@jb55.com are preferred, but I accept PRs on GitHub as well.
Recommended settings when submitting code via email:
```
$ git config sendemail.to "patches@damus.io"
$ git config format.subjectPrefix "PATCH damus"
$ git config --global sendemail.annotate yes
$ git config format.signOff yes
```
[git-send-email]: http://git-send-email.io [git-send-email]: http://git-send-email.io
+15
View File
@@ -110,6 +110,21 @@ static inline int peek_char(struct cursor *cur, int ind) {
return *(cur->p + ind); return *(cur->p + ind);
} }
static int parse_digit(struct cursor *cur, int *digit) {
int c;
if ((c = peek_char(cur, 0)) == -1)
return 0;
c -= '0';
if (c >= 0 && c <= 9) {
*digit = c;
cur->p++;
return 1;
}
return 0;
}
static inline int pull_byte(struct cursor *cur, u8 *byte) { static inline int pull_byte(struct cursor *cur, u8 *byte) {
if (cur->p >= cur->end) if (cur->p >= cur->end)
-16
View File
@@ -12,22 +12,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
static int parse_digit(struct cursor *cur, int *digit) {
int c;
if ((c = peek_char(cur, 0)) == -1)
return 0;
c -= '0';
if (c >= 0 && c <= 9) {
*digit = c;
cur->p++;
return 1;
}
return 0;
}
static int parse_mention_index(struct cursor *cur, struct block *block) { static int parse_mention_index(struct cursor *cur, struct block *block) {
int d1, d2, d3, ind; int d1, d2, d3, ind;
const u8 *start = cur->p; const u8 *start = cur->p;
+1 -1
View File
@@ -26,7 +26,7 @@ bool hex_decode(const char *str, size_t slen, void *buf, size_t bufsize);
/** /**
* hex_encode - Create a nul-terminated hex string * hex_encode - Create a nul-terminated hex string
* @buf: the buffer to read the data from * @buf: the buffer to read the data from
* @bufsize: the length of buf * @bufsize: the length of @buf
* @dest: the string to fill * @dest: the string to fill
* @destsize: the max size of the string * @destsize: the max size of the string
* *
+8 -152
View File
@@ -11,16 +11,13 @@
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; }; 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; };
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; }; 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; }; 31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */; };
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */; }; 3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */; };
3A3040EF29A8FEE9008A0F29 /* EventDetailBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EE29A8FEE9008A0F29 /* EventDetailBarTests.swift */; }; 3A3040EF29A8FEE9008A0F29 /* EventDetailBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EE29A8FEE9008A0F29 /* EventDetailBarTests.swift */; };
3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */; }; 3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */; };
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F229A91366008A0F29 /* ProfileViewTests.swift */; }; 3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F229A91366008A0F29 /* ProfileViewTests.swift */; };
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */; }; 3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */; };
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; }; 3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */; };
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; }; 3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; };
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FC297E3CFF0090C62D /* RepostsModel.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 */; };
@@ -52,13 +49,10 @@
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2229DDDB8100516EAC /* IconLabel.swift */; }; 4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2229DDDB8100516EAC /* IconLabel.swift */; };
4C1A9A2529DDDF2600516EAC /* ZapSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */; }; 4C1A9A2529DDDF2600516EAC /* ZapSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */; };
4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */; }; 4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */; };
4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2929DDF54400516EAC /* DamusVideoPlayer.swift */; };
4C216F32286E388800040376 /* DMChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F31286E388800040376 /* DMChatView.swift */; }; 4C216F32286E388800040376 /* DMChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F31286E388800040376 /* DMChatView.swift */; };
4C216F34286F5ACD00040376 /* DMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F33286F5ACD00040376 /* DMView.swift */; }; 4C216F34286F5ACD00040376 /* DMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F33286F5ACD00040376 /* DMView.swift */; };
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */; }; 4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */; };
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F372871EDE300040376 /* DirectMessageModel.swift */; }; 4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C216F372871EDE300040376 /* DirectMessageModel.swift */; };
4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28595F2A12A2BE004746F7 /* SupporterBadge.swift */; };
4C2859622A12A7F0004746F7 /* GoldSupportGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2859612A12A7F0004746F7 /* GoldSupportGradient.swift */; };
4C285C8228385570008A31F1 /* CarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8128385570008A31F1 /* CarouselView.swift */; }; 4C285C8228385570008A31F1 /* CarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8128385570008A31F1 /* CarouselView.swift */; };
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8328385690008A31F1 /* CreateAccountView.swift */; }; 4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8328385690008A31F1 /* CreateAccountView.swift */; };
4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C85283892E7008A31F1 /* CreateAccountModel.swift */; }; 4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C85283892E7008A31F1 /* CreateAccountModel.swift */; };
@@ -132,7 +126,6 @@
4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */; }; 4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */; };
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */; }; 4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */; };
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */ = {isa = PBXBuildFile; productRef = 4C649880286E0EE300EAE2B3 /* secp256k1 */; }; 4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */ = {isa = PBXBuildFile; productRef = 4C649880286E0EE300EAE2B3 /* secp256k1 */; };
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C5132A4437C10062CAC0 /* ZapUserView.swift */; };
4C75EFA427FA577B0006080F /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA327FA577B0006080F /* PostView.swift */; }; 4C75EFA427FA577B0006080F /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA327FA577B0006080F /* PostView.swift */; };
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA527FF87A20006080F /* Nostr.swift */; }; 4C75EFA627FF87A20006080F /* Nostr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA527FF87A20006080F /* Nostr.swift */; };
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFAC28049CFB0006080F /* PostButton.swift */; }; 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFAC28049CFB0006080F /* PostButton.swift */; };
@@ -171,8 +164,6 @@
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; }; 4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; }; 4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; }; 4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
4C9AA1482A44442E003F49FD /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */; };
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */; };
4C9BB83129C0ED4F00FC4E37 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; }; 4C9BB83129C0ED4F00FC4E37 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
4C9BB83429C12D9900FC4E37 /* EventProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */; }; 4C9BB83429C12D9900FC4E37 /* EventProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */; };
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; }; 4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; };
@@ -200,7 +191,6 @@
4CB883AE2976FA9300DC99E7 /* FormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883AD2976FA9300DC99E7 /* FormatTests.swift */; }; 4CB883AE2976FA9300DC99E7 /* FormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883AD2976FA9300DC99E7 /* FormatTests.swift */; };
4CB883B0297705DD00DC99E7 /* ZapButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883AF297705DD00DC99E7 /* ZapButton.swift */; }; 4CB883B0297705DD00DC99E7 /* ZapButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883AF297705DD00DC99E7 /* ZapButton.swift */; };
4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883B5297730E400DC99E7 /* LNUrls.swift */; }; 4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883B5297730E400DC99E7 /* LNUrls.swift */; };
4CB8FC232A41ABA800763C51 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8FC222A41ABA500763C51 /* AboutView.swift */; };
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */; }; 4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */; };
4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */; }; 4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */; };
4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */; }; 4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */; };
@@ -215,8 +205,6 @@
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; }; 4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; };
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */; }; 4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */; };
4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */; }; 4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */; };
4CCF9AAF2A1FDBDB00E03CFB /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCF9AAE2A1FDBDB00E03CFB /* VideoPlayer.swift */; };
4CCF9AB22A1FE80C00E03CFB /* GSPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = 4CCF9AB12A1FE80C00E03CFB /* GSPlayer */; };
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */; }; 4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */; };
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; }; 4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128929E9D10C0006FA5A /* SignalView.swift */; }; 4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128929E9D10C0006FA5A /* SignalView.swift */; };
@@ -244,7 +232,7 @@
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794729941DA700F758CC /* RelayFilters.swift */; }; 4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794729941DA700F758CC /* RelayFilters.swift */; };
4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */; }; 4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */; };
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794D2996B16A00F758CC /* RelayToggle.swift */; }; 4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794D2996B16A00F758CC /* RelayToggle.swift */; };
4CE879502996B2BD00F758CC /* RelayStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794F2996B2BD00F758CC /* RelayStatusView.swift */; }; 4CE879502996B2BD00F758CC /* RelayStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794F2996B2BD00F758CC /* RelayStatus.swift */; };
4CE879522996B68900F758CC /* RelayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879512996B68900F758CC /* RelayType.swift */; }; 4CE879522996B68900F758CC /* RelayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879512996B68900F758CC /* RelayType.swift */; };
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879542996BAB900F758CC /* RelayPaidDetail.swift */; }; 4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879542996BAB900F758CC /* RelayPaidDetail.swift */; };
4CE879582996C45300F758CC /* ZapsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879572996C45300F758CC /* ZapsView.swift */; }; 4CE879582996C45300F758CC /* ZapsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879572996C45300F758CC /* ZapsView.swift */; };
@@ -275,21 +263,13 @@
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; }; 4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; };
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; }; 4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50088DA029E8271A008A1FDF /* WebSocket.swift */; }; 50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50088DA029E8271A008A1FDF /* WebSocket.swift */; };
5019CADD2A0FB0A9000069E1 /* ProfileDatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5019CADC2A0FB0A9000069E1 /* ProfileDatabaseTests.swift */; };
501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */; };
501F8C5829FF5FC5001AFC1D /* Damus.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5629FF5FC5001AFC1D /* Damus.xcdatamodeld */; };
501F8C5A29FF70F5001AFC1D /* ProfileDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */; };
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; }; 501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; };
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; }; 501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; }; 50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; }; 50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; };
50DA11262A16A23F00236234 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50DA11252A16A23F00236234 /* Launch.storyboard */; };
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; }; 5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; }; 5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; }; 5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; };
5C6E1DAF2A194075008FC15A /* PinkGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAE2A194075008FC15A /* PinkGradient.swift */; };
5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF72FC129B9142F00124A13 /* ShareAction.swift */; }; 5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF72FC129B9142F00124A13 /* ShareAction.swift */; };
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfilePicImageView.swift */; }; 6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfilePicImageView.swift */; };
643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643EA5C7296B764E005081BB /* RelayFilterView.swift */; }; 643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643EA5C7296B764E005081BB /* RelayFilterView.swift */; };
@@ -304,9 +284,7 @@
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; }; 9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; };
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; }; BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; }; BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; }; DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; };
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; }; E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; }; E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
F757933A29D7AECD007DEAC1 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757933929D7AECD007DEAC1 /* ImagePicker.swift */; }; F757933A29D7AECD007DEAC1 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757933929D7AECD007DEAC1 /* ImagePicker.swift */; };
@@ -345,7 +323,6 @@
3A185A04297F2C3800F4BDC0 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/InfoPlist.strings"; sourceTree = "<group>"; }; 3A185A04297F2C3800F4BDC0 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3A185A05297F2C3800F4BDC0 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/Localizable.strings"; sourceTree = "<group>"; }; 3A185A05297F2C3800F4BDC0 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/Localizable.strings"; sourceTree = "<group>"; };
3A185A06297F2C3800F4BDC0 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "lv-LV"; path = "lv-LV.lproj/Localizable.stringsdict"; sourceTree = "<group>"; }; 3A185A06297F2C3800F4BDC0 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "lv-LV"; path = "lv-LV.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapButtonModel.swift; sourceTree = "<group>"; };
3A25EF132992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/InfoPlist.strings"; sourceTree = "<group>"; }; 3A25EF132992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3A25EF142992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = "<group>"; }; 3A25EF142992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = "<group>"; };
3A25EF152992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "el-GR"; path = "el-GR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; }; 3A25EF152992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "el-GR"; path = "el-GR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
@@ -371,7 +348,6 @@
3A41E559299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 3A41E559299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; }; 3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CondensedProfilePicturesView.swift; sourceTree = "<group>"; };
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; }; 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; };
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; }; 3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A5CAE1D298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; }; 3A5CAE1D298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
@@ -389,7 +365,6 @@
3A8624D9299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 3A8624D9299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3A8624DA299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; }; 3A8624DA299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
3A8624DB299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 3A8624DB299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringUtil.swift; sourceTree = "<group>"; };
3A929C20297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/InfoPlist.strings"; sourceTree = "<group>"; }; 3A929C20297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3A929C21297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/Localizable.strings"; sourceTree = "<group>"; }; 3A929C21297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/Localizable.strings"; sourceTree = "<group>"; };
3A929C22297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "it-IT"; path = "it-IT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; }; 3A929C22297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "it-IT"; path = "it-IT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
@@ -465,13 +440,10 @@
4C1A9A2229DDDB8100516EAC /* IconLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconLabel.swift; sourceTree = "<group>"; }; 4C1A9A2229DDDB8100516EAC /* IconLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconLabel.swift; sourceTree = "<group>"; };
4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapSettingsView.swift; sourceTree = "<group>"; }; 4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapSettingsView.swift; sourceTree = "<group>"; };
4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = "<group>"; }; 4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = "<group>"; };
4C1A9A2929DDF54400516EAC /* DamusVideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusVideoPlayer.swift; sourceTree = "<group>"; };
4C216F31286E388800040376 /* DMChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMChatView.swift; sourceTree = "<group>"; }; 4C216F31286E388800040376 /* DMChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMChatView.swift; sourceTree = "<group>"; };
4C216F33286F5ACD00040376 /* DMView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMView.swift; sourceTree = "<group>"; }; 4C216F33286F5ACD00040376 /* DMView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DMView.swift; sourceTree = "<group>"; };
4C216F352870A9A700040376 /* InputDismissKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDismissKeyboard.swift; sourceTree = "<group>"; }; 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputDismissKeyboard.swift; sourceTree = "<group>"; };
4C216F372871EDE300040376 /* DirectMessageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessageModel.swift; sourceTree = "<group>"; }; 4C216F372871EDE300040376 /* DirectMessageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessageModel.swift; sourceTree = "<group>"; };
4C28595F2A12A2BE004746F7 /* SupporterBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupporterBadge.swift; sourceTree = "<group>"; };
4C2859612A12A7F0004746F7 /* GoldSupportGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoldSupportGradient.swift; sourceTree = "<group>"; };
4C285C8128385570008A31F1 /* CarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselView.swift; sourceTree = "<group>"; }; 4C285C8128385570008A31F1 /* CarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselView.swift; sourceTree = "<group>"; };
4C285C8328385690008A31F1 /* CreateAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAccountView.swift; sourceTree = "<group>"; }; 4C285C8328385690008A31F1 /* CreateAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAccountView.swift; sourceTree = "<group>"; };
4C285C85283892E7008A31F1 /* CreateAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAccountModel.swift; sourceTree = "<group>"; }; 4C285C85283892E7008A31F1 /* CreateAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAccountModel.swift; sourceTree = "<group>"; };
@@ -574,7 +546,6 @@
4C633351283D419F00B1C9C3 /* SignalModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalModel.swift; sourceTree = "<group>"; }; 4C633351283D419F00B1C9C3 /* SignalModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalModel.swift; sourceTree = "<group>"; };
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesView.swift; sourceTree = "<group>"; }; 4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesView.swift; sourceTree = "<group>"; };
4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesModel.swift; sourceTree = "<group>"; }; 4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesModel.swift; sourceTree = "<group>"; };
4C73C5132A4437C10062CAC0 /* ZapUserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapUserView.swift; sourceTree = "<group>"; };
4C75EFA327FA577B0006080F /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; }; 4C75EFA327FA577B0006080F /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
4C75EFA527FF87A20006080F /* Nostr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nostr.swift; sourceTree = "<group>"; }; 4C75EFA527FF87A20006080F /* Nostr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nostr.swift; sourceTree = "<group>"; };
4C75EFA72804823E0006080F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; 4C75EFA72804823E0006080F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
@@ -618,8 +589,6 @@
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>"; };
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>"; };
4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = "<group>"; };
4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusModel.swift; sourceTree = "<group>"; };
4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayName.swift; sourceTree = "<group>"; }; 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayName.swift; sourceTree = "<group>"; };
4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfileName.swift; sourceTree = "<group>"; }; 4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfileName.swift; sourceTree = "<group>"; };
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapView.swift; sourceTree = "<group>"; }; 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapView.swift; sourceTree = "<group>"; };
@@ -647,7 +616,6 @@
4CB883AD2976FA9300DC99E7 /* FormatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormatTests.swift; sourceTree = "<group>"; }; 4CB883AD2976FA9300DC99E7 /* FormatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormatTests.swift; sourceTree = "<group>"; };
4CB883AF297705DD00DC99E7 /* ZapButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapButton.swift; sourceTree = "<group>"; }; 4CB883AF297705DD00DC99E7 /* ZapButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapButton.swift; sourceTree = "<group>"; };
4CB883B5297730E400DC99E7 /* LNUrls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LNUrls.swift; sourceTree = "<group>"; }; 4CB883B5297730E400DC99E7 /* LNUrls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LNUrls.swift; sourceTree = "<group>"; };
4CB8FC222A41ABA500763C51 /* AboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNameView.swift; sourceTree = "<group>"; }; 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNameView.swift; sourceTree = "<group>"; };
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowsYou.swift; sourceTree = "<group>"; }; 4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowsYou.swift; sourceTree = "<group>"; };
4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteLink.swift; sourceTree = "<group>"; }; 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteLink.swift; sourceTree = "<group>"; };
@@ -662,7 +630,6 @@
4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; }; 4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; };
4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingEventView.swift; sourceTree = "<group>"; }; 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingEventView.swift; sourceTree = "<group>"; };
4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingProfileView.swift; sourceTree = "<group>"; }; 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingProfileView.swift; sourceTree = "<group>"; };
4CCF9AAE2A1FDBDB00E03CFB /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = "<group>"; };
4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadModel.swift; sourceTree = "<group>"; }; 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadModel.swift; sourceTree = "<group>"; };
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; }; 4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
4CDA128929E9D10C0006FA5A /* SignalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalView.swift; sourceTree = "<group>"; }; 4CDA128929E9D10C0006FA5A /* SignalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalView.swift; sourceTree = "<group>"; };
@@ -693,7 +660,7 @@
4CE8794729941DA700F758CC /* RelayFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilters.swift; sourceTree = "<group>"; }; 4CE8794729941DA700F758CC /* RelayFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilters.swift; sourceTree = "<group>"; };
4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayMetadatas.swift; sourceTree = "<group>"; }; 4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayMetadatas.swift; sourceTree = "<group>"; };
4CE8794D2996B16A00F758CC /* RelayToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayToggle.swift; sourceTree = "<group>"; }; 4CE8794D2996B16A00F758CC /* RelayToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayToggle.swift; sourceTree = "<group>"; };
4CE8794F2996B2BD00F758CC /* RelayStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayStatusView.swift; sourceTree = "<group>"; }; 4CE8794F2996B2BD00F758CC /* RelayStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayStatus.swift; sourceTree = "<group>"; };
4CE879512996B68900F758CC /* RelayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayType.swift; sourceTree = "<group>"; }; 4CE879512996B68900F758CC /* RelayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayType.swift; sourceTree = "<group>"; };
4CE879542996BAB900F758CC /* RelayPaidDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPaidDetail.swift; sourceTree = "<group>"; }; 4CE879542996BAB900F758CC /* RelayPaidDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPaidDetail.swift; sourceTree = "<group>"; };
4CE879572996C45300F758CC /* ZapsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapsView.swift; sourceTree = "<group>"; }; 4CE879572996C45300F758CC /* ZapsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapsView.swift; sourceTree = "<group>"; };
@@ -725,21 +692,13 @@
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; }; 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; };
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; }; 4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
50088DA029E8271A008A1FDF /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; }; 50088DA029E8271A008A1FDF /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; };
5019CADC2A0FB0A9000069E1 /* ProfileDatabaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDatabaseTests.swift; sourceTree = "<group>"; };
501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistedProfile.swift; sourceTree = "<group>"; };
501F8C5729FF5FC5001AFC1D /* Damus.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Damus.xcdatamodel; sourceTree = "<group>"; };
501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDatabase.swift; sourceTree = "<group>"; };
501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; }; 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; };
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; }; 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; }; 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; }; 50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; };
50DA11252A16A23F00236234 /* Launch.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Launch.storyboard; sourceTree = "<group>"; };
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLogoGradient.swift; sourceTree = "<group>"; };
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; }; 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; }; 5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; }; 5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.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>"; };
5CF72FC129B9142F00124A13 /* ShareAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAction.swift; sourceTree = "<group>"; }; 5CF72FC129B9142F00124A13 /* ShareAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAction.swift; sourceTree = "<group>"; };
6439E013296790CF0020672B /* ProfilePicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePicImageView.swift; sourceTree = "<group>"; }; 6439E013296790CF0020672B /* ProfilePicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePicImageView.swift; sourceTree = "<group>"; };
643EA5C7296B764E005081BB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; }; 643EA5C7296B764E005081BB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; };
@@ -754,9 +713,7 @@
9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachMediaUtility.swift; sourceTree = "<group>"; }; 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachMediaUtility.swift; sourceTree = "<group>"; };
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; }; BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; }; BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; }; DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = "<group>"; };
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; }; E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; }; E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; };
F757933929D7AECD007DEAC1 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; }; F757933929D7AECD007DEAC1 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; };
@@ -776,7 +733,6 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */, 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
4CCF9AB22A1FE80C00E03CFB /* GSPlayer in Frameworks */,
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */, 4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -878,7 +834,6 @@
4C0A3F8D280F63FF000448DE /* Models */ = { 4C0A3F8D280F63FF000448DE /* Models */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4C9AA1462A444422003F49FD /* Zaps */,
4C54AA0829A55416003E4487 /* Notifications */, 4C54AA0829A55416003E4487 /* Notifications */,
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */, 3AA247FC297E3CFF0090C62D /* RepostsModel.swift */,
4C0A3F8E280F640A000448DE /* ThreadModel.swift */, 4C0A3F8E280F640A000448DE /* ThreadModel.swift */,
@@ -920,7 +875,6 @@
4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */, 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */,
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */, 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */,
4C7D09772A0B0CC900943473 /* WalletModel.swift */, 4C7D09772A0B0CC900943473 /* WalletModel.swift */,
3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -952,20 +906,10 @@
4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */, 4C1A9A2029DDD3E100516EAC /* KeySettingsView.swift */,
4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */, 4C1A9A2429DDDF2600516EAC /* ZapSettingsView.swift */,
4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */, 4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */,
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */,
); );
path = Settings; path = Settings;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4C1A9A2829DDF53B00516EAC /* Video */ = {
isa = PBXGroup;
children = (
4C1A9A2929DDF54400516EAC /* DamusVideoPlayer.swift */,
4CCF9AAE2A1FDBDB00E03CFB /* VideoPlayer.swift */,
);
path = Video;
sourceTree = "<group>";
};
4C30AC7029A5676F00E2BD5A /* Notifications */ = { 4C30AC7029A5676F00E2BD5A /* Notifications */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -982,7 +926,6 @@
children = ( children = (
4C54AA0929A55429003E4487 /* EventGroup.swift */, 4C54AA0929A55429003E4487 /* EventGroup.swift */,
4C54AA0B29A5543C003E4487 /* ZapGroup.swift */, 4C54AA0B29A5543C003E4487 /* ZapGroup.swift */,
4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */,
); );
path = Notifications; path = Notifications;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -993,7 +936,6 @@
4C7D09692A0AEA0400943473 /* CodeScanner */, 4C7D09692A0AEA0400943473 /* CodeScanner */,
4C7D095A2A098C5C00943473 /* Wallet */, 4C7D095A2A098C5C00943473 /* Wallet */,
4C8D1A6D29F31E4100ACDF75 /* Buttons */, 4C8D1A6D29F31E4100ACDF75 /* Buttons */,
4C1A9A2829DDF53B00516EAC /* Video */,
4C1A9A1B29DDCF8B00516EAC /* Settings */, 4C1A9A1B29DDCF8B00516EAC /* Settings */,
4CFF8F6129CC9A80008DB934 /* Images */, 4CFF8F6129CC9A80008DB934 /* Images */,
4CCEB7AC29B53D180078AA28 /* Search */, 4CCEB7AC29B53D180078AA28 /* Search */,
@@ -1048,7 +990,6 @@
4CF0ABD529817F5B00D66079 /* ReportView.swift */, 4CF0ABD529817F5B00D66079 /* ReportView.swift */,
4CF0ABE42981EE0C00D66079 /* EULAView.swift */, 4CF0ABE42981EE0C00D66079 /* EULAView.swift */,
3AA247FE297E3D900090C62D /* RepostsView.swift */, 3AA247FE297E3D900090C62D /* RepostsView.swift */,
50DA11252A16A23F00236234 /* Launch.storyboard */,
5C513FCB2984ACA60072348F /* QRCodeView.swift */, 5C513FCB2984ACA60072348F /* QRCodeView.swift */,
643EA5C7296B764E005081BB /* RelayFilterView.swift */, 643EA5C7296B764E005081BB /* RelayFilterView.swift */,
); );
@@ -1058,7 +999,6 @@
4C75EFAB28049CC80006080F /* Nostr */ = { 4C75EFAB28049CC80006080F /* Nostr */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
501F8C5329FF5EE2001AFC1D /* CoreData */,
4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */, 4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */,
4C75EFA527FF87A20006080F /* Nostr.swift */, 4C75EFA527FF87A20006080F /* Nostr.swift */,
4C75EFAE28049D340006080F /* NostrFilter.swift */, 4C75EFAE28049D340006080F /* NostrFilter.swift */,
@@ -1069,7 +1009,6 @@
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */, 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */,
4CEE2AEC2805B22500AB5EEF /* NostrRequest.swift */, 4CEE2AEC2805B22500AB5EEF /* NostrRequest.swift */,
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */, 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */,
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */, 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
4C363A8F28247A1D006E126D /* NostrLink.swift */, 4C363A8F28247A1D006E126D /* NostrLink.swift */,
50088DA029E8271A008A1FDF /* WebSocket.swift */, 50088DA029E8271A008A1FDF /* WebSocket.swift */,
@@ -1102,9 +1041,6 @@
children = ( children = (
4C7D09712A0AEF5E00943473 /* DamusGradient.swift */, 4C7D09712A0AEF5E00943473 /* DamusGradient.swift */,
4C7D09732A0AEF9000943473 /* AlbyGradient.swift */, 4C7D09732A0AEF9000943473 /* AlbyGradient.swift */,
4C2859612A12A7F0004746F7 /* GoldSupportGradient.swift */,
5C6E1DAE2A194075008FC15A /* PinkGradient.swift */,
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */,
); );
path = Gradients; path = Gradients;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1156,8 +1092,6 @@
4CA5588229F33F5B00DC6A45 /* StringCodable.swift */, 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */,
50B5685229F97CB400A23243 /* CredentialHandler.swift */, 50B5685229F97CB400A23243 /* CredentialHandler.swift */,
4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */, 4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */,
3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */,
D2277EE92A089BD5006C3807 /* Router.swift */,
); );
path = Util; path = Util;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1171,14 +1105,6 @@
path = Buttons; path = Buttons;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4C9AA1462A444422003F49FD /* Zaps */ = {
isa = PBXGroup;
children = (
4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */,
);
path = Zaps;
sourceTree = "<group>";
};
4CAAD8AE29888A9B00060CEA /* Relays */ = { 4CAAD8AE29888A9B00060CEA /* Relays */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -1188,7 +1114,7 @@
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */, 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */,
F7908E91298B0F0700AB113A /* RelayDetailView.swift */, F7908E91298B0F0700AB113A /* RelayDetailView.swift */,
4CE8794D2996B16A00F758CC /* RelayToggle.swift */, 4CE8794D2996B16A00F758CC /* RelayToggle.swift */,
4CE8794F2996B2BD00F758CC /* RelayStatusView.swift */, 4CE8794F2996B2BD00F758CC /* RelayStatus.swift */,
4CE879512996B68900F758CC /* RelayType.swift */, 4CE879512996B68900F758CC /* RelayType.swift */,
4CDA128929E9D10C0006FA5A /* SignalView.swift */, 4CDA128929E9D10C0006FA5A /* SignalView.swift */,
); );
@@ -1219,7 +1145,6 @@
4CB9D4A52992D01900A9A7E4 /* Profile */ = { 4CB9D4A52992D01900A9A7E4 /* Profile */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4CB8FC222A41ABA500763C51 /* AboutView.swift */,
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */, 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */, 4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */, F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */,
@@ -1231,7 +1156,6 @@
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */, 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */,
4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */, 4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */,
4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */, 4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */,
3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */,
); );
path = Profile; path = Profile;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1294,8 +1218,6 @@
4CE4F0F729DB7399005914DB /* ThiccDivider.swift */, 4CE4F0F729DB7399005914DB /* ThiccDivider.swift */,
4C1A9A2229DDDB8100516EAC /* IconLabel.swift */, 4C1A9A2229DDDB8100516EAC /* IconLabel.swift */,
4C8D00C929DF80350036AF10 /* TruncatedText.swift */, 4C8D00C929DF80350036AF10 /* TruncatedText.swift */,
4C28595F2A12A2BE004746F7 /* SupporterBadge.swift */,
5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1310,9 +1232,7 @@
4CE6DEE427F7A08100C66700 /* Products */, 4CE6DEE427F7A08100C66700 /* Products */,
4CEE2AE62804F57B00AB5EEF /* Frameworks */, 4CEE2AE62804F57B00AB5EEF /* Frameworks */,
); );
indentWidth = 4;
sourceTree = "<group>"; sourceTree = "<group>";
tabWidth = 4;
}; };
4CE6DEE427F7A08100C66700 /* Products */ = { 4CE6DEE427F7A08100C66700 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
@@ -1373,7 +1293,6 @@
4CB883AD2976FA9300DC99E7 /* FormatTests.swift */, 4CB883AD2976FA9300DC99E7 /* FormatTests.swift */,
3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */, 3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */,
3A3040EE29A8FEE9008A0F29 /* EventDetailBarTests.swift */, 3A3040EE29A8FEE9008A0F29 /* EventDetailBarTests.swift */,
5019CADC2A0FB0A9000069E1 /* ProfileDatabaseTests.swift */,
3A3040F229A91366008A0F29 /* ProfileViewTests.swift */, 3A3040F229A91366008A0F29 /* ProfileViewTests.swift */,
3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */, 3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */,
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */, 4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */,
@@ -1416,7 +1335,6 @@
4CE879572996C45300F758CC /* ZapsView.swift */, 4CE879572996C45300F758CC /* ZapsView.swift */,
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */, 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */,
4CA3FA0F29F593D000FDB3C3 /* ZapTypePicker.swift */, 4CA3FA0F29F593D000FDB3C3 /* ZapTypePicker.swift */,
4C73C5132A4437C10062CAC0 /* ZapUserView.swift */,
); );
path = Zaps; path = Zaps;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1466,15 +1384,6 @@
path = Images; path = Images;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
501F8C5329FF5EE2001AFC1D /* CoreData */ = {
isa = PBXGroup;
children = (
501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */,
501F8C5629FF5FC5001AFC1D /* Damus.xcdatamodeld */,
);
path = CoreData;
sourceTree = "<group>";
};
7C0F392D29B57C8F0039859C /* Extensions */ = { 7C0F392D29B57C8F0039859C /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -1519,7 +1428,6 @@
packageProductDependencies = ( packageProductDependencies = (
4C649880286E0EE300EAE2B3 /* secp256k1 */, 4C649880286E0EE300EAE2B3 /* secp256k1 */,
4C06670328FC7EC500038D2A /* Kingfisher */, 4C06670328FC7EC500038D2A /* Kingfisher */,
4CCF9AB12A1FE80C00E03CFB /* GSPlayer */,
); );
productName = damus; productName = damus;
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */; productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
@@ -1624,7 +1532,6 @@
packageReferences = ( packageReferences = (
4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */, 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */,
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */, 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */,
4CCF9AB02A1FE80B00E03CFB /* XCRemoteSwiftPackageReference "GSPlayer" */,
); );
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */; productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
projectDirPath = ""; projectDirPath = "";
@@ -1645,7 +1552,6 @@
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */, 3ACB685F297633BC00C46468 /* Localizable.strings in Resources */,
4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */, 4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */,
3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */, 3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */,
50DA11262A16A23F00236234 /* Launch.storyboard in Resources */,
4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */, 4CE6DEEB27F7A08200C66700 /* Assets.xcassets in Resources */,
4C198DF129F88C6B004C165C /* License.txt in Resources */, 4C198DF129F88C6B004C165C /* License.txt in Resources */,
4C198DF029F88C6B004C165C /* Readme.md in Resources */, 4C198DF029F88C6B004C165C /* Readme.md in Resources */,
@@ -1682,7 +1588,6 @@
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */, 4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
4C216F34286F5ACD00040376 /* DMView.swift in Sources */, 4C216F34286F5ACD00040376 /* DMView.swift in Sources */,
4C3EA64428FF558100C48A62 /* sha256.c in Sources */, 4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
4CCF9AAF2A1FDBDB00E03CFB /* VideoPlayer.swift in Sources */,
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */, 4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */,
4C363AA828297703006E126D /* InsertSort.swift in Sources */, 4C363AA828297703006E126D /* InsertSort.swift in Sources */,
4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */, 4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */,
@@ -1701,7 +1606,6 @@
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */, 4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */, 4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */, 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */,
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */, 4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
4C363AA228296A7E006E126D /* SearchView.swift in Sources */, 4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */, 4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
@@ -1760,7 +1664,6 @@
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */, 4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */,
4C7D096E2A0AEA0400943473 /* ScannerCoordinator.swift in Sources */, 4C7D096E2A0AEA0400943473 /* ScannerCoordinator.swift in Sources */,
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */, 4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
501F8C5A29FF70F5001AFC1D /* ProfileDatabase.swift in Sources */,
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */, 4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */, 9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */, 4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */,
@@ -1786,7 +1689,6 @@
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */, 4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */, 4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */,
4C9AA1482A44442E003F49FD /* CustomizeZapModel.swift in Sources */,
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */, 7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */,
4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */, 4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */,
@@ -1826,11 +1728,8 @@
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */, 4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
4C363A8828236948006E126D /* BlocksView.swift in Sources */, 4C363A8828236948006E126D /* BlocksView.swift in Sources */,
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */, 4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */,
F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */, F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */,
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */, 4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */,
4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */,
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */, 4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */, 4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */, 4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */,
@@ -1839,7 +1738,6 @@
4C363A9C282838B9006E126D /* EventRef.swift in Sources */, 4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
4C7D095F2A098C5D00943473 /* ConnectWalletView.swift in Sources */, 4C7D095F2A098C5D00943473 /* ConnectWalletView.swift in Sources */,
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */, 3AA24802297E3DC20090C62D /* RepostView.swift in Sources */,
5C6E1DAF2A194075008FC15A /* PinkGradient.swift in Sources */,
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */, 4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */,
4C3EA66528FF5F6800C48A62 /* mem.c in Sources */, 4C3EA66528FF5F6800C48A62 /* mem.c in Sources */,
4C198DEF29F88C6B004C165C /* BlurHashEncode.swift in Sources */, 4C198DEF29F88C6B004C165C /* BlurHashEncode.swift in Sources */,
@@ -1855,18 +1753,13 @@
4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */, 4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */,
4CFF8F6B29CD0079008DB934 /* RepostedEvent.swift in Sources */, 4CFF8F6B29CD0079008DB934 /* RepostedEvent.swift in Sources */,
4C8682872814DE470026224F /* ProfileView.swift in Sources */, 4C8682872814DE470026224F /* ProfileView.swift in Sources */,
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */,
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */, 4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */, 4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */,
4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */, 4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */,
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */, 4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */, 4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
501F8C5829FF5FC5001AFC1D /* Damus.xcdatamodeld in Sources */,
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */, 4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */, 4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */,
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */,
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */, 4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
4CE1399429F0669900AC6A0B /* BigButton.swift in Sources */, 4CE1399429F0669900AC6A0B /* BigButton.swift in Sources */,
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */, 7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
@@ -1914,13 +1807,11 @@
4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */, 4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */,
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */, 4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */,
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */, E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */,
4CB8FC232A41ABA800763C51 /* AboutView.swift in Sources */,
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */, 5C513FBA297F72980072348F /* CustomPicker.swift in Sources */,
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */, 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */, F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */,
4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */, 4C3A1D332960DB0500558C0F /* Markdown.swift in Sources */,
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */, 4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */,
501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */,
4CF0ABD42980996B00D66079 /* Report.swift in Sources */, 4CF0ABD42980996B00D66079 /* Report.swift in Sources */,
4C06670B28FDE64700038D2A /* damus.c in Sources */, 4C06670B28FDE64700038D2A /* damus.c in Sources */,
4C1A9A2529DDDF2600516EAC /* ZapSettingsView.swift in Sources */, 4C1A9A2529DDDF2600516EAC /* ZapSettingsView.swift in Sources */,
@@ -1929,27 +1820,23 @@
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */, 4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */,
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */, 3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */,
3AE45AF6297BB2E700C1D842 /* LibreTranslateServer.swift in Sources */, 3AE45AF6297BB2E700C1D842 /* LibreTranslateServer.swift in Sources */,
4CE879502996B2BD00F758CC /* RelayStatusView.swift in Sources */, 4CE879502996B2BD00F758CC /* RelayStatus.swift in Sources */,
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */, 4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */,
4C75EFA427FA577B0006080F /* PostView.swift in Sources */, 4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */, 4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */, 4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */,
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */,
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */, 501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */,
4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */, 4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */,
4C75EFB528049D790006080F /* Relay.swift in Sources */, 4C75EFB528049D790006080F /* Relay.swift in Sources */,
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */, 4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */, 4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */,
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */, 4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */,
4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */, 4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */,
4C1A9A1F29DDD24B00516EAC /* AppearanceSettingsView.swift in Sources */, 4C1A9A1F29DDD24B00516EAC /* AppearanceSettingsView.swift in Sources */,
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */, 3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */,
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */, 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */,
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */,
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */, 4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */, 4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */,
4C2859622A12A7F0004746F7 /* GoldSupportGradient.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -1957,7 +1844,6 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5019CADD2A0FB0A9000069E1 /* ProfileDatabaseTests.swift in Sources */,
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */, 3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */,
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */, 4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */,
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */, 3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */,
@@ -2242,7 +2128,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 8; CURRENT_PROJECT_VERSION = 24;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -2257,10 +2143,8 @@
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UILaunchStoryboardName = Launch.storyboard;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -2291,7 +2175,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 8; CURRENT_PROJECT_VERSION = 24;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -2306,10 +2190,8 @@
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UILaunchStoryboardName = Launch.storyboard;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@@ -2340,7 +2222,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0; IPHONEOS_DEPLOYMENT_TARGET = 15.3;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damusTests; PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damusTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -2360,7 +2242,7 @@
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0; IPHONEOS_DEPLOYMENT_TARGET = 15.3;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damusTests; PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damusTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -2465,14 +2347,6 @@
revision = 40b4b38b3b1c83f7088c76189a742870e0ca06a9; revision = 40b4b38b3b1c83f7088c76189a742870e0ca06a9;
}; };
}; };
4CCF9AB02A1FE80B00E03CFB /* XCRemoteSwiftPackageReference "GSPlayer" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/wxxsw/GSPlayer";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.2.26;
};
};
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@@ -2486,25 +2360,7 @@
package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */; package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */;
productName = secp256k1; productName = secp256k1;
}; };
4CCF9AB12A1FE80C00E03CFB /* GSPlayer */ = {
isa = XCSwiftPackageProductDependency;
package = 4CCF9AB02A1FE80B00E03CFB /* XCRemoteSwiftPackageReference "GSPlayer" */;
productName = GSPlayer;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */
501F8C5629FF5FC5001AFC1D /* Damus.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
501F8C5729FF5FC5001AFC1D /* Damus.xcdatamodel */,
);
currentVersion = 501F8C5729FF5FC5001AFC1D /* Damus.xcdatamodel */;
path = Damus.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
}; };
rootObject = 4CE6DEDB27F7A08100C66700 /* Project object */; rootObject = 4CE6DEDB27F7A08100C66700 /* Project object */;
} }
@@ -1,14 +1,5 @@
{ {
"pins" : [ "pins" : [
{
"identity" : "gsplayer",
"kind" : "remoteSourceControl",
"location" : "https://github.com/wxxsw/GSPlayer",
"state" : {
"revision" : "aa6dad7943d52f5207f7fcc2ad3e4274583443b8",
"version" : "0.2.26"
}
},
{ {
"identity" : "kingfisher", "identity" : "kingfisher",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
-28
View File
@@ -1,28 +0,0 @@
<svg width="430" height="813" viewBox="0 0 430 813" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_f_1069_29012)">
<path d="M44.0811 256.851L186.315 111L276.203 223.574L244.02 388.295L69.9751 697.084L100.678 498.338L44.0811 256.851Z" fill="url(#paint0_linear_1069_29012)"/>
</g>
<g filter="url(#filter1_f_1069_29012)">
<path d="M116.509 587.348L206.677 479.401L230.746 273.265L266.666 231.183L367.424 396.975L281.292 659.008L266.665 801.413L66.889 763.694L116.509 587.348Z" fill="url(#paint1_linear_1069_29012)"/>
</g>
<defs>
<filter id="filter0_f_1069_29012" x="-66.6248" y="0.294121" width="453.534" height="807.496" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="55.3529" result="effect1_foregroundBlur_1069_29012"/>
</filter>
<filter id="filter1_f_1069_29012" x="-43.8172" y="120.477" width="521.947" height="791.642" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="55.3529" result="effect1_foregroundBlur_1069_29012"/>
</filter>
<linearGradient id="paint0_linear_1069_29012" x1="230.179" y1="166.577" x2="-67.7956" y2="310.108" gradientUnits="userSpaceOnUse">
<stop stop-color="#D34CD9"/>
<stop offset="1" stop-color="#4E4DF4"/>
</linearGradient>
<linearGradient id="paint1_linear_1069_29012" x1="139.483" y1="462.902" x2="377.854" y2="565.47" gradientUnits="userSpaceOnUse">
<stop stop-color="#0DE8FF"/>
<stop offset="1" stop-color="#641AAE"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

-12
View File
@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "gradient.jpg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

-14
View File
@@ -1,14 +0,0 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.2" width="48" height="48" rx="24" fill="url(#paint0_linear_1843_42349)"/>
<path d="M19.9993 36.0001H27.9993M28.8095 28.0001C31.5199 26.3669 33.3327 23.3952 33.3327 20.0001C33.3327 14.8454 29.154 10.6667 23.9993 10.6667C18.8447 10.6667 14.666 14.8454 14.666 20.0001C14.666 23.3952 16.4788 26.3669 19.1892 28.0001M28.8095 28.0001C28.5475 28.1579 28.2772 28.3032 27.9993 28.4352V31.3334C27.9993 31.7016 27.7009 32.0001 27.3327 32.0001H20.666C20.2978 32.0001 19.9993 31.7016 19.9993 31.3334V28.4352C19.7215 28.3032 19.4512 28.1579 19.1892 28.0001M28.8095 28.0001H19.1892" stroke="url(#paint1_linear_1843_42349)" stroke-width="2.66667" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_1843_42349" x1="5.41935" y1="0.774194" x2="37.9355" y2="47.2258" gradientUnits="userSpaceOnUse">
<stop stop-color="#F9AD1C"/>
<stop offset="1" stop-color="#DF7E0C"/>
</linearGradient>
<linearGradient id="paint1_linear_1843_42349" x1="16.7735" y1="11.0754" x2="35.0141" y2="30.2759" gradientUnits="userSpaceOnUse">
<stop stop-color="#F9AD1C"/>
<stop offset="1" stop-color="#DF7E0C"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "header.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 34 KiB

-12
View File
@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "nostr-logo.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

@@ -1,12 +1,15 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "eula-bg.svg", "filename" : "shaka-full.pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],
"info" : { "info" : {
"author" : "xcode", "author" : "xcode",
"version" : 1 "version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
} }
} }
@@ -0,0 +1,88 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 -0.073975 cm
1.000000 1.000000 1.000000 scn
1.295334 8.661732 m
3.613694 8.367855 l
4.475733 8.733568 5.268113 9.771931 5.474915 10.327032 c
6.083156 11.959681 5.507567 14.604573 5.474915 15.061715 c
5.448792 15.427428 6.008246 15.693006 6.291239 15.780080 c
7.571236 15.858447 8.508359 14.876789 8.642253 13.984165 c
8.740212 13.331103 8.576948 11.752880 8.381030 10.849482 c
8.979668 10.936556 10.980525 10.901726 11.868687 10.849482 c
12.756847 10.797236 13.474895 10.196423 14.193260 9.412750 c
14.767952 8.237244 13.953805 7.725680 13.474895 7.616838 c
13.834077 7.257654 l
14.781013 5.918882 13.649043 5.178749 13.115711 5.004600 c
13.474895 4.743376 l
14.487136 3.763786 13.246323 2.751544 13.017752 2.882155 c
11.058574 3.176033 l
15.499378 1.673996 l
16.054478 0.400530 15.074889 0.073999 14.781013 0.073999 c
8.576947 1.673996 l
6.291239 1.673996 5.311650 1.869914 4.299407 2.163791 c
4.157911 2.131138 3.659409 1.987464 2.797370 1.673996 c
1.935332 1.360527 1.219143 2.087601 0.968804 2.490320 c
-0.285071 4.083785 -0.467927 7.257655 1.295334 8.661732 c
h
f
n
Q
endstream
endobj
3 0 obj
1149
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 15.666626 15.710510 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001239 00000 n
0000001262 00000 n
0000001435 00000 n
0000001509 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1568
%%EOF
@@ -1,12 +1,15 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "lightbulb.svg", "filename" : "shaka-line.pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],
"info" : { "info" : {
"author" : "xcode", "author" : "xcode",
"version" : 1 "version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
} }
} }
+323
View File
@@ -0,0 +1,323 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.474731 -0.563965 cm
1.000000 1.000000 1.000000 scn
3.613694 9.332577 m
3.553993 8.861599 l
3.637261 8.851044 3.721838 8.862753 3.799107 8.895533 c
3.613694 9.332577 l
h
1.295334 9.626453 m
1.355035 10.097433 l
1.227973 10.113539 1.099794 10.077623 0.999601 9.997839 c
1.295334 9.626453 l
h
0.968804 3.455042 m
1.372000 3.705677 l
1.362764 3.720535 1.352713 3.734872 1.341894 3.748621 c
0.968804 3.455042 l
h
4.299407 3.128512 m
4.431771 3.584435 l
4.353942 3.607030 4.271623 3.609325 4.192656 3.591103 c
4.299407 3.128512 l
h
8.576947 2.638718 m
8.695503 3.098424 l
8.656776 3.108411 8.616942 3.113465 8.576947 3.113465 c
8.576947 2.638718 l
h
14.781013 1.038721 m
14.662457 0.579016 l
14.701184 0.569027 14.741018 0.563974 14.781013 0.563974 c
14.781013 1.038721 l
h
15.499378 2.638718 m
15.934578 2.828420 l
15.881091 2.951125 15.778289 3.045548 15.651489 3.088437 c
15.499378 2.638718 l
h
11.058574 4.140755 m
11.128998 4.610250 l
10.885809 4.646729 10.655017 4.491467 10.597156 4.252461 c
10.539293 4.013455 10.673516 3.769826 10.906463 3.691035 c
11.058574 4.140755 l
h
13.017752 3.846877 m
13.253292 4.259073 l
13.202273 4.288227 13.146286 4.307655 13.088176 4.316372 c
13.017752 3.846877 l
h
13.474895 5.708097 m
13.805044 6.049252 l
13.789093 6.064689 13.772079 6.078987 13.754128 6.092043 c
13.474895 5.708097 l
h
13.115711 5.969321 m
12.968349 6.420619 l
12.798800 6.365256 12.674588 6.219535 12.646772 6.043359 c
12.618958 5.867183 12.692234 5.690281 12.836478 5.585376 c
13.115711 5.969321 l
h
13.834077 8.222376 m
14.221668 8.496526 l
14.206144 8.518474 14.188784 8.539063 14.169774 8.558073 c
13.834077 8.222376 l
h
13.474895 8.581559 m
13.369680 9.044500 l
13.201114 9.006190 13.066693 8.879284 13.018762 8.713197 c
12.970830 8.547110 13.016963 8.368095 13.139197 8.245862 c
13.474895 8.581559 l
h
14.193260 10.377472 m
14.619765 10.585986 l
14.599768 10.626891 14.573989 10.664707 14.543221 10.698271 c
14.193260 10.377472 l
h
8.381030 11.814203 m
7.917068 11.914822 l
7.884080 11.762714 7.927746 11.604099 8.033934 11.490305 c
8.140121 11.376513 8.295343 11.321997 8.449365 11.344399 c
8.381030 11.814203 l
h
8.642253 14.948887 m
9.111748 15.019311 l
8.642253 14.948887 l
h
6.291239 16.744801 m
6.262227 17.218662 l
6.224693 17.216364 6.187564 17.209614 6.151623 17.198555 c
6.291239 16.744801 l
h
5.474915 16.026436 m
5.948456 16.060261 l
5.474915 16.026436 l
h
5.474915 11.291754 m
5.030037 11.457493 l
5.474915 11.291754 l
h
3.673396 9.803555 m
1.355035 10.097433 l
1.235632 9.155476 l
3.553993 8.861599 l
3.673396 9.803555 l
h
0.999601 9.997839 m
-0.029049 9.178730 -0.454726 7.875908 -0.474048 6.619066 c
-0.493367 5.362488 -0.110331 4.058727 0.595713 3.161463 c
1.341894 3.748621 l
0.794064 4.444821 0.458734 5.524729 0.475334 6.604470 c
0.491930 7.683949 0.856455 8.670100 1.591066 9.255068 c
0.999601 9.997839 l
h
0.565608 3.204407 m
0.721970 2.952868 1.013515 2.611341 1.407507 2.372385 c
1.811404 2.127421 2.357187 1.973489 2.959612 2.192553 c
2.635129 3.084882 l
2.375515 2.990478 2.132184 3.043347 1.899893 3.184233 c
1.657696 3.331126 1.465977 3.554496 1.372000 3.705677 c
0.565608 3.204407 l
h
2.959612 2.192553 m
3.816493 2.504146 4.293336 2.639887 4.406158 2.665923 c
4.192656 3.591103 l
4.022485 3.551832 3.502325 3.400227 2.635129 3.084882 c
2.959612 2.192553 l
h
4.167043 2.672591 m
5.229115 2.364247 6.254152 2.163970 8.576947 2.163970 c
8.576947 3.113465 l
6.328326 3.113465 5.394184 3.305025 4.431771 3.584435 c
4.167043 2.672591 l
h
8.458392 2.179011 m
14.662457 0.579016 l
14.899569 1.498427 l
8.695503 3.098424 l
8.458392 2.179011 l
h
14.781013 0.563974 m
15.036198 0.563974 15.495326 0.684875 15.814721 1.047266 c
16.180891 1.462728 16.264221 2.072176 15.934578 2.828420 c
15.064179 2.449016 l
15.289635 1.931793 15.160722 1.741243 15.102402 1.675073 c
15.055794 1.622190 14.990156 1.579316 14.916806 1.549556 c
14.881134 1.535082 14.847747 1.525430 14.820526 1.519657 c
14.791491 1.513498 14.777695 1.513469 14.781013 1.513469 c
14.781013 0.563974 l
h
15.651489 3.088437 m
11.210685 4.590474 l
10.906463 3.691035 l
15.347267 2.188998 l
15.651489 3.088437 l
h
10.988150 3.671260 m
12.947328 3.377382 l
13.088176 4.316372 l
11.128998 4.610250 l
10.988150 3.671260 l
h
12.782211 3.434681 m
12.991495 3.315090 13.204453 3.370091 13.288217 3.396689 c
13.400116 3.432221 13.506123 3.490767 13.598186 3.554502 c
13.783985 3.683133 13.977411 3.877748 14.120350 4.119644 c
14.264680 4.363894 14.369576 4.678114 14.335162 5.031647 c
14.300108 5.391746 14.125634 5.739002 13.805044 6.049252 c
13.144745 5.366943 l
13.330275 5.187398 13.380290 5.040778 13.390134 4.939653 c
13.400617 4.831963 13.370820 4.717613 13.302905 4.602680 c
13.233600 4.485394 13.137231 4.390213 13.057724 4.335170 c
13.017135 4.307070 12.996612 4.300308 13.000857 4.301657 c
13.003194 4.302399 13.024761 4.309311 13.061064 4.310122 c
13.095938 4.310902 13.170414 4.306433 13.253292 4.259073 c
12.782211 3.434681 l
h
13.754128 6.092043 m
13.394944 6.353267 l
12.836478 5.585376 l
13.195662 5.324152 l
13.754128 6.092043 l
h
13.263074 5.518023 m
13.593105 5.625790 14.123367 5.907292 14.433812 6.409482 c
14.595931 6.671733 14.696482 6.993351 14.669847 7.364054 c
14.643518 7.730516 14.495621 8.109214 14.221668 8.496526 c
13.446486 7.948226 l
13.646002 7.666152 13.711709 7.450294 13.722795 7.296009 c
13.733575 7.145966 13.695351 7.020646 13.626177 6.908748 c
13.474038 6.662641 13.171650 6.487002 12.968349 6.420619 c
13.263074 5.518023 l
h
14.169774 8.558073 m
13.810592 8.917255 l
13.139197 8.245862 l
13.498380 7.886679 l
14.169774 8.558073 l
h
13.580109 8.118617 m
13.896242 8.190466 14.344993 8.395787 14.624650 8.816864 c
14.929440 9.275781 14.963785 9.882310 14.619765 10.585986 c
13.766754 10.168959 l
13.997427 9.697128 13.912044 9.460121 13.833706 9.342171 c
13.730235 9.186377 13.532457 9.081495 13.369680 9.044500 c
13.580109 8.118617 l
h
14.543221 10.698271 m
13.820906 11.486253 12.989320 12.223852 11.896564 12.288132 c
11.840808 11.340275 l
12.524374 11.300065 13.128883 10.836036 13.843298 10.056674 c
14.543221 10.698271 l
h
11.896564 12.288132 m
11.441970 12.314873 10.711069 12.336796 10.019300 12.341186 c
9.341933 12.345484 8.654247 12.333687 8.312695 12.284006 c
8.449365 11.344399 l
8.706450 11.381794 9.318512 11.396118 10.013274 11.391710 c
10.693633 11.387392 11.407242 11.365778 11.840808 11.340275 c
11.896564 12.288132 l
h
8.844993 11.713585 m
8.948084 12.188952 9.040332 12.829445 9.094679 13.432834 c
9.147870 14.023395 9.169946 14.631327 9.111748 15.019311 c
8.172758 14.878462 l
8.212520 14.613384 8.201942 14.105675 8.149012 13.518009 c
8.097237 12.943172 8.009893 12.342852 7.917068 11.914822 c
8.844993 11.713585 l
h
9.111748 15.019311 m
8.944062 16.137217 7.805658 17.313158 6.262227 17.218662 c
6.320251 16.270941 l
7.336813 16.333179 8.072657 15.545805 8.172758 14.878462 c
9.111748 15.019311 l
h
6.151623 17.198555 m
5.976391 17.144638 5.715709 17.036982 5.490986 16.876261 c
5.292936 16.734617 4.969444 16.439627 5.001374 15.992612 c
5.948456 16.060261 l
5.951383 16.019283 5.934667 15.999795 5.943361 16.012491 c
5.954769 16.029152 5.984430 16.061831 6.043331 16.103956 c
6.162553 16.189222 6.323094 16.257891 6.430855 16.291048 c
6.151623 17.198555 l
h
5.001374 15.992612 m
5.011176 15.855374 5.059216 15.566318 5.104405 15.255149 c
5.152757 14.922197 5.207128 14.509316 5.241940 14.062993 c
5.312967 13.152368 5.295928 12.171200 5.030037 11.457493 c
5.919792 11.126015 l
6.262142 12.044956 6.261431 13.202559 6.188560 14.136827 c
6.151423 14.612950 6.093790 15.049047 6.044043 15.391605 c
5.991133 15.755945 5.954979 15.968927 5.948456 16.060261 c
5.001374 15.992612 l
h
5.030037 11.457493 m
4.953650 11.252455 4.742510 10.903708 4.434547 10.555828 c
4.127778 10.209298 3.769400 9.914337 3.428282 9.769621 c
3.799107 8.895533 l
4.320028 9.116529 4.788858 9.523607 5.145489 9.926461 c
5.500926 10.327968 5.789377 10.775953 5.919792 11.126015 c
5.030037 11.457493 l
h
f
n
Q
endstream
endobj
3 0 obj
7995
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 16.615845 16.660034 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000008085 00000 n
0000008108 00000 n
0000008281 00000 n
0000008355 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
8414
%%EOF
@@ -1,47 +0,0 @@
//
// GradientButtonStyle.swift
// damus
//
// Created by eric on 5/20/23.
//
import SwiftUI
struct GradientButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
return configuration.label
.padding()
.foregroundColor(Color.white)
.background {
RoundedRectangle(cornerRadius: 12)
.fill(PinkGradient.gradient)
}
.scaleEffect(configuration.isPressed ? 0.8 : 1)
}
}
struct GradientButtonStyle_Previews: PreviewProvider {
static var previews: some View {
VStack {
Button(action: {
print("dynamic size")
}) {
Text(verbatim: "Dynamic Size")
}
.buttonStyle(GradientButtonStyle())
Button(action: {
print("infinite width")
}) {
HStack {
Text(verbatim: "Infinite Width")
}
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
}
.buttonStyle(GradientButtonStyle())
.padding()
}
}
}
@@ -14,13 +14,9 @@ fileprivate let damus_grad = [damus_grad_c1, damus_grad_c2, damus_grad_c3]
struct DamusGradient: View { struct DamusGradient: View {
var body: some View { var body: some View {
DamusGradient.gradient LinearGradient(colors: damus_grad, startPoint: .bottomLeading, endPoint: .topTrailing)
.edgesIgnoringSafeArea([.top,.bottom]) .edgesIgnoringSafeArea([.top,.bottom])
} }
static var gradient: LinearGradient {
LinearGradient(colors: damus_grad, startPoint: .bottomLeading, endPoint: .topTrailing)
}
} }
struct DamusGradient_Previews: PreviewProvider { struct DamusGradient_Previews: PreviewProvider {
@@ -1,29 +0,0 @@
//
// DamusLogoGradient.swift
// damus
//
// Created by eric on 5/24/23.
//
import SwiftUI
fileprivate let damus_logo_grad_c1 = hex_col(r: 0x30, g: 0xb3, b: 0xf1)
fileprivate let damus_logo_grad_c2 = hex_col(r: 0xc5, g: 0x39, b: 0xf9)
fileprivate let damus_logo_grad = [damus_logo_grad_c1, damus_logo_grad_c2]
struct DamusLogoGradient: View {
var body: some View {
DamusLogoGradient.gradient
.edgesIgnoringSafeArea([.top,.bottom])
}
static var gradient: LinearGradient {
LinearGradient(colors: damus_logo_grad, startPoint: .leading, endPoint: .trailing)
}
}
struct DamusLogoGradient_Previews: PreviewProvider {
static var previews: some View {
DamusLogoGradient()
}
}
@@ -1,29 +0,0 @@
//
// GoldSupportGradient.swift
// damus
//
// Created by William Casarin on 2023-05-15.
//
import SwiftUI
fileprivate let gold_grad_c1 = hex_col(r: 226, g: 168, b: 0)
fileprivate let gold_grad_c2 = hex_col(r: 249, g: 243, b: 100)
fileprivate let gold_grad = [gold_grad_c2, gold_grad_c1]
let GoldGradient: LinearGradient =
LinearGradient(colors: gold_grad, startPoint: .bottomLeading, endPoint: .topTrailing)
struct GoldGradientView: View {
var body: some View {
GoldGradient
.edgesIgnoringSafeArea([.top,.bottom])
}
}
struct GoldGradientView_Previews: PreviewProvider {
static var previews: some View {
GoldGradientView()
}
}
@@ -1,30 +0,0 @@
//
// PinkGradient.swift
// damus
//
// Created by eric on 5/20/23.
//
import SwiftUI
fileprivate let damus_grad_c1 = hex_col(r: 0xd3, g: 0x4c, b: 0xd9)
fileprivate let damus_grad_c2 = hex_col(r: 0xf8, g: 0x69, b: 0xb6)
fileprivate let pink_grad = [damus_grad_c1, damus_grad_c2]
struct PinkGradient: View {
var body: some View {
PinkGradient.gradient
.edgesIgnoringSafeArea([.top,.bottom])
}
static var gradient: LinearGradient {
LinearGradient(colors: pink_grad, startPoint: .topTrailing, endPoint: .bottom)
}
}
struct PinkGradient_Previews: PreviewProvider {
static var previews: some View {
PinkGradient()
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ struct IconLabel: View {
var body: some View { var body: some View {
HStack(spacing: 0) { HStack(spacing: 0) {
Image(img_name) Image(systemName: img_name)
.foregroundColor(img_color) .foregroundColor(img_color)
.frame(width: 20) .frame(width: 20)
.padding([.trailing], 20) .padding([.trailing], 20)
+49 -137
View File
@@ -52,10 +52,8 @@ enum ImageShape {
} }
} }
// MARK: - Image Carousel
@MainActor
struct ImageCarousel: View { struct ImageCarousel: View {
var urls: [MediaUrl] var urls: [URL]
let evid: String let evid: String
@@ -65,18 +63,13 @@ struct ImageCarousel: View {
@State private var current_url: URL? = nil @State private var current_url: URL? = nil
@State private var image_fill: ImageFill? = nil @State private var image_fill: ImageFill? = nil
@State private var fillHeight: CGFloat = 350 let fillHeight: CGFloat = 350
@State private var maxHeight: CGFloat = UIScreen.main.bounds.height * 1.2 // 1.2 let maxHeight: CGFloat = UIScreen.main.bounds.height * 1.2
@State private var firstImageHeight: CGFloat? = nil
@State private var currentImageHeight: CGFloat?
@State private var selectedIndex = 0
@State private var video_size: CGSize? = nil
init(state: DamusState, evid: String, urls: [MediaUrl]) { init(state: DamusState, evid: String, urls: [URL]) {
_open_sheet = State(initialValue: false) _open_sheet = State(initialValue: false)
_current_url = State(initialValue: nil) _current_url = State(initialValue: nil)
let media_model = state.events.get_cache_data(evid).media_metadata_model _image_fill = State(initialValue: state.previews.lookup_image_meta(evid))
_image_fill = State(initialValue: media_model.fill)
self.urls = urls self.urls = urls
self.evid = evid self.evid = evid
self.state = state self.state = state
@@ -87,154 +80,75 @@ struct ImageCarousel: View {
} }
var height: CGFloat { var height: CGFloat {
firstImageHeight ?? image_fill?.height ?? fillHeight image_fill?.height ?? fillHeight
} }
func Placeholder(url: URL, geo_size: CGSize, num_urls: Int) -> some View { func Placeholder(url: URL, geo_size: CGSize) -> some View {
Group { Group {
if num_urls > 1 { if let meta = state.events.lookup_img_metadata(url: url),
// jb55: quick hack since carousel with multiple images looks horrible with blurhash background
Color.clear
} else if let meta = state.events.lookup_img_metadata(url: url),
case .processed(let blurhash) = meta.state { case .processed(let blurhash) = meta.state {
Image(uiImage: blurhash) Image(uiImage: blurhash)
.resizable() .resizable()
.frame(width: geo_size.width * UIScreen.main.scale, height: self.height * UIScreen.main.scale) .frame(width: geo_size.width * UIScreen.main.scale, height: self.height * UIScreen.main.scale)
} else { } else {
Color.clear EmptyView()
} }
} }
.onAppear { .onAppear {
if self.image_fill == nil, let size = state.events.lookup_media_size(url: url) { if self.image_fill == nil,
let meta = state.events.lookup_img_metadata(url: url),
let size = meta.meta.dim?.size
{
let fill = ImageFill.calculate_image_fill(geo_size: geo_size, img_size: size, maxHeight: maxHeight, fillHeight: fillHeight) let fill = ImageFill.calculate_image_fill(geo_size: geo_size, img_size: size, maxHeight: maxHeight, fillHeight: fillHeight)
self.image_fill = fill self.image_fill = fill
} }
} }
} }
func video_model(_ url: URL) -> VideoPlayerModel { var body: some View {
return state.events.get_video_player_model(url: url) TabView {
} ForEach(urls, id: \.absoluteString) { url in
func Media(geo: GeometryProxy, url: MediaUrl, index: Int) -> some View {
Group {
switch url {
case .image(let url):
Img(geo: geo, url: url, index: index)
.onTapGesture {
open_sheet = true
}
case .video(let url):
DamusVideoPlayer(url: url, model: video_model(url), video_size: $video_size)
.onChange(of: video_size) { size in
guard let size else { return }
let fill = ImageFill.calculate_image_fill(geo_size: geo.size, img_size: size, maxHeight: maxHeight, fillHeight: fillHeight)
print("video_size changed \(size)")
if self.image_fill == nil {
print("video_size firstImageHeight \(fill.height)")
firstImageHeight = fill.height
state.events.get_cache_data(evid).media_metadata_model.fill = fill
}
self.image_fill = fill
}
}
}
}
func Img(geo: GeometryProxy, url: URL, index: Int) -> some View {
KFAnimatedImage(url)
.callbackQueue(.dispatch(.global(qos:.background)))
.backgroundDecode(true)
.imageContext(.note, disable_animation: state.settings.disable_animation)
.image_fade(duration: 0.25)
.cancelOnDisappear(true)
.configure { view in
view.framePreloadCount = 3
}
.imageFill(for: geo.size, max: maxHeight, fill: fillHeight) { fill in
state.events.get_cache_data(evid).media_metadata_model.fill = fill
// blur hash can be discarded when we have the url
// NOTE: this is the wrong place for this... we need to remove
// it when the image is loaded in memory. This may happen
// earlier than this (by the preloader, etc)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
state.events.lookup_img_metadata(url: url)?.state = .not_needed
}
image_fill = fill
if index == 0 {
firstImageHeight = fill.height
//maxHeight = firstImageHeight ?? maxHeight
} else {
//maxHeight = firstImageHeight ?? fill.height
}
}
.background {
Placeholder(url: url, geo_size: geo.size, num_urls: urls.count)
}
.aspectRatio(contentMode: filling ? .fill : .fit)
.position(x: geo.size.width / 2, y: geo.size.height / 2)
.tabItem {
Text(url.absoluteString)
}
.id(url.absoluteString)
.padding(0)
}
var Medias: some View {
TabView(selection: $selectedIndex) {
ForEach(urls.indices, id: \.self) { index in
GeometryReader { geo in GeometryReader { geo in
Media(geo: geo, url: urls[index], index: index) KFAnimatedImage(url)
.callbackQueue(.dispatch(.global(qos:.background)))
.backgroundDecode(true)
.imageContext(.note, disable_animation: state.settings.disable_animation)
.image_fade(duration: 0.25)
.cancelOnDisappear(true)
.configure { view in
view.framePreloadCount = 3
}
.imageFill(for: geo.size, max: maxHeight, fill: fillHeight) { fill in
state.previews.cache_image_meta(evid: evid, image_fill: fill)
// blur hash can be discarded when we have the url
// NOTE: this is the wrong place for this... we need to remove
// it when the image is loaded in memory. This may happen
// earlier than this (by the preloader, etc)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
state.events.lookup_img_metadata(url: url)?.state = .not_needed
}
image_fill = fill
}
.background {
Placeholder(url: url, geo_size: geo.size)
}
.aspectRatio(contentMode: filling ? .fill : .fit)
.tabItem {
Text(url.absoluteString)
}
.id(url.absoluteString)
} }
} }
} }
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.fullScreenCover(isPresented: $open_sheet) { .fullScreenCover(isPresented: $open_sheet) {
ImageView(cache: state.events, urls: urls, disable_animation: state.settings.disable_animation) ImageView(urls: urls, disable_animation: state.settings.disable_animation)
} }
.frame(height: height) .frame(height: self.height)
.onChange(of: selectedIndex) { value in .onTapGesture {
selectedIndex = value open_sheet = true
} }
.tabViewStyle(PageTabViewStyle()) .tabViewStyle(PageTabViewStyle())
} }
var body: some View {
VStack {
Medias
.onTapGesture { }
// This is our custom carousel image indicator
CarouselDotsView(urls: urls, selectedIndex: $selectedIndex)
}
}
}
// MARK: - Custom Carousel
struct CarouselDotsView<T>: View {
let urls: [T]
@Binding var selectedIndex: Int
var body: some View {
if urls.count > 1 {
HStack {
ForEach(urls.indices, id: \.self) { index in
Circle()
.fill(index == selectedIndex ? Color("DamusPurple") : Color("DamusLightGrey"))
.frame(width: 10, height: 10)
.onTapGesture {
selectedIndex = index
}
}
}
.padding(.top, CGFloat(8))
.id(UUID())
}
}
} }
// MARK: - Image Modifier // MARK: - Image Modifier
@@ -285,11 +199,9 @@ public struct ImageFill {
} }
} }
// MARK: - Preview Provider
struct ImageCarousel_Previews: PreviewProvider { struct ImageCarousel_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!) ImageCarousel(state: test_damus_state(), evid: "evid", urls: [URL(string: "https://jb55.com/red-me.jpg")!,URL(string: "https://jb55.com/red-me.jpg")!])
ImageCarousel(state: test_damus_state(), evid: "evid", urls: [url, url])
} }
} }
+7 -8
View File
@@ -25,10 +25,10 @@ struct InvoiceView: View {
UIPasteboard.general.string = invoice.string UIPasteboard.general.string = invoice.string
} label: { } label: {
if !copied { if !copied {
Image("copy2") Image(systemName: "doc.on.clipboard")
.foregroundColor(.gray) .foregroundColor(.gray)
} else { } else {
Image("check-circle") Image(systemName: "checkmark.circle")
.foregroundColor(DamusColors.green) .foregroundColor(DamusColors.green)
} }
} }
@@ -37,7 +37,7 @@ struct InvoiceView: View {
var PayButton: some View { var PayButton: some View {
Button { Button {
if settings.show_wallet_selector { if settings.show_wallet_selector {
present_sheet(.select_wallet(invoice: invoice.string)) showing_select_wallet = true
} else { } else {
open_with_wallet(wallet: settings.default_wallet.model, invoice: invoice.string) open_with_wallet(wallet: settings.default_wallet.model, invoice: invoice.string)
} }
@@ -63,7 +63,7 @@ struct InvoiceView: View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
HStack { HStack {
Label("", image: "zap.fill") Label("", systemImage: "bolt.fill")
.foregroundColor(.orange) .foregroundColor(.orange)
Text("Lightning Invoice", comment: "Indicates that the view is for paying a Lightning invoice.") Text("Lightning Invoice", comment: "Indicates that the view is for paying a Lightning invoice.")
Spacer() Spacer()
@@ -79,6 +79,9 @@ struct InvoiceView: View {
} }
.padding(30) .padding(30)
} }
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
SelectWalletView(default_wallet: settings.default_wallet, showingSelectWallet: $showing_select_wallet, our_pubkey: our_pubkey, invoice: invoice.string)
}
} }
} }
@@ -113,7 +116,3 @@ struct InvoiceView_Previews: PreviewProvider {
} }
} }
func present_sheet(_ sheet: Sheets) {
notify(.present_sheet, sheet)
}
+2 -2
View File
@@ -32,11 +32,11 @@ struct NIP05Badge: View {
Group { Group {
if nip05_color { if nip05_color {
LINEAR_GRADIENT LINEAR_GRADIENT
.mask(Image("check-circle.fill") .mask(Image(systemName: "checkmark.seal.fill")
.resizable() .resizable()
).frame(width: 14, height: 14) ).frame(width: 14, height: 14)
} else if show_domain { } else if show_domain {
Image("check-circle.fill") Image(systemName: "checkmark.seal.fill")
.font(.footnote) .font(.footnote)
.nip05_colorized(gradient: nip05_color) .nip05_colorized(gradient: nip05_color)
} }
+2 -2
View File
@@ -14,11 +14,11 @@ struct Reposted: View {
var body: some View { var body: some View {
HStack(alignment: .center) { HStack(alignment: .center) {
Image("repost") Image(systemName: "arrow.2.squarepath")
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_nip5_domain: false) ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_nip5_domain: false)
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
Text("Reposted", comment: "Text indicating that the note was reposted (i.e. re-shared).") Text("Reposted", comment: "Text indicating that the post was reposted (i.e. re-shared).")
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
} }
} }
-73
View File
@@ -1,73 +0,0 @@
//
// SupporterBadge.swift
// damus
//
// Created by William Casarin on 2023-05-15.
//
import SwiftUI
struct SupporterBadge: View {
let percent: Int
let size: CGFloat = 17
var body: some View {
if percent < 100 {
Image("star.fill")
.resizable()
.frame(width:size, height:size)
.foregroundColor(support_level_color(percent))
} else {
Image("star.fill")
.resizable()
.frame(width:size, height:size)
.foregroundStyle(GoldGradient)
}
}
}
func support_level_color(_ percent: Int) -> Color {
if percent == 0 {
return .gray
}
let percent_f = Double(percent) / 100.0
let cutoff = 0.5
let h = cutoff + (percent_f * cutoff); // Hue (note 0.2 = Green, see huge chart below)
let s = 0.9; // Saturation
let b = 0.9; // Brightness
return Color(hue: h, saturation: s, brightness: b)
}
struct SupporterBadge_Previews: PreviewProvider {
static func Level(_ p: Int) -> some View {
HStack(alignment: .center) {
SupporterBadge(percent: p)
.frame(width: 50)
Text(verbatim: p.formatted())
.frame(width: 50)
}
}
static var previews: some View {
VStack(spacing: 0) {
VStack(spacing: 0) {
Level(1)
Level(10)
Level(20)
Level(30)
Level(40)
Level(50)
}
Level(60)
Level(70)
Level(80)
Level(90)
Level(100)
}
}
}
+11 -1
View File
@@ -12,7 +12,7 @@ struct TruncatedText: View {
let maxChars: Int = 280 let maxChars: Int = 280
var body: some View { var body: some View {
let truncatedAttributedString: AttributedString? = text.attributed.truncateOrNil(maxLength: maxChars) let truncatedAttributedString: AttributedString? = getTruncatedString()
if let truncatedAttributedString { if let truncatedAttributedString {
Text(truncatedAttributedString) Text(truncatedAttributedString)
@@ -28,6 +28,16 @@ struct TruncatedText: View {
.allowsHitTesting(false) .allowsHitTesting(false)
} }
} }
func getTruncatedString() -> AttributedString? {
let nsAttributedString = NSAttributedString(text.attributed)
if nsAttributedString.length < maxChars { return nil }
let range = NSRange(location: 0, length: maxChars)
let truncatedAttributedString = nsAttributedString.attributedSubstring(from: range)
return AttributedString(truncatedAttributedString) + "..."
}
} }
struct TruncatedText_Previews: PreviewProvider { struct TruncatedText_Previews: PreviewProvider {
+16 -15
View File
@@ -11,27 +11,30 @@ struct UserViewRow: View {
let damus_state: DamusState let damus_state: DamusState
let pubkey: String let pubkey: String
@State var navigating: Bool = false
var body: some View { var body: some View {
let dest = ProfileView(damus_state: damus_state, pubkey: pubkey)
UserView(damus_state: damus_state, pubkey: pubkey) UserView(damus_state: damus_state, pubkey: pubkey)
.contentShape(Rectangle()) .contentShape(Rectangle())
.background(.clear) .background(
NavigationLink(destination: dest, isActive: $navigating) {
EmptyView()
}
)
.onTapGesture {
navigating = true
}
} }
} }
struct UserView: View { struct UserView: View {
let damus_state: DamusState let damus_state: DamusState
let pubkey: String let pubkey: String
let spacer: Bool
@State var about_text: Text? = nil
init(damus_state: DamusState, pubkey: String, spacer: Bool = true) {
self.damus_state = damus_state
self.pubkey = pubkey
self.spacer = spacer
}
var body: some View { var body: some View {
VStack { VStack {
HStack { HStack {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
@@ -39,16 +42,14 @@ struct UserView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
let profile = damus_state.profiles.lookup(id: pubkey) let profile = damus_state.profiles.lookup(id: pubkey)
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_nip5_domain: false) ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_nip5_domain: false)
if let about_text { if let about = profile?.about {
about_text Text(about)
.lineLimit(3) .lineLimit(3)
.font(.footnote) .font(.footnote)
} }
} }
if spacer { Spacer()
Spacer()
}
} }
} }
} }
+1 -3
View File
@@ -13,7 +13,7 @@ struct WebsiteLink: View {
var body: some View { var body: some View {
HStack { HStack {
Image("link") Image(systemName: "link")
.foregroundColor(.gray) .foregroundColor(.gray)
.font(.footnote) .font(.footnote)
@@ -23,8 +23,6 @@ struct WebsiteLink: View {
Text(link_text) Text(link_text)
.font(.footnote) .font(.footnote)
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
.truncationMode(.tail)
.lineLimit(1)
}) })
} }
} }
+71 -65
View File
@@ -10,41 +10,46 @@ import SwiftUI
enum ZappingEventType { enum ZappingEventType {
case failed(ZappingError) case failed(ZappingError)
case got_zap_invoice(String) case got_zap_invoice(String)
case sent_from_nwc
} }
enum ZappingError { enum ZappingError {
case fetching_invoice case fetching_invoice
case bad_lnurl case bad_lnurl
case canceled
case send_failed
} }
struct ZappingEvent { struct ZappingEvent {
let is_custom: Bool let is_custom: Bool
let type: ZappingEventType let type: ZappingEventType
let target: ZapTarget let event: NostrEvent
}
class ZapButtonModel: ObservableObject {
var invoice: String? = nil
@Published var zapping: String = ""
@Published var showing_select_wallet: Bool = false
@Published var showing_zap_customizer: Bool = false
} }
struct ZapButton: View { struct ZapButton: View {
let damus_state: DamusState let damus_state: DamusState
let target: ZapTarget let event: NostrEvent
let lnurl: String let lnurl: String
@ObservedObject var zaps: ZapsDataModel @ObservedObject var zaps: ZapsDataModel
@StateObject var button: ZapButtonModel = ZapButtonModel()
var our_zap: Zapping? { var our_zap: Zapping? {
zaps.zaps.first(where: { z in z.request.ev.pubkey == damus_state.pubkey }) zaps.zaps.first(where: { z in z.request.pubkey == damus_state.pubkey })
} }
var zap_img: String { var zap_img: String {
switch our_zap { switch our_zap {
case .none: case .none:
return "zap" return "bolt"
case .zap: case .zap:
return "zap.fill" return "bolt.fill"
case .pending: case .pending:
return "zap.fill" return "bolt.fill"
} }
} }
@@ -55,11 +60,18 @@ struct ZapButton: View {
// always orange ! // always orange !
return Color.orange return Color.orange
/*
if our_zap.is_paid {
return Color.orange
} else {
return Color.yellow
}
*/
} }
func tap() { func tap() {
guard let our_zap else { guard let our_zap else {
send_zap(damus_state: damus_state, target: target, lnurl: lnurl, is_custom: false, comment: nil, amount_sats: nil, zap_type: damus_state.settings.default_zap_type) send_zap(damus_state: damus_state, event: event, lnurl: lnurl, is_custom: false, comment: nil, amount_sats: nil, zap_type: damus_state.settings.default_zap_type)
return return
} }
@@ -106,17 +118,12 @@ struct ZapButton: View {
var body: some View { var body: some View {
HStack(spacing: 4) { HStack(spacing: 4) {
if !damus_state.settings.nozaps || zaps.zap_total > 0 { Button(action: {
Button(action: { }, label: {
}, label: { Image(systemName: zap_img)
Image(zap_img) .foregroundColor(zap_color)
.resizable() .font(.footnote.weight(.medium))
.foregroundColor(zap_color) })
.font(.footnote.weight(.medium))
.aspectRatio(contentMode: .fit)
.frame(width:20, height: 20)
})
}
if zaps.zap_total > 0 { if zaps.zap_total > 0 {
Text(verbatim: format_msats_abbrev(zaps.zap_total)) Text(verbatim: format_msats_abbrev(zaps.zap_total))
@@ -126,15 +133,41 @@ struct ZapButton: View {
} }
.accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button")) .accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button"))
.simultaneousGesture(LongPressGesture().onEnded {_ in .simultaneousGesture(LongPressGesture().onEnded {_ in
guard !damus_state.settings.nozaps else { return } button.showing_zap_customizer = true
present_sheet(.zap(target: target, lnurl: lnurl))
}) })
.highPriorityGesture(TapGesture().onEnded { .highPriorityGesture(TapGesture().onEnded {
guard !damus_state.settings.nozaps else { return }
tap() tap()
}) })
.sheet(isPresented: $button.showing_zap_customizer) {
CustomizeZapView(state: damus_state, event: event, lnurl: lnurl)
}
.sheet(isPresented: $button.showing_select_wallet, onDismiss: {button.showing_select_wallet = false}) {
SelectWalletView(default_wallet: damus_state.settings.default_wallet, showingSelectWallet: $button.showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: button.invoice ?? "")
}
.onReceive(handle_notify(.zapping)) { notif in
let zap_ev = notif.object as! ZappingEvent
guard zap_ev.event.id == self.event.id else {
return
}
guard !zap_ev.is_custom else {
return
}
switch zap_ev.type {
case .failed:
break
case .got_zap_invoice(let inv):
if damus_state.settings.show_wallet_selector {
self.button.invoice = inv
self.button.showing_select_wallet = true
} else {
let wallet = damus_state.settings.default_wallet.model
open_with_wallet(wallet: wallet, invoice: inv)
}
}
}
} }
} }
@@ -144,7 +177,7 @@ struct ZapButton_Previews: PreviewProvider {
let pending_zap = PendingZap(amount_msat: 1000, target: ZapTarget.note(id: "noteid", author: "author"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice))) let pending_zap = PendingZap(amount_msat: 1000, target: ZapTarget.note(id: "noteid", author: "author"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
let zaps = ZapsDataModel([.pending(pending_zap)]) let zaps = ZapsDataModel([.pending(pending_zap)])
ZapButton(damus_state: test_damus_state(), target: ZapTarget.note(id: test_event.id, author: test_event.pubkey), lnurl: "lnurl", zaps: zaps) ZapButton(damus_state: test_damus_state(), event: test_event, lnurl: "lnurl", zaps: zaps)
} }
} }
@@ -160,13 +193,14 @@ func initial_pending_zap_state(settings: UserSettingsStore) -> PendingZapState {
return .external(ExtPendingZapState(state: .fetching_invoice)) return .external(ExtPendingZapState(state: .fetching_invoice))
} }
func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_custom: Bool, comment: String?, amount_sats: Int?, zap_type: ZapType) { func send_zap(damus_state: DamusState, event: NostrEvent, lnurl: String, is_custom: Bool, comment: String?, amount_sats: Int?, zap_type: ZapType) {
guard let keypair = damus_state.keypair.to_full() else { guard let keypair = damus_state.keypair.to_full() else {
return return
} }
// Only take the first 10 because reasons // Only take the first 10 because reasons
let relays = Array(damus_state.pool.our_descriptors.prefix(10)) let relays = Array(damus_state.pool.our_descriptors.prefix(10))
let target = ZapTarget.note(id: event.id, author: event.pubkey)
let content = comment ?? "" let content = comment ?? ""
guard let mzapreq = make_zap_request_event(keypair: keypair, content: content, relays: relays, target: target, zap_type: zap_type) else { guard let mzapreq = make_zap_request_event(keypair: keypair, content: content, relays: relays, target: target, zap_type: zap_type) else {
@@ -174,7 +208,8 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
return return
} }
let amount_msat = Int64(amount_sats ?? damus_state.settings.default_zap_amount) * 1000 let zap_amount = amount_sats ?? damus_state.settings.default_zap_amount
let amount_msat = Int64(zap_amount) * 1000
let pending_zap_state = initial_pending_zap_state(settings: damus_state.settings) let pending_zap_state = initial_pending_zap_state(settings: damus_state.settings)
let pending_zap = PendingZap(amount_msat: amount_msat, target: target, request: mzapreq, type: zap_type, state: pending_zap_state) let pending_zap = PendingZap(amount_msat: amount_msat, target: target, request: mzapreq, type: zap_type, state: pending_zap_state)
let zapreq = mzapreq.potentially_anon_outer_request.ev let zapreq = mzapreq.potentially_anon_outer_request.ev
@@ -194,7 +229,7 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
DispatchQueue.main.async { DispatchQueue.main.async {
remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events) remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events)
let typ = ZappingEventType.failed(.bad_lnurl) let typ = ZappingEventType.failed(.bad_lnurl)
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target) let ev = ZappingEvent(is_custom: is_custom, type: typ, event: event)
notify(.zapping, ev) notify(.zapping, ev)
} }
return return
@@ -204,11 +239,11 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
damus_state.lnurls.endpoints[target.pubkey] = payreq damus_state.lnurls.endpoints[target.pubkey] = payreq
} }
guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, msats: amount_msat, zap_type: zap_type, comment: comment) else { guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, sats: zap_amount, zap_type: zap_type, comment: comment) else {
DispatchQueue.main.async { DispatchQueue.main.async {
remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events) remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events)
let typ = ZappingEventType.failed(.fetching_invoice) let typ = ZappingEventType.failed(.fetching_invoice)
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target) let ev = ZappingEvent(is_custom: is_custom, type: typ, event: event)
notify(.zapping, ev) notify(.zapping, ev)
} }
return return
@@ -221,50 +256,21 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
// don't both continuing, user has canceled // don't both continuing, user has canceled
if case .cancel_fetching_invoice = nwc_state.state { if case .cancel_fetching_invoice = nwc_state.state {
remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events) remove_zap(reqid: reqid, zapcache: damus_state.zaps, evcache: damus_state.events)
let typ = ZappingEventType.failed(.canceled)
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target)
notify(.zapping, ev)
return return
} }
var flusher: OnFlush? = nil guard let nwc_req = nwc_pay(url: nwc_state.url, pool: damus_state.pool, post: damus_state.postbox, invoice: inv),
case .nwc(let pzap_state) = pending_zap_state
// donations are only enabled on one-tap zaps and off appstore else {
if !damus_state.settings.nozaps && !is_custom && damus_state.settings.donation_percent > 0 {
flusher = .once({ pe in
// send donation zap when the pending zap is flushed, this allows user to cancel and not send a donation
Task { @MainActor in
await send_donation_zap(pool: damus_state.pool, postbox: damus_state.postbox, nwc: nwc_state.url, percent: damus_state.settings.donation_percent, base_msats: amount_msat)
}
})
}
// we don't have a delay on one-tap nozaps (since this will be from customize zap view)
let delay = damus_state.settings.nozaps ? nil : 5.0
let nwc_req = nwc_pay(url: nwc_state.url, pool: damus_state.pool, post: damus_state.postbox, invoice: inv, delay: delay, on_flush: flusher)
guard let nwc_req, case .nwc(let pzap_state) = pending_zap_state else {
print("nwc: failed to send nwc request for zapreq \(reqid.reqid)")
let typ = ZappingEventType.failed(.send_failed)
let ev = ZappingEvent(is_custom: is_custom, type: typ, target: target)
notify(.zapping, ev)
return return
} }
print("nwc: sending request \(nwc_req.id) zap_req_id \(reqid.reqid)")
if pzap_state.update_state(state: .postbox_pending(nwc_req)) { if pzap_state.update_state(state: .postbox_pending(nwc_req)) {
// we don't need to trigger a ZapsDataModel update here // we don't need to trigger a ZapsDataModel update here
} }
let ev = ZappingEvent(is_custom: is_custom, type: .sent_from_nwc, target: target)
notify(.zapping, ev)
case .external(let pending_ext): case .external(let pending_ext):
pending_ext.state = .done pending_ext.state = .done
let ev = ZappingEvent(is_custom: is_custom, type: .got_zap_invoice(inv), target: target) let ev = ZappingEvent(is_custom: is_custom, type: .got_zap_invoice(inv), event: event)
notify(.zapping, ev) notify(.zapping, ev)
} }
} }
+101 -159
View File
@@ -6,7 +6,6 @@
// //
import SwiftUI import SwiftUI
import AVKit
struct TimestampedProfile { struct TimestampedProfile {
let profile: Profile let profile: Profile
@@ -14,38 +13,17 @@ struct TimestampedProfile {
let event: NostrEvent let event: NostrEvent
} }
struct ZapSheet {
let target: ZapTarget
let lnurl: String
}
struct SelectWallet {
let invoice: String
}
enum Sheets: Identifiable { enum Sheets: Identifiable {
case post(PostAction) case post(PostAction)
case report(ReportTarget) case report(ReportTarget)
case event(NostrEvent) case event(NostrEvent)
case zap(ZapSheet)
case select_wallet(SelectWallet)
case filter case filter
static func zap(target: ZapTarget, lnurl: String) -> Sheets {
return .zap(ZapSheet(target: target, lnurl: lnurl))
}
static func select_wallet(invoice: String) -> Sheets {
return .select_wallet(SelectWallet(invoice: invoice))
}
var id: String { var id: String {
switch self { switch self {
case .report: return "report" case .report: return "report"
case .post(let action): return "post-" + (action.ev?.id ?? "") case .post(let action): return "post-" + (action.ev?.id ?? "")
case .event(let ev): return "event-" + ev.id case .event(let ev): return "event-" + ev.id
case .zap(let sheet): return "zap-" + sheet.target.id
case .select_wallet: return "select-wallet"
case .filter: return "filter" case .filter: return "filter"
} }
} }
@@ -82,14 +60,21 @@ struct ContentView: View {
@State var damus_state: DamusState? = nil @State var damus_state: DamusState? = nil
@SceneStorage("ContentView.selected_timeline") var selected_timeline: Timeline = .home @SceneStorage("ContentView.selected_timeline") var selected_timeline: Timeline = .home
@State var is_deleted_account: Bool = false @State var is_deleted_account: Bool = false
@State var active_profile: String? = nil
@State var active_search: NostrFilter? = nil
@State var active_event: NostrEvent? = nil
@State var profile_open: Bool = false
@State var thread_open: Bool = false
@State var search_open: Bool = false
@State var wallet_open: Bool = false
@State var active_nwc: WalletConnectURL? = nil
@State var muting: String? = nil @State var muting: String? = nil
@State var confirm_mute: Bool = false @State var confirm_mute: Bool = false
@State var user_muted_confirm: Bool = false @State var user_muted_confirm: Bool = false
@State var confirm_overwrite_mutelist: Bool = false @State var confirm_overwrite_mutelist: Bool = false
@SceneStorage("ContentView.filter_state") var filter_state : FilterState = .posts_and_replies @SceneStorage("ContentView.filter_state") var filter_state : FilterState = .posts_and_replies
@State private var isSideBarOpened = false @State private var isSideBarOpened = false
var home: HomeModel = HomeModel() @StateObject var home: HomeModel = HomeModel()
@StateObject var navigationCoordinator: NavigationCoordinator = NavigationCoordinator()
let sub_id = UUID().description let sub_id = UUID().description
@@ -121,7 +106,7 @@ struct ContentView: View {
if privkey != nil { if privkey != nil {
PostButtonContainer(is_left_handed: damus_state?.settings.left_handed ?? false) { PostButtonContainer(is_left_handed: damus_state?.settings.left_handed ?? false) {
self.active_sheet = .post(.posting(.none)) self.active_sheet = .post(.posting)
} }
} }
} }
@@ -129,8 +114,8 @@ struct ContentView: View {
.safeAreaInset(edge: .top, spacing: 0) { .safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) { VStack(spacing: 0) {
CustomPicker(selection: $filter_state, content: { CustomPicker(selection: $filter_state, content: {
Text("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies).").tag(FilterState.posts) Text("Posts", comment: "Label for filter for seeing only posts (instead of posts and replies).").tag(FilterState.posts)
Text("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes).").tag(FilterState.posts_and_replies) Text("Posts & Replies", comment: "Label for filter for seeing posts and replies (instead of only posts).").tag(FilterState.posts_and_replies)
}) })
Divider() Divider()
.frame(height: 1) .frame(height: 1)
@@ -142,13 +127,16 @@ struct ContentView: View {
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View { func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
ZStack { ZStack {
if let damus = self.damus_state { if let damus = self.damus_state {
TimelineView(events: home.events, loading: .constant(false), damus: damus, show_friend_icon: false, filter: filter) TimelineView(events: home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter)
} }
} }
} }
func popToRoot() { func popToRoot() {
navigationCoordinator.popToRoot() profile_open = false
thread_open = false
search_open = false
wallet_open = false
isSideBarOpened = false isSideBarOpened = false
} }
@@ -159,6 +147,21 @@ struct ContentView: View {
func MainContent(damus: DamusState) -> some View { func MainContent(damus: DamusState) -> some View {
VStack { VStack {
NavigationLink(destination: WalletView(model: damus_state!.wallet), isActive: $wallet_open) {
EmptyView()
}
NavigationLink(destination: MaybeProfileView, isActive: $profile_open) {
EmptyView()
}
if let active_event {
let thread = ThreadModel(event: active_event, damus_state: damus_state!)
NavigationLink(destination: ThreadView(state: damus_state!, thread: thread), isActive: $thread_open) {
EmptyView()
}
}
NavigationLink(destination: MaybeSearchView, isActive: $search_open) {
EmptyView()
}
switch selected_timeline { switch selected_timeline {
case .search: case .search:
if #available(iOS 16.0, *) { if #available(iOS 16.0, *) {
@@ -200,6 +203,28 @@ struct ContentView: View {
} }
} }
var MaybeSearchView: some View {
Group {
if let search = self.active_search {
SearchView(appstate: damus_state!, search: SearchModel(state: damus_state!, search: search))
} else {
EmptyView()
}
}
}
var MaybeProfileView: some View {
Group {
if let pk = self.active_profile {
let profile_model = ProfileModel(pubkey: pk, damus: damus_state!)
let followers = FollowersModel(damus_state: damus_state!, target: pk)
ProfileView(damus_state: damus_state!, profile: profile_model, followers: followers)
} else {
EmptyView()
}
}
}
func MaybeReportView(target: ReportTarget) -> some View { func MaybeReportView(target: ReportTarget) -> some View {
Group { Group {
if let damus_state { if let damus_state {
@@ -215,30 +240,32 @@ struct ContentView: View {
} }
func open_event(ev: NostrEvent) { func open_event(ev: NostrEvent) {
let thread = ThreadModel(event: ev, damus_state: damus_state!) popToRoot()
navigationCoordinator.push(route: Route.Thread(thread: thread)) self.active_event = ev
self.thread_open = true
} }
func open_wallet(nwc: WalletConnectURL) { func open_wallet(nwc: WalletConnectURL) {
self.damus_state!.wallet.new(nwc) self.damus_state!.wallet.new(nwc)
navigationCoordinator.push(route: Route.Wallet(wallet: damus_state!.wallet)) self.wallet_open = true
} }
func open_profile(id: String) { func open_profile(id: String) {
let profile_model = ProfileModel(pubkey: id, damus: damus_state!) popToRoot()
let followers = FollowersModel(damus_state: damus_state!, target: id) self.active_profile = id
navigationCoordinator.push(route: Route.Profile(profile: profile_model, followers: followers)) self.profile_open = true
} }
func open_search(filt: NostrFilter) { func open_search(filt: NostrFilter) {
let search = SearchModel(state: damus_state!, search: filt) popToRoot()
navigationCoordinator.push(route: Route.Search(search: search)) self.active_search = filt
self.search_open = true
} }
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
if let damus = self.damus_state { if let damus = self.damus_state {
NavigationStack(path: $navigationCoordinator.path) { NavigationView {
TabView { // Prevents navbar appearance change on scroll TabView { // Prevents navbar appearance change on scroll
MainContent(damus: damus) MainContent(damus: damus)
.toolbar() { .toolbar() {
@@ -261,14 +288,13 @@ struct ContentView: View {
if selected_timeline == .search { if selected_timeline == .search {
Button(action: { Button(action: {
//isFilterVisible.toggle() //isFilterVisible.toggle()
present_sheet(.filter) self.active_sheet = .filter
}) { }) {
// checklist, checklist.checked, lisdt.bullet, list.bullet.circle, line.3.horizontal.decrease..., line.3.horizontail.decrease // checklist, checklist.checked, lisdt.bullet, list.bullet.circle, line.3.horizontal.decrease..., line.3.horizontail.decrease
Label(NSLocalizedString("Filter", comment: "Button label text for filtering relay servers."), image: "filter") Label(NSLocalizedString("Filter", comment: "Button label text for filtering relay servers."), systemImage: "line.3.horizontal.decrease")
.foregroundColor(.gray) .foregroundColor(.gray)
//.contentShape(Rectangle()) //.contentShape(Rectangle())
} }
.buttonStyle(.plain)
} }
} }
} }
@@ -278,16 +304,10 @@ struct ContentView: View {
.overlay( .overlay(
SideMenuView(damus_state: damus, isSidebarVisible: $isSideBarOpened.animation()) SideMenuView(damus_state: damus, isSidebarVisible: $isSideBarOpened.animation())
) )
.navigationDestination(for: Route.self) { route in
route.view(navigationCordinator: navigationCoordinator, damusState: damus_state!)
}
.onReceive(handle_notify(.switched_timeline)) { _ in
navigationCoordinator.popToRoot()
}
} }
.navigationViewStyle(.stack) .navigationViewStyle(.stack)
TabBar(nstatus: home.notification_status, selected: $selected_timeline, settings: damus.settings, action: switch_timeline) TabBar(new_events: $home.new_events, selected: $selected_timeline, settings: damus.settings, action: switch_timeline)
.padding([.bottom], 8) .padding([.bottom], 8)
.background(Color(uiColor: .systemBackground).ignoresSafeArea()) .background(Color(uiColor: .systemBackground).ignoresSafeArea())
} }
@@ -295,7 +315,6 @@ struct ContentView: View {
.ignoresSafeArea(.keyboard) .ignoresSafeArea(.keyboard)
.onAppear() { .onAppear() {
self.connect() self.connect()
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .default, options: .mixWithOthers)
setup_notifications() setup_notifications()
} }
.sheet(item: $active_sheet) { item in .sheet(item: $active_sheet) { item in
@@ -306,10 +325,6 @@ struct ContentView: View {
PostView(action: action, damus_state: damus_state!) PostView(action: action, damus_state: damus_state!)
case .event: case .event:
EventDetailView() EventDetailView()
case .zap(let zapsheet):
CustomizeZapView(state: damus_state!, target: zapsheet.target, lnurl: zapsheet.lnurl)
case .select_wallet(let select):
SelectWalletView(default_wallet: damus_state!.settings.default_wallet, active_sheet: $active_sheet, our_pubkey: damus_state!.pubkey, invoice: select.invoice)
case .filter: case .filter:
let timeline = selected_timeline let timeline = selected_timeline
if #available(iOS 16.0, *) { if #available(iOS 16.0, *) {
@@ -382,7 +397,7 @@ struct ContentView: View {
return return
} }
ds.postbox.send(ev) ds.postbox.send(ev)
if let profile = ds.profiles.lookup_with_timestamp(id: ev.pubkey) { if let profile = ds.profiles.profiles[ev.pubkey] {
ds.postbox.send(profile.event) ds.postbox.send(profile.event)
} }
} }
@@ -417,31 +432,6 @@ struct ContentView: View {
.onReceive(handle_notify(.unmute_thread)) { notif in .onReceive(handle_notify(.unmute_thread)) { notif in
home.filter_events() home.filter_events()
} }
.onReceive(handle_notify(.present_sheet)) { notif in
let sheet = notif.object as! Sheets
self.active_sheet = sheet
}
.onReceive(handle_notify(.zapping)) { notif in
let zap_ev = notif.object as! ZappingEvent
guard !zap_ev.is_custom else {
return
}
switch zap_ev.type {
case .failed:
break
case .got_zap_invoice(let inv):
if damus_state!.settings.show_wallet_selector {
present_sheet(.select_wallet(invoice: inv))
} else {
let wallet = damus_state!.settings.default_wallet.model
open_with_wallet(wallet: wallet, invoice: inv)
}
case .sent_from_nwc:
break
}
}
.onChange(of: scenePhase) { (phase: ScenePhase) in .onChange(of: scenePhase) { (phase: ScenePhase) in
switch phase { switch phase {
case .background: case .background:
@@ -465,11 +455,6 @@ struct ContentView: View {
return return
} }
if local.type == .profile_zap {
open_profile(id: local.event_id)
return
}
guard let target = damus_state.events.lookup(local.event_id) else { guard let target = damus_state.events.lookup(local.event_id) else {
return return
} }
@@ -477,16 +462,13 @@ struct ContentView: View {
switch local.type { switch local.type {
case .dm: case .dm:
selected_timeline = .dms selected_timeline = .dms
damus_state.dms.set_active_dm(target.pubkey) damus_state.dms.open_dm_by_pk(target.pubkey)
navigationCoordinator.push(route: Route.DMChat(dms: damus_state.dms.active_model))
case .like: fallthrough case .like: fallthrough
case .zap: fallthrough case .zap: fallthrough
case .mention: fallthrough case .mention: fallthrough
case .repost: case .repost:
open_event(ev: target) open_event(ev: target)
case .profile_zap:
// Handled separately above.
break
} }
} }
.onReceive(handle_notify(.onlyzaps_mode)) { notif in .onReceive(handle_notify(.onlyzaps_mode)) { notif in
@@ -517,7 +499,7 @@ struct ContentView: View {
}, message: { }, message: {
if let pubkey = self.muting { if let pubkey = self.muting {
let profile = damus_state!.profiles.lookup(id: pubkey) let profile = damus_state!.profiles.lookup(id: pubkey)
let name = Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) let name = Profile.displayName(profile: profile, pubkey: pubkey).username
Text("\(name) has been muted", comment: "Alert message that informs a user was muted.") Text("\(name) has been muted", comment: "Alert message that informs a user was muted.")
} else { } else {
Text("User has been muted", comment: "Alert message that informs a user was d.") Text("User has been muted", comment: "Alert message that informs a user was d.")
@@ -577,7 +559,7 @@ struct ContentView: View {
}, message: { }, message: {
if let pubkey = muting { if let pubkey = muting {
let profile = damus_state?.profiles.lookup(id: pubkey) let profile = damus_state?.profiles.lookup(id: pubkey)
let name = Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50) let name = Profile.displayName(profile: profile, pubkey: pubkey).username
Text("Mute \(name)?", comment: "Alert message prompt to ask if a user should be muted.") Text("Mute \(name)?", comment: "Alert message prompt to ask if a user should be muted.")
} else { } else {
Text("Could not find user to mute...", comment: "Alert message to indicate that the muted user could not be found.") Text("Could not find user to mute...", comment: "Alert message to indicate that the muted user could not be found.")
@@ -645,8 +627,7 @@ struct ContentView: View {
bootstrap_relays: bootstrap_relays, bootstrap_relays: bootstrap_relays,
replies: ReplyCounter(our_pubkey: pubkey), replies: ReplyCounter(our_pubkey: pubkey),
muted_threads: MutedThreadsManager(keypair: keypair), muted_threads: MutedThreadsManager(keypair: keypair),
wallet: WalletModel(settings: settings), wallet: WalletModel(settings: settings)
nav: self.navigationCoordinator
) )
home.damus_state = self.damus_state! home.damus_state = self.damus_state!
@@ -711,7 +692,7 @@ func update_filters_with_since(last_of_kind: [Int: NostrEvent], filters: [NostrF
let kinds = filter.kinds ?? [] let kinds = filter.kinds ?? []
let initial: Int64? = nil let initial: Int64? = nil
let earliest = kinds.reduce(initial) { earliest, kind in let earliest = kinds.reduce(initial) { earliest, kind in
let last = last_of_kind[kind.rawValue] let last = last_of_kind[kind]
let since: Int64? = get_since_time(last_event: last) let since: Int64? = get_since_time(last_event: last)
if earliest == nil { if earliest == nil {
@@ -755,57 +736,24 @@ func setup_notifications() {
} }
} }
struct FindEvent { func find_event(state: DamusState, evid: String, search_type: SearchType, find_from: [String]?, callback: @escaping (NostrEvent?) -> ()) {
let type: FindEventType if let ev = state.events.lookup(evid) {
let find_from: [String]? callback(ev)
return
static func profile(pubkey: String, find_from: [String]? = nil) -> FindEvent {
return FindEvent(type: .profile(pubkey), find_from: find_from)
}
static func event(evid: String, find_from: [String]? = nil) -> FindEvent {
return FindEvent(type: .event(evid), find_from: find_from)
}
}
enum FindEventType {
case profile(String)
case event(String)
}
enum FoundEvent {
case profile(Profile, NostrEvent)
case invalid_profile(NostrEvent)
case event(NostrEvent)
}
func find_event(state: DamusState, query query_: FindEvent, callback: @escaping (FoundEvent?) -> ()) {
var filter: NostrFilter? = nil
let find_from = query_.find_from
let query = query_.type
switch query {
case .profile(let pubkey):
if let profile = state.profiles.lookup_with_timestamp(id: pubkey) {
callback(.profile(profile.profile, profile.event))
return
}
filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey])
case .event(let evid):
if let ev = state.events.lookup(evid) {
callback(.event(ev))
return
}
filter = NostrFilter(ids: [evid], limit: 1)
} }
let subid = UUID().description let subid = UUID().description
var attempts: Int = 0
var has_event = false var has_event = false
guard let filter else { return }
var filter = search_type == .event ? NostrFilter.filter_ids([ evid ]) : NostrFilter.filter_authors([ evid ])
if search_type == .profile {
filter.kinds = [NostrKind.metadata.rawValue]
}
filter.limit = 1
var attempts = 0
state.pool.subscribe_to(sub_id: subid, filters: [filter], to: find_from) { relay_id, res in state.pool.subscribe_to(sub_id: subid, filters: [filter], to: find_from) { relay_id, res in
guard case .nostr_event(let ev) = res else { guard case .nostr_event(let ev) = res else {
@@ -821,22 +769,15 @@ func find_event(state: DamusState, query query_: FindEvent, callback: @escaping
break break
case .event(_, let ev): case .event(_, let ev):
has_event = true has_event = true
state.pool.unsubscribe(sub_id: subid) state.pool.unsubscribe(sub_id: subid)
switch query { if search_type == .profile && ev.known_kind == .metadata {
case .profile: process_metadata_event(events: state.events, our_pubkey: state.pubkey, profiles: state.profiles, ev: ev) {
if ev.known_kind == .metadata { callback(ev)
process_metadata_event(events: state.events, our_pubkey: state.pubkey, profiles: state.profiles, ev: ev) { profile in
guard let profile else {
callback(.invalid_profile(ev))
return
}
callback(.profile(profile, ev))
return
}
} }
case .event: } else {
callback(.event(ev)) callback(ev)
} }
case .eose: case .eose:
if !has_event { if !has_event {
@@ -859,11 +800,11 @@ func timeline_name(_ timeline: Timeline?) -> String {
} }
switch timeline { switch timeline {
case .home: case .home:
return NSLocalizedString("Home", comment: "Navigation bar title for Home view where notes and replies appear from those who the user is following.") return NSLocalizedString("Home", comment: "Navigation bar title for Home view where posts and replies appear from those who the user is following.")
case .notifications: case .notifications:
return NSLocalizedString("Notifications", comment: "Toolbar label for Notifications view.") return NSLocalizedString("Notifications", comment: "Toolbar label for Notifications view.")
case .search: case .search:
return NSLocalizedString("Universe 🛸", comment: "Toolbar label for the universal view where notes from all connected relay servers appear.") return NSLocalizedString("Universe 🛸", comment: "Toolbar label for the universal view where posts from all connected relay servers appear.")
case .dms: case .dms:
return NSLocalizedString("DMs", comment: "Toolbar label for DMs view, where DM is the English abbreviation for Direct Message.") return NSLocalizedString("DMs", comment: "Toolbar label for DMs view, where DM is the English abbreviation for Direct Message.")
} }
@@ -961,9 +902,10 @@ func on_open_url(state: DamusState, url: URL, result: @escaping (OpenResult?) ->
if ref.key == "p" { if ref.key == "p" {
result(.profile(ref.ref_id)) result(.profile(ref.ref_id))
} else if ref.key == "e" { } else if ref.key == "e" {
find_event(state: state, query: .event(evid: ref.ref_id)) { res in find_event(state: state, evid: ref.ref_id, search_type: .event, find_from: nil) { ev in
guard let res, case .event(let ev) = res else { return } if let ev {
result(.event(ev)) result(.event(ev))
}
} }
} }
case .filter(let filt): case .filter(let filt):
+1 -1
View File
@@ -20,7 +20,7 @@ class ActionBarModel: ObservableObject {
@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 private(set) var zaps: Int @Published var zaps: Int
@Published var zap_total: Int64 @Published var zap_total: Int64
@Published var replies: Int @Published var replies: Int
-20
View File
@@ -11,8 +11,6 @@ import Foundation
class Contacts { class Contacts {
private var friends: Set<String> = Set() private var friends: Set<String> = Set()
private var friend_of_friends: Set<String> = Set() private var friend_of_friends: Set<String> = Set()
/// Tracks which friends are friends of a given pubkey.
private var pubkey_to_our_friends = [String : Set<String>]()
private var muted: Set<String> = Set() private var muted: Set<String> = Set()
let our_pubkey: String let our_pubkey: String
@@ -60,10 +58,6 @@ class Contacts {
func remove_friend(_ pubkey: String) { func remove_friend(_ pubkey: String) {
friends.remove(pubkey) friends.remove(pubkey)
pubkey_to_our_friends.forEach {
pubkey_to_our_friends[$0.key]?.remove(pubkey)
}
} }
func get_friend_list() -> [String] { func get_friend_list() -> [String] {
@@ -79,15 +73,6 @@ class Contacts {
for tag in contact.tags { for tag in contact.tags {
if tag.count >= 2 && tag[0] == "p" { if tag.count >= 2 && tag[0] == "p" {
friend_of_friends.insert(tag[1]) friend_of_friends.insert(tag[1])
// Exclude themself and us.
if contact.pubkey != our_pubkey && contact.pubkey != tag[1] {
if pubkey_to_our_friends[tag[1]] == nil {
pubkey_to_our_friends[tag[1]] = Set<String>()
}
pubkey_to_our_friends[tag[1]]?.insert(contact.pubkey)
}
} }
} }
} }
@@ -111,11 +96,6 @@ class Contacts {
func follow_state(_ pubkey: String) -> FollowState { func follow_state(_ pubkey: String) -> FollowState {
return is_friend(pubkey) ? .follows : .unfollows return is_friend(pubkey) ? .follows : .unfollows
} }
/// Gets the list of pubkeys of our friends who follow the given pubkey.
func get_friended_followers(_ pubkey: String) -> [String] {
return Array((pubkey_to_our_friends[pubkey] ?? Set()))
}
} }
func follow_user(pool: RelayPool, our_contacts: NostrEvent?, pubkey: String, privkey: String, follow: ReferencedId) -> NostrEvent? { func follow_user(pool: RelayPool, our_contacts: NostrEvent?, pubkey: String, privkey: String, follow: ReferencedId) -> NostrEvent? {
+2 -12
View File
@@ -30,23 +30,13 @@ struct DamusState {
let replies: ReplyCounter let replies: ReplyCounter
let muted_threads: MutedThreadsManager let muted_threads: MutedThreadsManager
let wallet: WalletModel let wallet: WalletModel
let nav: NavigationCoordinator
@discardableResult @discardableResult
func add_zap(zap: Zapping) -> Bool { func add_zap(zap: Zapping) -> Bool {
// store generic zap mapping // store generic zap mapping
self.zaps.add_zap(zap: zap) self.zaps.add_zap(zap: zap)
let stored = self.events.store_zap(zap: zap)
// thread zaps
if let ev = zap.event, !settings.nozaps, zap.is_in_thread {
// [nozaps]: thread zaps are only available outside of the app store
replies.count_replies(ev)
events.add_replies(ev: ev)
}
// associate with events as well // associate with events as well
return stored return self.events.store_zap(zap: zap)
} }
var pubkey: String { var pubkey: String {
@@ -58,5 +48,5 @@ struct DamusState {
} }
static var empty: DamusState { static var empty: DamusState {
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil)), wallet: WalletModel(settings: UserSettingsStore()), nav: NavigationCoordinator()) } return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil)), wallet: WalletModel()) }
} }
+10
View File
@@ -30,6 +30,16 @@ class DirectMessagesModel: ObservableObject {
self.active_model = model self.active_model = model
} }
func open_dm_by_pk(_ pubkey: String) {
self.set_active_dm(pubkey)
self.open_dm = true
}
func open_dm_by_model(_ model: DirectMessageModel) {
self.set_active_dm_model(model)
self.open_dm = true
}
func set_active_dm(_ pubkey: String) { func set_active_dm(_ pubkey: String) {
for model in self.dms where model.pubkey == pubkey { for model in self.dms where model.pubkey == pubkey {
self.set_active_dm_model(model) self.set_active_dm_model(model)
+1 -1
View File
@@ -24,7 +24,7 @@ class EventsModel: ObservableObject {
} }
private func get_filter() -> NostrFilter { private func get_filter() -> NostrFilter {
var filter = NostrFilter(kinds: [kind]) var filter = NostrFilter.filter_kinds([kind.rawValue])
filter.referenced_ids = [target] filter.referenced_ids = [target]
filter.limit = 500 filter.limit = 500
return filter return filter
+7 -5
View File
@@ -30,8 +30,9 @@ class FollowersModel: ObservableObject {
} }
func get_filter() -> NostrFilter { func get_filter() -> NostrFilter {
NostrFilter(kinds: [.contacts], var filter = NostrFilter.filter_contacts
pubkeys: [target]) filter.pubkeys = [target]
return filter
} }
func subscribe() { func subscribe() {
@@ -55,13 +56,14 @@ class FollowersModel: ObservableObject {
} }
func load_profiles(relay_id: String) { func load_profiles(relay_id: String) {
let authors = find_profiles_to_fetch_from_keys(profiles: damus_state.profiles, pks: contacts ?? []) var filter = NostrFilter.filter_profiles
let authors = find_profiles_to_fetch_pk(profiles: damus_state.profiles, event_pubkeys: contacts ?? [])
if authors.isEmpty { if authors.isEmpty {
return return
} }
let filter = NostrFilter(kinds: [.metadata], filter.authors = authors
authors: authors)
damus_state.pool.subscribe_to(sub_id: profiles_id, filters: [filter], to: [relay_id], handler: handle_event) damus_state.pool.subscribe_to(sub_id: profiles_id, filters: [filter], to: [relay_id], handler: handle_event)
} }
+2 -2
View File
@@ -21,10 +21,10 @@ class FollowingModel {
} }
func get_filter() -> NostrFilter { func get_filter() -> NostrFilter {
var f = NostrFilter(kinds: [.metadata]) var f = NostrFilter.filter_kinds([NostrKind.metadata.rawValue])
f.authors = self.contacts.reduce(into: Array<String>()) { acc, pk in f.authors = self.contacts.reduce(into: Array<String>()) { acc, pk in
// don't fetch profiles we already have // don't fetch profiles we already have
if damus_state.profiles.has_fresh_profile(id: pk) { if damus_state.profiles.lookup(id: pk) != nil {
return return
} }
acc.append(pk) acc.append(pk)
+104 -171
View File
@@ -23,7 +23,7 @@ struct NewEventsBits: OptionSet {
static let notifications: NewEventsBits = [.zaps, .likes, .reposts, .mentions] static let notifications: NewEventsBits = [.zaps, .likes, .reposts, .mentions]
} }
class HomeModel { class HomeModel: ObservableObject {
// Don't trigger a user notification for events older than a certain age // Don't trigger a user notification for events older than a certain age
static let event_max_age_for_notification: TimeInterval = 12 * 60 * 60 static let event_max_age_for_notification: TimeInterval = 12 * 60 * 60
@@ -49,10 +49,9 @@ class HomeModel {
var signal = SignalModel() var signal = SignalModel()
var notifications = NotificationsModel() @Published var new_events: NewEventsBits = NewEventsBits()
var notification_status = NotificationStatusModel() @Published var notifications = NotificationsModel()
var events: EventHolder = EventHolder() @Published var events: EventHolder = EventHolder()
var zap_button: ZapButtonModel = ZapButtonModel()
init() { init() {
self.damus_state = DamusState.empty self.damus_state = DamusState.empty
@@ -133,11 +132,11 @@ class HomeModel {
case .nwc_request: case .nwc_request:
break break
case .nwc_response: case .nwc_response:
handle_nwc_response(ev, relay: relay_id) handle_nwc_response(ev)
} }
} }
func handle_nwc_response(_ ev: NostrEvent, relay: String) { func handle_nwc_response(_ ev: NostrEvent) {
Task { @MainActor in Task { @MainActor in
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time // TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
guard let nwc_str = damus_state.settings.nostr_wallet_connect, guard let nwc_str = damus_state.settings.nostr_wallet_connect,
@@ -149,54 +148,78 @@ class HomeModel {
// since command results are not returned for ephemeral events, // since command results are not returned for ephemeral events,
// remove the request from the postbox which is likely failing over and over // remove the request from the postbox which is likely failing over and over
if damus_state.postbox.remove_relayer(relay_id: nwc.relay.id, event_id: resp.req_id) { if damus_state.postbox.remove_relayer(relay_id: nwc.relay.id, event_id: resp.req_id) {
print("nwc: got response, removed \(resp.req_id) from the postbox [\(relay)]") print("nwc: got response, removed \(resp.req_id) from the postbox")
} else { } else {
print("nwc: \(resp.req_id) not found in the postbox, nothing to remove [\(relay)]") print("nwc: \(resp.req_id) not found in the postbox, nothing to remove")
} }
guard resp.response.error != nil else { if resp.response.error == nil {
print("nwc success: \(resp.response.result.debugDescription) [\(relay)]") nwc_success(zapcache: self.damus_state.zaps, evcache: self.damus_state.events, resp: resp)
nwc_success(state: self.damus_state, resp: resp)
return return
} }
print("nwc error: \(resp.response)")
nwc_error(zapcache: self.damus_state.zaps, evcache: self.damus_state.events, resp: resp) nwc_error(zapcache: self.damus_state.zaps, evcache: self.damus_state.events, resp: resp)
} }
} }
func handle_zap_event(_ ev: NostrEvent) { func handle_zap_event_with_zapper(profiles: Profiles, ev: NostrEvent, our_keypair: Keypair, zapper: String) {
process_zap_event(damus_state: damus_state, ev: ev) { zapres in guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else {
guard case .done(let zap) = zapres else { return } return
}
guard zap.target.pubkey == self.damus_state.keypair.pubkey else { damus_state.add_zap(zap: .zap(zap))
return
}
if !self.notifications.insert_zap(.zap(zap)) { guard zap.target.pubkey == our_keypair.pubkey else {
return return
} }
guard let new_bits = handle_last_events(new_events: self.notification_status.new_events, ev: ev, timeline: .notifications, shouldNotify: true) else { if !notifications.insert_zap(.zap(zap)) {
return return
} }
if self.damus_state.settings.zap_vibration { if handle_last_event(ev: ev, timeline: .notifications) {
if damus_state.settings.zap_vibration {
// Generate zap vibration // Generate zap vibration
zap_vibrate(zap_amount: zap.invoice.amount) zap_vibrate(zap_amount: zap.invoice.amount)
} }
if damus_state.settings.zap_notification {
if self.damus_state.settings.zap_notification {
// Create in-app local notification for zap received. // Create in-app local notification for zap received.
switch zap.target { create_in_app_zap_notification(profiles: profiles, zap: zap, evId: ev.referenced_ids.first?.id ?? "")
case .profile(let profile_id): }
create_in_app_profile_zap_notification(profiles: self.damus_state.profiles, zap: zap, profile_id: profile_id) }
case .note(let note_target):
create_in_app_event_zap_notification(profiles: self.damus_state.profiles, zap: zap, evId: note_target.note_id) return
} }
func handle_zap_event(_ ev: NostrEvent) {
// These are zap notifications
guard let ptag = event_tag(ev, name: "p") else {
return
}
let our_keypair = damus_state.keypair
if let local_zapper = damus_state.profiles.lookup_zapper(pubkey: ptag) {
handle_zap_event_with_zapper(profiles: self.damus_state.profiles, ev: ev, our_keypair: our_keypair, zapper: local_zapper)
return
}
guard let profile = damus_state.profiles.lookup(id: ptag) else {
return
}
guard let lnurl = profile.lnurl else {
return
}
Task {
guard let zapper = await fetch_zapper_from_lnurl(lnurl) else {
return
} }
self.notification_status.new_events = new_bits DispatchQueue.main.async {
self.damus_state.profiles.zappers[ptag] = zapper
self.handle_zap_event_with_zapper(profiles: self.damus_state.profiles, ev: ev, our_keypair: our_keypair, zapper: zapper)
}
} }
} }
@@ -371,12 +394,13 @@ class HomeModel {
/// Send the initial filters, just our contact list mostly /// Send the initial filters, just our contact list mostly
func send_initial_filters(relay_id: String) { func send_initial_filters(relay_id: String) {
let filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey]) var filter = NostrFilter.filter_contacts
let subscription = NostrSubscribe(filters: [filter], sub_id: init_subid) filter.authors = [self.damus_state.pubkey]
pool.send(.subscribe(subscription), to: [relay_id]) filter.limit = 1
pool.send(.subscribe(.init(filters: [filter], sub_id: init_subid)), to: [relay_id])
} }
/// After initial connection or reconnect, send subscription filters for the home timeline, DMs, and notifications
func send_home_filters(relay_id: String?) { func send_home_filters(relay_id: String?) {
// TODO: since times should be based on events from a specific relay // TODO: since times should be based on events from a specific relay
// perhaps we could mark this in the relay pool somehow // perhaps we could mark this in the relay pool somehow
@@ -384,19 +408,23 @@ class HomeModel {
var friends = damus_state.contacts.get_friend_list() var friends = damus_state.contacts.get_friend_list()
friends.append(damus_state.pubkey) friends.append(damus_state.pubkey)
var contacts_filter = NostrFilter(kinds: [.metadata]) var contacts_filter = NostrFilter.filter_kinds([NostrKind.metadata.rawValue])
contacts_filter.authors = friends contacts_filter.authors = friends
var our_contacts_filter = NostrFilter(kinds: [.contacts, .metadata]) var our_contacts_filter = NostrFilter.filter_kinds([NostrKind.contacts.rawValue, NostrKind.metadata.rawValue])
our_contacts_filter.authors = [damus_state.pubkey] our_contacts_filter.authors = [damus_state.pubkey]
var our_blocklist_filter = NostrFilter(kinds: [.list]) var our_blocklist_filter = NostrFilter.filter_kinds([NostrKind.list.rawValue])
our_blocklist_filter.parameter = ["mute"] our_blocklist_filter.parameter = ["mute"]
our_blocklist_filter.authors = [damus_state.pubkey] our_blocklist_filter.authors = [damus_state.pubkey]
var dms_filter = NostrFilter(kinds: [.dm]) var dms_filter = NostrFilter.filter_kinds([
NostrKind.dm.rawValue,
])
var our_dms_filter = NostrFilter(kinds: [.dm]) var our_dms_filter = NostrFilter.filter_kinds([
NostrKind.dm.rawValue,
])
// friends only?... // friends only?...
//dms_filter.authors = friends //dms_filter.authors = friends
@@ -405,27 +433,27 @@ class HomeModel {
our_dms_filter.authors = [ damus_state.pubkey ] our_dms_filter.authors = [ damus_state.pubkey ]
// TODO: separate likes? // TODO: separate likes?
var home_filter_kinds: [NostrKind] = [ var home_filter_kinds = [
.text, NostrKind.text.rawValue,
.boost NostrKind.boost.rawValue
] ]
if !damus_state.settings.onlyzaps_mode { if !damus_state.settings.onlyzaps_mode {
home_filter_kinds.append(.like) home_filter_kinds.append(NostrKind.like.rawValue)
} }
var home_filter = NostrFilter(kinds: home_filter_kinds) var home_filter = NostrFilter.filter_kinds(home_filter_kinds)
// include our pubkey as well even if we're not technically a friend // include our pubkey as well even if we're not technically a friend
home_filter.authors = friends home_filter.authors = friends
home_filter.limit = 500 home_filter.limit = 500
var notifications_filter_kinds: [NostrKind] = [ var notifications_filter_kinds = [
.text, NostrKind.text.rawValue,
.boost, NostrKind.boost.rawValue,
.zap, NostrKind.zap.rawValue,
] ]
if !damus_state.settings.onlyzaps_mode { if !damus_state.settings.onlyzaps_mode {
notifications_filter_kinds.append(.like) notifications_filter_kinds.append(NostrKind.like.rawValue)
} }
var notifications_filter = NostrFilter(kinds: notifications_filter_kinds) var notifications_filter = NostrFilter.filter_kinds(notifications_filter_kinds)
notifications_filter.pubkeys = [damus_state.pubkey] notifications_filter.pubkeys = [damus_state.pubkey]
notifications_filter.limit = 500 notifications_filter.limit = 500
@@ -441,7 +469,7 @@ class HomeModel {
notifications_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: notifications_filters) notifications_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: notifications_filters)
dms_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: dms_filters) dms_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: dms_filters)
//print_filters(relay_id: relay_id, filters: [home_filters, contacts_filters, notifications_filters, dms_filters]) print_filters(relay_id: relay_id, filters: [home_filters, contacts_filters, notifications_filters, dms_filters])
if let relay_id { if let relay_id {
pool.send(.subscribe(.init(filters: home_filters, sub_id: home_subid)), to: [relay_id]) pool.send(.subscribe(.init(filters: home_filters, sub_id: home_subid)), to: [relay_id])
@@ -524,8 +552,8 @@ class HomeModel {
@discardableResult @discardableResult
func handle_last_event(ev: NostrEvent, timeline: Timeline, shouldNotify: Bool = true) -> Bool { func handle_last_event(ev: NostrEvent, timeline: Timeline, shouldNotify: Bool = true) -> Bool {
if let new_bits = handle_last_events(new_events: self.notification_status.new_events, ev: ev, timeline: timeline, shouldNotify: shouldNotify) { if let new_bits = handle_last_events(new_events: self.new_events, ev: ev, timeline: timeline, shouldNotify: shouldNotify) {
self.notification_status.new_events = new_bits new_events = new_bits
return true return true
} else { } else {
return false return false
@@ -557,7 +585,7 @@ class HomeModel {
} }
func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) { func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) {
notification_status.new_events = notifs self.new_events = notifs
if damus_state.settings.dm_notification && ev.age < HomeModel.event_max_age_for_notification { if damus_state.settings.dm_notification && ev.age < HomeModel.event_max_age_for_notification {
let convo = ev.decrypted(privkey: self.damus_state.keypair.privkey) ?? NSLocalizedString("New encrypted direct message", comment: "Notification that the user has received a new direct message") let convo = ev.decrypted(privkey: self.damus_state.keypair.privkey) ?? NSLocalizedString("New encrypted direct message", comment: "Notification that the user has received a new direct message")
@@ -575,7 +603,7 @@ class HomeModel {
if !should_debounce_dms { if !should_debounce_dms {
self.incoming_dms.append(ev) self.incoming_dms.append(ev)
if let notifs = handle_incoming_dms(prev_events: notification_status.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) { if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
got_new_dm(notifs: notifs, ev: ev) got_new_dm(notifs: notifs, ev: ev)
} }
self.incoming_dms = [] self.incoming_dms = []
@@ -585,7 +613,7 @@ class HomeModel {
incoming_dms.append(ev) incoming_dms.append(ev)
dm_debouncer.debounce { [self] in dm_debouncer.debounce { [self] in
if let notifs = handle_incoming_dms(prev_events: notification_status.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) { if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
got_new_dm(notifs: notifs, ev: ev) got_new_dm(notifs: notifs, ev: ev)
} }
self.incoming_dms = [] self.incoming_dms = []
@@ -703,7 +731,7 @@ func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: P
var old_nip05: String? = nil var old_nip05: String? = nil
if let mprof = profiles.lookup_with_timestamp(id: ev.pubkey) { if let mprof = profiles.lookup_with_timestamp(id: ev.pubkey) {
old_nip05 = mprof.profile.nip05 old_nip05 = mprof.profile.nip05
if mprof.event.created_at > ev.created_at { if mprof.timestamp > ev.created_at {
// skip if we already have an newer profile // skip if we already have an newer profile
return return
} }
@@ -720,7 +748,7 @@ func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: P
print("validated nip05 for '\(nip05)'") print("validated nip05 for '\(nip05)'")
} }
Task { @MainActor in DispatchQueue.main.async {
profiles.validated[ev.pubkey] = validated profiles.validated[ev.pubkey] = validated
profiles.nip05_pubkey[nip05] = ev.pubkey profiles.nip05_pubkey[nip05] = ev.pubkey
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile)) notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
@@ -752,7 +780,7 @@ func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping (
switch validated { switch validated {
case .unknown: case .unknown:
Task.detached(priority: .medium) { Task {
let result = validate_event(ev: ev) let result = validate_event(ev: ev)
DispatchQueue.main.async { DispatchQueue.main.async {
@@ -773,11 +801,11 @@ func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping (
} }
} }
func process_metadata_event(events: EventCache, our_pubkey: String, profiles: Profiles, ev: NostrEvent, completion: ((Profile?) -> Void)? = nil) { func process_metadata_event(events: EventCache, our_pubkey: String, profiles: Profiles, ev: NostrEvent, completion: (() -> Void)? = nil) {
guard_valid_event(events: events, ev: ev) { guard_valid_event(events: events, ev: ev) {
DispatchQueue.global(qos: .background).async { DispatchQueue.global(qos: .background).async {
guard let profile: Profile = decode_data(Data(ev.content.utf8)) else { guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
completion?(nil) completion?()
return return
} }
@@ -785,7 +813,7 @@ func process_metadata_event(events: EventCache, our_pubkey: String, profiles: Pr
DispatchQueue.main.async { DispatchQueue.main.async {
process_metadata_profile(our_pubkey: our_pubkey, profiles: profiles, profile: profile, ev: ev) process_metadata_profile(our_pubkey: our_pubkey, profiles: profiles, profile: profile, ev: ev)
completion?(profile) completion?()
} }
} }
} }
@@ -858,7 +886,6 @@ func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
if changed { if changed {
save_bootstrap_relays(pubkey: state.pubkey, relays: Array(new)) save_bootstrap_relays(pubkey: state.pubkey, relays: Array(new))
state.pool.connect()
notify(.relays_changed, ()) notify(.relays_changed, ())
} }
} }
@@ -1076,12 +1103,13 @@ func zap_notification_title(_ zap: Zap) -> String {
} }
func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String { func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String {
let src = zap.request.ev let src = zap.private_request ?? zap.request.ev
let pk = zap.is_anon ? ANON_PUBKEY : src.pubkey let anon = event_is_anonymous(ev: src)
let pk = anon ? "anon" : src.pubkey
let profile = profiles.lookup(id: pk) let profile = profiles.lookup(id: pk)
let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0)) let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0))
let formattedSats = format_msats_abbrev(zap.invoice.amount) let formattedSats = format_msats_abbrev(zap.invoice.amount)
let name = Profile.displayName(profile: profile, pubkey: pk).display_name.truncate(maxLength: 50) let name = Profile.displayName(profile: profile, pubkey: pk).display_name
if src.content.isEmpty { if src.content.isEmpty {
let format = localizedStringFormat(key: "zap_notification_no_message", locale: locale) let format = localizedStringFormat(key: "zap_notification_no_message", locale: locale)
@@ -1092,28 +1120,7 @@ func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale
} }
} }
func create_in_app_profile_zap_notification(profiles: Profiles, zap: Zap, locale: Locale = Locale.current, profile_id: String) { func create_in_app_zap_notification(profiles: Profiles, zap: Zap, locale: Locale = Locale.current, evId: String) {
let content = UNMutableNotificationContent()
content.title = zap_notification_title(zap)
content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale)
content.sound = UNNotificationSound.default
content.userInfo = LossyLocalNotification(type: .profile_zap, event_id: profile_id).to_user_info()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: "myZapNotification", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Error: \(error)")
} else {
print("Local notification scheduled")
}
}
}
func create_in_app_event_zap_notification(profiles: Profiles, zap: Zap, locale: Locale = Locale.current, evId: String) {
let content = UNMutableNotificationContent() let content = UNMutableNotificationContent()
content.title = zap_notification_title(zap) content.title = zap_notification_title(zap)
@@ -1165,15 +1172,13 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) {
create_local_notification(profiles: damus_state.profiles, notify: notify ) create_local_notification(profiles: damus_state.profiles, notify: notify )
} }
} else if type == .boost && damus_state.settings.repost_notification, let inner_ev = ev.get_inner_event(cache: damus_state.events) { } else if type == .boost && damus_state.settings.repost_notification, let inner_ev = ev.get_inner_event(cache: damus_state.events) {
let content = NSAttributedString(render_note_content(ev: inner_ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content.attributed).string let notify = LocalNotification(type: .repost, event: ev, target: inner_ev, content: inner_ev.content)
let notify = LocalNotification(type: .repost, event: ev, target: inner_ev, content: content)
create_local_notification(profiles: damus_state.profiles, notify: notify) create_local_notification(profiles: damus_state.profiles, notify: notify)
} else if type == .like && damus_state.settings.like_notification, } else if type == .like && damus_state.settings.like_notification,
let evid = ev.referenced_ids.last?.ref_id, let evid = ev.referenced_ids.last?.ref_id,
let liked_event = damus_state.events.lookup(evid) let liked_event = damus_state.events.lookup(evid)
{ {
let content = NSAttributedString(render_note_content(ev: liked_event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content.attributed).string let notify = LocalNotification(type: .like, event: ev, target: liked_event, content: liked_event.content)
let notify = LocalNotification(type: .like, event: ev, target: liked_event, content: content)
create_local_notification(profiles: damus_state.profiles, notify: notify) create_local_notification(profiles: damus_state.profiles, notify: notify)
} }
@@ -1194,12 +1199,12 @@ func create_local_notification(profiles: Profiles, notify: LocalNotification) {
title = String(format: NSLocalizedString("Reposted by %@", comment: "Reposted by heading in local notification"), displayName) title = String(format: NSLocalizedString("Reposted by %@", comment: "Reposted by heading in local notification"), displayName)
identifier = "myBoostNotification" identifier = "myBoostNotification"
case .like: case .like:
title = String(format: NSLocalizedString("%@ reacted with %@", comment: "Reacted by heading in local notification"), displayName, to_reaction_emoji(ev: notify.event) ?? "") title = String(format: NSLocalizedString("%@ reacted with %@", comment: "Reacted by heading in local notification"), displayName, notify.event.content)
identifier = "myLikeNotification" identifier = "myLikeNotification"
case .dm: case .dm:
title = displayName title = displayName
identifier = "myDMNotification" identifier = "myDMNotification"
case .zap, .profile_zap: case .zap:
// not handled here // not handled here
break break
} }
@@ -1221,75 +1226,3 @@ func create_local_notification(profiles: Profiles, notify: LocalNotification) {
} }
} }
enum ProcessZapResult {
case already_processed(Zap)
case done(Zap)
case failed
}
func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @escaping (ProcessZapResult) -> Void) {
// These are zap notifications
guard let ptag = event_tag(ev, name: "p") else {
completion(.failed)
return
}
// just return the zap if we already have it
if let zap = damus_state.zaps.zaps[ev.id], case .zap(let z) = zap {
completion(.already_processed(z))
return
}
if let local_zapper = damus_state.profiles.lookup_zapper(pubkey: ptag) {
guard let zap = process_zap_event_with_zapper(damus_state: damus_state, ev: ev, zapper: local_zapper) else {
completion(.failed)
return
}
damus_state.add_zap(zap: .zap(zap))
completion(.done(zap))
return
}
guard let profile = damus_state.profiles.lookup(id: ptag) else {
completion(.failed)
return
}
guard let lnurl = profile.lnurl else {
completion(.failed)
return
}
Task {
guard let zapper = await fetch_zapper_from_lnurl(lnurl) else {
completion(.failed)
return
}
DispatchQueue.main.async {
damus_state.profiles.zappers[ptag] = zapper
guard let zap = process_zap_event_with_zapper(damus_state: damus_state, ev: ev, zapper: zapper) else {
completion(.failed)
return
}
damus_state.add_zap(zap: .zap(zap))
completion(.done(zap))
}
}
}
fileprivate func process_zap_event_with_zapper(damus_state: DamusState, ev: NostrEvent, zapper: String) -> Zap? {
let our_keypair = damus_state.keypair
guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else {
return nil
}
damus_state.add_zap(zap: .zap(zap))
return zap
}
+4 -7
View File
@@ -495,17 +495,14 @@ func make_post_tags(post_blocks: [PostBlock], tags: [[String]], silent_mentions:
continue continue
} }
if find_tag_ref(type: ref.key, id: ref.ref_id, tags: tags) != nil { if let ind = find_tag_ref(type: ref.key, id: ref.ref_id, tags: tags) {
// Mention index is nil because indexed mentions from NIP-08 is deprecated. let mention = Mention(index: ind, type: mention_type, ref: ref)
// It has been replaced with NIP-27 text note references with nostr: prefixed URIs.
let mention = Mention(index: nil, type: mention_type, ref: ref)
let block = Block.mention(mention) let block = Block.mention(mention)
blocks.append(block) blocks.append(block)
} else { } else {
let ind = new_tags.count
new_tags.append(refid_to_tag(ref)) new_tags.append(refid_to_tag(ref))
// Mention index is nil because indexed mentions from NIP-08 is deprecated. let mention = Mention(index: ind, type: mention_type, ref: ref)
// It has been replaced with NIP-27 text note references with nostr: prefixed URIs.
let mention = Mention(index: nil, type: mention_type, ref: ref)
let block = Block.mention(mention) let block = Block.mention(mention)
blocks.append(block) blocks.append(block)
} }
@@ -1,12 +0,0 @@
//
// NotificationStatusModel.swift
// damus
//
// Created by William Casarin on 2023-06-23.
//
import Foundation
class NotificationStatusModel: ObservableObject {
@Published var new_events: NewEventsBits = NewEventsBits()
}
+5 -5
View File
@@ -21,12 +21,12 @@ class ZapGroup {
} }
func zap_requests() -> [NostrEvent] { func zap_requests() -> [NostrEvent] {
zaps.map { z in z.request.ev } zaps.map { z in z.request }
} }
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool { func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
for zap in zaps { for zap in zaps {
if !isIncluded(zap.request.ev) { if !isIncluded(zap.request) {
return true return true
} }
} }
@@ -35,7 +35,7 @@ class ZapGroup {
} }
func filter(_ isIncluded: (NostrEvent) -> Bool) -> ZapGroup? { func filter(_ isIncluded: (NostrEvent) -> Bool) -> ZapGroup? {
let new_zaps = zaps.filter { isIncluded($0.request.ev) } let new_zaps = zaps.filter { isIncluded($0.request) }
guard new_zaps.count > 0 else { guard new_zaps.count > 0 else {
return nil return nil
} }
@@ -60,8 +60,8 @@ class ZapGroup {
msat_total += zap.amount msat_total += zap.amount
if !zappers.contains(zap.request.ev.pubkey) { if !zappers.contains(zap.request.pubkey) {
zappers.insert(zap.request.ev.pubkey) zappers.insert(zap.request.pubkey)
} }
return true return true
+3 -3
View File
@@ -150,7 +150,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
} }
for zap in incoming_zaps { for zap in incoming_zaps {
pks.insert(zap.request.ev.pubkey) pks.insert(zap.request.pubkey)
} }
return Array(pks) return Array(pks)
@@ -307,7 +307,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
changed = changed || incoming_events.count != count changed = changed || incoming_events.count != count
count = profile_zaps.zaps.count count = profile_zaps.zaps.count
profile_zaps.zaps = profile_zaps.zaps.filter { zap in isIncluded(zap.request.ev) } profile_zaps.zaps = profile_zaps.zaps.filter { zap in isIncluded(zap.request) }
changed = changed || profile_zaps.zaps.count != count changed = changed || profile_zaps.zaps.count != count
for el in reactions { for el in reactions {
@@ -325,7 +325,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
for el in zaps { for el in zaps {
count = el.value.zaps.count count = el.value.zaps.count
el.value.zaps = el.value.zaps.filter { el.value.zaps = el.value.zaps.filter {
isIncluded($0.request.ev) isIncluded($0.request)
} }
changed = changed || el.value.zaps.count != count changed = changed || el.value.zaps.count != count
} }
+1
View File
@@ -21,6 +21,7 @@ struct NostrPost {
} }
} }
// TODO: parse nostr:{e,p}:pubkey uris as well
func parse_post_mention_type(_ p: Parser) -> MentionType? { func parse_post_mention_type(_ p: Parser) -> MentionType? {
if parse_char(p, "@") { if parse_char(p, "@") {
return .pubkey return .pubkey
+10 -3
View File
@@ -69,14 +69,21 @@ class ProfileModel: ObservableObject, Equatable {
} }
func subscribe() { func subscribe() {
var text_filter = NostrFilter(kinds: [.text, .chat]) var text_filter = NostrFilter.filter_kinds([
NostrKind.text.rawValue,
NostrKind.chat.rawValue,
])
var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost]) var profile_filter = NostrFilter.filter_kinds([
NostrKind.contacts.rawValue,
NostrKind.metadata.rawValue,
NostrKind.boost.rawValue,
])
profile_filter.authors = [pubkey] profile_filter.authors = [pubkey]
text_filter.authors = [pubkey] text_filter.authors = [pubkey]
text_filter.limit = 50 text_filter.limit = 500
print("subscribing to profile \(pubkey) with sub_id \(sub_id)") print("subscribing to profile \(pubkey) with sub_id \(sub_id)")
print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]]) print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]])
+32 -9
View File
@@ -18,7 +18,6 @@ class SearchHomeModel: ObservableObject {
let base_subid = UUID().description let base_subid = UUID().description
let profiles_subid = UUID().description let profiles_subid = UUID().description
let limit: UInt32 = 250 let limit: UInt32 = 250
//let multiple_events_per_pubkey: Bool = false
init(damus_state: DamusState) { init(damus_state: DamusState) {
self.damus_state = damus_state self.damus_state = damus_state
@@ -28,7 +27,7 @@ class SearchHomeModel: ObservableObject {
} }
func get_base_filter() -> NostrFilter { func get_base_filter() -> NostrFilter {
var filter = NostrFilter(kinds: [.text, .chat]) var filter = NostrFilter.filter_kinds([NostrKind.text.rawValue, NostrKind.chat.rawValue])
filter.limit = self.limit filter.limit = self.limit
filter.until = Int64(Date.now.timeIntervalSince1970) filter.until = Int64(Date.now.timeIntervalSince1970)
return filter return filter
@@ -61,7 +60,7 @@ class SearchHomeModel: ObservableObject {
return return
} }
if ev.is_textlike && should_show_event(contacts: damus_state.contacts, ev: ev) && !ev.is_reply(nil) { if ev.is_textlike && should_show_event(contacts: damus_state.contacts, ev: ev) && !ev.is_reply(nil) {
if !damus_state.settings.multiple_events_per_pubkey && seen_pubkey.contains(ev.pubkey) { if seen_pubkey.contains(ev.pubkey) {
return return
} }
seen_pubkey.insert(ev.pubkey) seen_pubkey.insert(ev.pubkey)
@@ -91,6 +90,20 @@ class SearchHomeModel: ObservableObject {
} }
} }
func find_profiles_to_fetch_pk(profiles: Profiles, event_pubkeys: [String]) -> [String] {
var pubkeys = Set<String>()
for pk in event_pubkeys {
if profiles.lookup(id: pk) != nil {
continue
}
pubkeys.insert(pk)
}
return Array(pubkeys)
}
func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad, cache: EventCache) -> [String] { func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad, cache: EventCache) -> [String] {
switch load { switch load {
case .from_events(let events): case .from_events(let events):
@@ -101,7 +114,17 @@ func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad, cache: Even
} }
func find_profiles_to_fetch_from_keys(profiles: Profiles, pks: [String]) -> [String] { func find_profiles_to_fetch_from_keys(profiles: Profiles, pks: [String]) -> [String] {
Array(Set(pks.filter { pk in !profiles.has_fresh_profile(id: pk) })) var pubkeys = Set<String>()
for pk in pks {
if profiles.lookup(id: pk) != nil {
continue
}
pubkeys.insert(pk)
}
return Array(pubkeys)
} }
func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent], cache: EventCache) -> [String] { func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent], cache: EventCache) -> [String] {
@@ -109,11 +132,11 @@ func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent]
for ev in events { for ev in events {
// lookup profiles from boosted events // lookup profiles from boosted events
if ev.known_kind == .boost, let bev = ev.get_inner_event(cache: cache), !profiles.has_fresh_profile(id: bev.pubkey) { if ev.known_kind == .boost, let bev = ev.get_inner_event(cache: cache), profiles.lookup(id: bev.pubkey) == nil {
pubkeys.insert(bev.pubkey) pubkeys.insert(bev.pubkey)
} }
if !profiles.has_fresh_profile(id: ev.pubkey) { if profiles.lookup(id: ev.pubkey) == nil {
pubkeys.insert(ev.pubkey) pubkeys.insert(ev.pubkey)
} }
} }
@@ -127,16 +150,16 @@ enum PubkeysToLoad {
} }
func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad, damus_state: DamusState) { func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad, damus_state: DamusState) {
var filter = NostrFilter.filter_profiles
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load, cache: damus_state.events) let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load, cache: damus_state.events)
filter.authors = authors
guard !authors.isEmpty else { guard !authors.isEmpty else {
return return
} }
print("loading \(authors.count) profiles from \(relay_id)") print("loading \(authors.count) profiles from \(relay_id)")
let filter = NostrFilter(kinds: [.metadata],
authors: authors)
damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { sub_id, conn_ev in damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { sub_id, conn_ev in
let (sid, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: conn_ev) { sub_id, ev in let (sid, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: conn_ev) { sub_id, ev in
guard sub_id == profiles_subid else { guard sub_id == profiles_subid else {
+1 -1
View File
@@ -34,7 +34,7 @@ class SearchModel: ObservableObject {
func subscribe() { func subscribe() {
// since 1 month // since 1 month
search.limit = self.limit search.limit = self.limit
search.kinds = [.text, .like] search.kinds = [NostrKind.text.rawValue, NostrKind.like.rawValue]
//likes_filter.ids = ref_events.referenced_ids! //likes_filter.ids = ref_events.referenced_ids!
+3 -20
View File
@@ -10,21 +10,15 @@ import Foundation
/// manages the lifetime of a thread /// manages the lifetime of a thread
class ThreadModel: ObservableObject { class ThreadModel: ObservableObject {
@Published var event: NostrEvent @Published var event: NostrEvent
let original_event: NostrEvent
var event_map: Set<NostrEvent> var event_map: Set<NostrEvent>
init(event: NostrEvent, damus_state: DamusState) { init(event: NostrEvent, damus_state: DamusState) {
self.damus_state = damus_state self.damus_state = damus_state
self.event_map = Set() self.event_map = Set()
self.event = event self.event = event
self.original_event = event
add_event(event) add_event(event)
} }
var is_original: Bool {
return original_event.id == event.id
}
let damus_state: DamusState let damus_state: DamusState
let profiles_subid = UUID().description let profiles_subid = UUID().description
@@ -62,16 +56,16 @@ class ThreadModel: ObservableObject {
let thread_id = event.thread_id(privkey: nil) let thread_id = event.thread_id(privkey: nil)
ref_events.referenced_ids = [thread_id, event.id] ref_events.referenced_ids = [thread_id, event.id]
ref_events.kinds = [.text] ref_events.kinds = [NostrKind.text.rawValue]
ref_events.limit = 1000 ref_events.limit = 1000
event_filter.ids = [thread_id, event.id] event_filter.ids = [thread_id, event.id]
meta_events.referenced_ids = [event.id] meta_events.referenced_ids = [event.id]
var kinds: [NostrKind] = [.zap, .text, .boost] var kinds = [NostrKind.zap.rawValue, NostrKind.text.rawValue, NostrKind.boost.rawValue]
if !damus_state.settings.onlyzaps_mode { if !damus_state.settings.onlyzaps_mode {
kinds.append(.like) kinds.append(NostrKind.like.rawValue)
} }
meta_events.kinds = kinds meta_events.kinds = kinds
@@ -107,10 +101,6 @@ class ThreadModel: ObservableObject {
if ev.known_kind == .metadata { if ev.known_kind == .metadata {
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev) process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
} else if ev.known_kind == .zap {
process_zap_event(damus_state: damus_state, ev: ev) { zap in
}
} else if ev.is_textlike { } else if ev.is_textlike {
self.add_event(ev) self.add_event(ev)
} }
@@ -126,10 +116,3 @@ class ThreadModel: ObservableObject {
} }
} }
func get_top_zap(events: EventCache, evid: String) -> Zapping? {
return events.get_cache_data(evid).zaps_model.zaps.first(where: { zap in
!zap.request.marked_hidden
})
}
+3 -19
View File
@@ -19,11 +19,8 @@ let fallback_zap_amount = 1000
if let loaded = UserDefaults.standard.object(forKey: self.key) as? T { if let loaded = UserDefaults.standard.object(forKey: self.key) as? T {
self.value = loaded self.value = loaded
} else if let loaded = UserDefaults.standard.object(forKey: key) as? T { } else if let loaded = UserDefaults.standard.object(forKey: key) as? T {
// If pubkey-scoped setting does not exist but the deprecated non-pubkey-scoped setting does, // try to load from deprecated non-pubkey-keyed setting
// migrate the deprecated setting into the pubkey-scoped one and delete the deprecated one.
self.value = loaded self.value = loaded
UserDefaults.standard.set(loaded, forKey: self.key)
UserDefaults.standard.removeObject(forKey: key)
} else { } else {
self.value = default_value self.value = default_value
} }
@@ -51,11 +48,8 @@ let fallback_zap_amount = 1000
if let loaded = UserDefaults.standard.string(forKey: self.key), let val = T.init(from: loaded) { if let loaded = UserDefaults.standard.string(forKey: self.key), let val = T.init(from: loaded) {
self.value = val self.value = val
} else if let loaded = UserDefaults.standard.string(forKey: key), let val = T.init(from: loaded) { } else if let loaded = UserDefaults.standard.string(forKey: key), let val = T.init(from: loaded) {
// If pubkey-scoped setting does not exist but the deprecated non-pubkey-scoped setting does, // try to load from deprecated non-pubkey-keyed setting
// migrate the deprecated setting into the pubkey-scoped one and delete the deprecated one.
self.value = val self.value = val
UserDefaults.standard.set(val.to_string(), forKey: self.key)
UserDefaults.standard.removeObject(forKey: key)
} else { } else {
self.value = default_value self.value = default_value
} }
@@ -84,7 +78,7 @@ class UserSettingsStore: ObservableObject {
@StringSetting(key: "default_media_uploader", default_value: .nostrBuild) @StringSetting(key: "default_media_uploader", default_value: .nostrBuild)
var default_media_uploader: MediaUploader var default_media_uploader: MediaUploader
@Setting(key: "show_wallet_selector", default_value: false) @Setting(key: "show_wallet_selector", default_value: true)
var show_wallet_selector: Bool var show_wallet_selector: Bool
@Setting(key: "left_handed", default_value: false) @Setting(key: "left_handed", default_value: false)
@@ -126,10 +120,6 @@ class UserSettingsStore: ObservableObject {
@Setting(key: "truncate_timeline_text", default_value: false) @Setting(key: "truncate_timeline_text", default_value: false)
var truncate_timeline_text: Bool var truncate_timeline_text: Bool
/// Nozaps mode gimps note zapping to fit into apple's content-tipping guidelines. It can not be configurable to end-users on the app store
@Setting(key: "nozaps", default_value: true)
var nozaps: Bool
@Setting(key: "truncate_mention_text", default_value: true) @Setting(key: "truncate_mention_text", default_value: true)
var truncate_mention_text: Bool var truncate_mention_text: Bool
@@ -142,18 +132,12 @@ class UserSettingsStore: ObservableObject {
@Setting(key: "show_only_preferred_languages", default_value: false) @Setting(key: "show_only_preferred_languages", default_value: false)
var show_only_preferred_languages: Bool var show_only_preferred_languages: Bool
@Setting(key: "multiple_events_per_pubkey", default_value: false)
var multiple_events_per_pubkey: Bool
@Setting(key: "onlyzaps_mode", default_value: false) @Setting(key: "onlyzaps_mode", default_value: false)
var onlyzaps_mode: Bool var onlyzaps_mode: Bool
@Setting(key: "disable_animation", default_value: UIAccessibility.isReduceMotionEnabled) @Setting(key: "disable_animation", default_value: UIAccessibility.isReduceMotionEnabled)
var disable_animation: Bool var disable_animation: Bool
@Setting(key: "donation_percent", default_value: 0)
var donation_percent: Int
// Helper for inverse of disable_animation. // Helper for inverse of disable_animation.
// disable_animation was introduced as a setting first, but it's more natural for the settings UI to show the inverse. // disable_animation was introduced as a setting first, but it's more natural for the settings UI to show the inverse.
var enable_animation: Bool { var enable_animation: Bool {
+6 -10
View File
@@ -14,17 +14,14 @@ enum WalletConnectState {
} }
class WalletModel: ObservableObject { class WalletModel: ObservableObject {
var settings: UserSettingsStore let settings: UserSettingsStore?
private(set) var previous_state: WalletConnectState private(set) var previous_state: WalletConnectState
var inital_percent: Int
@Published private(set) var connect_state: WalletConnectState @Published private(set) var connect_state: WalletConnectState
init(state: WalletConnectState, settings: UserSettingsStore) { init() {
self.connect_state = state self.connect_state = .none
self.previous_state = .none self.previous_state = .none
self.settings = settings self.settings = nil
self.inital_percent = settings.donation_percent
} }
init(settings: UserSettingsStore) { init(settings: UserSettingsStore) {
@@ -37,7 +34,6 @@ class WalletModel: ObservableObject {
self.previous_state = .none self.previous_state = .none
self.connect_state = .none self.connect_state = .none
} }
self.inital_percent = settings.donation_percent
} }
func cancel() { func cancel() {
@@ -46,7 +42,7 @@ class WalletModel: ObservableObject {
} }
func disconnect() { func disconnect() {
self.settings.nostr_wallet_connect = nil self.settings?.nostr_wallet_connect = nil
self.connect_state = .none self.connect_state = .none
self.previous_state = .none self.previous_state = .none
} }
@@ -56,7 +52,7 @@ class WalletModel: ObservableObject {
} }
func connect(_ nwc: WalletConnectURL) { func connect(_ nwc: WalletConnectURL) {
self.settings.nostr_wallet_connect = nwc.to_url().absoluteString self.settings?.nostr_wallet_connect = nwc.to_url().absoluteString
notify(.attached_wallet, nwc) notify(.attached_wallet, nwc)
self.connect_state = .existing(nwc) self.connect_state = .existing(nwc)
self.previous_state = .existing(nwc) self.previous_state = .existing(nwc)
-13
View File
@@ -1,13 +0,0 @@
//
// ZapButtonModel.swift
// damus
//
// Created by Terry Yiu on 6/1/23.
//
import Foundation
class ZapButtonModel: ObservableObject {
var invoice: String? = nil
@Published var zapping: String = ""
}
-29
View File
@@ -1,29 +0,0 @@
//
// CustomizeZapModel.swift
// damus
//
// Created by William Casarin on 2023-06-22.
//
import Foundation
class CustomizeZapModel: ObservableObject {
@Published var comment: String = ""
@Published var custom_amount: String = ""
@Published var custom_amount_sats: Int? = nil
@Published var zap_type: ZapType = .pub
@Published var invoice: String = ""
@Published var error: String? = nil
@Published var zapping: Bool = false
@Published var show_zap_types: Bool = false
init() {
}
func set_defaults(settings: UserSettingsStore) {
self.zap_type = settings.default_zap_type
self.custom_amount = String(settings.default_zap_amount)
self.custom_amount_sats = settings.default_zap_amount
}
}
+7 -2
View File
@@ -24,7 +24,7 @@ class ZapsModel: ObservableObject {
} }
func subscribe() { func subscribe() {
var filter = NostrFilter(kinds: [.zap]) var filter = NostrFilter.filter_kinds([NostrKind.zap.rawValue])
switch target { switch target {
case .profile(let profile_id): case .profile(let profile_id):
filter.pubkeys = [profile_id] filter.pubkeys = [profile_id]
@@ -53,13 +53,18 @@ class ZapsModel: ObservableObject {
case .notice: case .notice:
break break
case .eose: case .eose:
let events = state.events.lookup_zaps(target: target).map { $0.request.ev } let events = state.events.lookup_zaps(target: target).map { $0.request }
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state) load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state)
case .event(_, let ev): case .event(_, let ev):
guard ev.kind == 9735 else { guard ev.kind == 9735 else {
return return
} }
if let zap = state.zaps.zaps[ev.id] {
state.events.store_zap(zap: zap)
return
}
guard let zapper = state.profiles.lookup_zapper(pubkey: target.pubkey) else { guard let zapper = state.profiles.lookup_zapper(pubkey: target.pubkey) else {
return return
} }
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22E261" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="PersistedProfile" representedClassName="PersistedProfile" syncable="YES">
<attribute name="about" optional="YES" attributeType="String"/>
<attribute name="banner" optional="YES" attributeType="String"/>
<attribute name="damus_donation" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="display_name" optional="YES" attributeType="String"/>
<attribute name="id" optional="YES" attributeType="String"/>
<attribute name="last_update" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="lud06" optional="YES" attributeType="String"/>
<attribute name="lud16" optional="YES" attributeType="String"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="network_pull_date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="nip05" optional="YES" attributeType="String"/>
<attribute name="picture" optional="YES" attributeType="String"/>
<attribute name="website" optional="YES" attributeType="String"/>
</entity>
</model>
@@ -1,39 +0,0 @@
//
// PersistedProfile.swift
// damus
//
// Created by Bryan Montz on 4/30/23.
//
import Foundation
import CoreData
@objc(PersistedProfile)
final class PersistedProfile: NSManagedObject {
@NSManaged var id: String?
@NSManaged var name: String?
@NSManaged var display_name: String?
@NSManaged var about: String?
@NSManaged var picture: String?
@NSManaged var banner: String?
@NSManaged var website: String?
@NSManaged var lud06: String?
@NSManaged var lud16: String?
@NSManaged var nip05: String?
@NSManaged var damus_donation: Int16
@NSManaged var last_update: Date? // The date that the profile was last updated by the user
@NSManaged var network_pull_date: Date? // The date we got this profile from a relay (for staleness checking)
func copyValues(from profile: Profile) {
name = profile.name
display_name = profile.display_name
about = profile.about
picture = profile.picture
banner = profile.banner
website = profile.website
lud06 = profile.lud06
lud16 = profile.lud16
nip05 = profile.nip05
damus_donation = profile.damus_donation != nil ? Int16(profile.damus_donation!) : 0
}
}
+2 -29
View File
@@ -10,7 +10,7 @@ import Foundation
class Profile: Codable { class Profile: Codable {
var value: [String: AnyCodable] var value: [String: AnyCodable]
init (name: String?, display_name: String?, about: String?, picture: String?, banner: String?, website: String?, lud06: String?, lud16: String?, nip05: String?, damus_donation: Int?) { init (name: String?, display_name: String?, about: String?, picture: String?, banner: String?, website: String?, lud06: String?, lud16: String?, nip05: String?) {
self.value = [:] self.value = [:]
self.name = name self.name = name
self.display_name = display_name self.display_name = display_name
@@ -21,30 +21,12 @@ class Profile: Codable {
self.lud06 = lud06 self.lud06 = lud06
self.lud16 = lud16 self.lud16 = lud16
self.nip05 = nip05 self.nip05 = nip05
self.damus_donation = damus_donation
}
convenience init(persisted_profile: PersistedProfile) {
self.init(name: persisted_profile.name,
display_name: persisted_profile.display_name,
about: persisted_profile.about,
picture: persisted_profile.picture,
banner: persisted_profile.banner,
website: persisted_profile.website,
lud06: persisted_profile.lud06,
lud16: persisted_profile.lud16,
nip05: persisted_profile.nip05,
damus_donation: Int(persisted_profile.damus_donation))
} }
private func str(_ str: String) -> String? { private func str(_ str: String) -> String? {
return get_val(str) return get_val(str)
} }
private func int(_ key: String) -> Int? {
return get_val(key)
}
private func get_val<T>(_ v: String) -> T? { private func get_val<T>(_ v: String) -> T? {
guard let val = self.value[v] else{ guard let val = self.value[v] else{
return nil return nil
@@ -70,10 +52,6 @@ class Profile: Codable {
set_val(key, val) set_val(key, val)
} }
private func set_int(_ key: String, _ val: Int?) {
set_val(key, val)
}
var reactions: Bool? { var reactions: Bool? {
get { return get_val("reactions"); } get { return get_val("reactions"); }
set(s) { set_val("reactions", s) } set(s) { set_val("reactions", s) }
@@ -99,11 +77,6 @@ class Profile: Codable {
set(s) { set_str("about", s) } set(s) { set_str("about", s) }
} }
var damus_donation: Int? {
get { return int("damus_donation_v2"); }
set(s) { set_int("damus_donation_v2", s) }
}
var picture: String? { var picture: String? {
get { return str("picture"); } get { return str("picture"); }
set(s) { set_str("picture", s) } set(s) { set_str("picture", s) }
@@ -207,7 +180,7 @@ class Profile: Codable {
} }
func make_test_profile() -> Profile { func make_test_profile() -> Profile {
return Profile(name: "jb55", display_name: "Will", about: "Its a me", picture: "https://cdn.jb55.com/img/red-me.jpg", banner: "https://pbs.twimg.com/profile_banners/9918032/1531711830/600x200", website: "jb55.com", lud06: "jb55@jb55.com", lud16: nil, nip05: "jb55@jb55.com", damus_donation: 1) return Profile(name: "jb55", display_name: "Will", about: "Its a me", picture: "https://cdn.jb55.com/img/red-me.jpg", banner: "https://pbs.twimg.com/profile_banners/9918032/1531711830/600x200", website: "jb55.com", lud06: "jb55@jb55.com", lud16: nil, nip05: "jb55@jb55.com")
} }
func make_ln_url(_ str: String?) -> URL? { func make_ln_url(_ str: String?) -> URL? {
+3 -25
View File
@@ -492,11 +492,11 @@ func make_boost_event(pubkey: String, privkey: String, boosted: NostrEvent) -> N
return ev return ev
} }
func make_like_event(pubkey: String, privkey: String, liked: NostrEvent, content: String = "🤙") -> NostrEvent { func make_like_event(pubkey: String, privkey: String, liked: NostrEvent) -> NostrEvent {
var tags: [[String]] = liked.tags.filter { tag in tag.count >= 2 && (tag[0] == "e" || tag[0] == "p") } var tags: [[String]] = liked.tags.filter { tag in tag.count >= 2 && (tag[0] == "e" || tag[0] == "p") }
tags.append(["e", liked.id]) tags.append(["e", liked.id])
tags.append(["p", liked.pubkey]) tags.append(["p", liked.pubkey])
let ev = NostrEvent(content: content, pubkey: pubkey, kind: 7, tags: tags) let ev = NostrEvent(content: "🤙", pubkey: pubkey, kind: 7, tags: tags)
ev.calculate_id() ev.calculate_id()
ev.sign(privkey: privkey) ev.sign(privkey: privkey)
@@ -601,7 +601,7 @@ enum MakeZapRequest {
var private_inner_request: ZapRequest { var private_inner_request: ZapRequest {
switch self { switch self {
case .priv(_, let pzr): case .priv(let _, let pzr):
return pzr.req return pzr.req
case .normal(let zr): case .normal(let zr):
return zr return zr
@@ -966,28 +966,6 @@ func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? {
return nil return nil
} }
/**
Transforms a `NostrEvent` of known kind `NostrKind.like`to a human-readable emoji.
If the known kind is not a `NostrKind.like`, it will return `nil`.
If the event content is an empty string or `+`, it will map that to a heart emoji.
If the event content is a "-", it will map that to a dislike 👎 emoji.
Otherwise, it will return the event content at face value without transforming it.
*/
func to_reaction_emoji(ev: NostrEvent) -> String? {
guard ev.known_kind == NostrKind.like else {
return nil
}
switch ev.content {
case "", "+":
return "❤️"
case "-":
return "👎"
default:
return ev.content
}
}
extension [ReferencedId] { extension [ReferencedId] {
var pRefs: [ReferencedId] { var pRefs: [ReferencedId] {
get { get {
+25 -17
View File
@@ -9,15 +9,15 @@ import Foundation
struct NostrFilter: Codable, Equatable { struct NostrFilter: Codable, Equatable {
var ids: [String]? var ids: [String]?
var kinds: [NostrKind]? var kinds: [Int]?
var referenced_ids: [String]? var referenced_ids: [String]?
var pubkeys: [String]? var pubkeys: [String]?
var since: Int64? var since: Int64?
var until: Int64? var until: Int64?
var limit: UInt32? var limit: UInt32?
var authors: [String]? var authors: [String]?
var hashtag: [String]? var hashtag: [String]? = nil
var parameter: [String]? var parameter: [String]? = nil
private enum CodingKeys : String, CodingKey { private enum CodingKeys : String, CodingKey {
case ids case ids
@@ -32,23 +32,31 @@ struct NostrFilter: Codable, Equatable {
case limit case limit
} }
init(ids: [String]? = nil, kinds: [NostrKind]? = nil, referenced_ids: [String]? = nil, pubkeys: [String]? = nil, since: Int64? = nil, until: Int64? = nil, limit: UInt32? = nil, authors: [String]? = nil, hashtag: [String]? = nil) {
self.ids = ids
self.kinds = kinds
self.referenced_ids = referenced_ids
self.pubkeys = pubkeys
self.since = since
self.until = until
self.limit = limit
self.authors = authors
self.hashtag = hashtag
}
public static func copy(from: NostrFilter) -> NostrFilter { public static func copy(from: NostrFilter) -> NostrFilter {
NostrFilter(ids: from.ids, kinds: from.kinds, referenced_ids: from.referenced_ids, pubkeys: from.pubkeys, since: from.since, until: from.until, authors: from.authors, hashtag: from.hashtag) return NostrFilter(ids: from.ids, kinds: from.kinds, referenced_ids: from.referenced_ids, pubkeys: from.pubkeys, since: from.since, until: from.until, authors: from.authors, hashtag: from.hashtag)
} }
public static func filter_hashtag(_ htags: [String]) -> NostrFilter { public static func filter_hashtag(_ htags: [String]) -> NostrFilter {
NostrFilter(hashtag: htags.map { $0.lowercased() }) return NostrFilter(ids: nil, kinds: nil, referenced_ids: nil, pubkeys: nil, since: nil, until: nil, authors: nil, hashtag: htags.map { $0.lowercased() })
}
public static func filter_ids(_ ids: [String]) -> NostrFilter {
return NostrFilter(ids: ids, kinds: nil, referenced_ids: nil, pubkeys: nil, since: nil, until: nil, authors: nil, hashtag: nil)
}
public static var filter_profiles: NostrFilter {
return filter_kinds([NostrKind.metadata.rawValue])
}
public static var filter_contacts: NostrFilter {
return filter_kinds([NostrKind.contacts.rawValue])
}
public static func filter_authors(_ authors: [String]) -> NostrFilter {
return NostrFilter(ids: nil, kinds: nil, referenced_ids: nil, pubkeys: nil, since: nil, until: nil, authors: authors)
}
public static func filter_kinds(_ kinds: [Int]) -> NostrFilter {
return NostrFilter(ids: nil, kinds: kinds, referenced_ids: nil, pubkeys: nil, since: nil, until: nil, authors: nil)
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@
import Foundation import Foundation
enum NostrKind: Int, Codable { enum NostrKind: Int {
case metadata = 0 case metadata = 0
case text = 1 case text = 1
case contacts = 3 case contacts = 3
+14 -3
View File
@@ -61,12 +61,23 @@ func parse_nostr_ref_uri(_ p: Parser) -> ReferencedId? {
return nil return nil
} }
guard let ref = parse_post_bech32_mention(p) else { guard let typ = parse_nostr_ref_uri_type(p) else {
p.pos = start p.pos = start
return nil return nil
} }
return ref if !parse_char(p, ":") {
p.pos = start
return nil
}
guard let pk = parse_hexstr(p, len: 64) else {
p.pos = start
return nil
}
// TODO: parse relays from nostr uris
return ReferencedId(ref_id: pk, relay_id: nil, key: typ)
} }
func decode_universal_link(_ s: String) -> NostrLink? { func decode_universal_link(_ s: String) -> NostrLink? {
@@ -129,7 +140,7 @@ func decode_nostr_uri(_ s: String) -> NostrLink? {
} }
if tag_is_hashtag(parts) { if tag_is_hashtag(parts) {
return .filter(NostrFilter(hashtag: [parts[1].lowercased()])) return .filter(NostrFilter.filter_hashtag([parts[1]]))
} }
if let rid = tag_to_refid(parts) { if let rid = tag_to_refid(parts) {
-181
View File
@@ -1,181 +0,0 @@
//
// ProfileDatabase.swift
// damus
//
// Created by Bryan Montz on 4/30/23.
//
import Foundation
import CoreData
enum ProfileDatabaseError: Error {
case missing_context
case outdated_input
}
final class ProfileDatabase {
private let entity_name = "PersistedProfile"
private var persistent_container: NSPersistentContainer?
private var background_context: NSManagedObjectContext?
private let cache_url: URL
/// This queue is used to synchronize access to the network_pull_date_cache dictionary, which
/// prevents data races from crashing the app.
private var queue = DispatchQueue(label: "io.damus.profile_db",
qos: .userInteractive,
attributes: .concurrent)
private var network_pull_date_cache = [String: Date]()
init(cache_url: URL = ProfileDatabase.profile_cache_url) {
self.cache_url = cache_url
set_up()
}
private static var profile_cache_url: URL {
(FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("profiles"))!
}
private var persistent_store_description: NSPersistentStoreDescription {
let description = NSPersistentStoreDescription(url: cache_url)
description.type = NSSQLiteStoreType
description.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
description.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
description.setOption(true as NSNumber, forKey: NSSQLiteManualVacuumOption)
return description
}
private var object_model: NSManagedObjectModel? {
guard let url = Bundle.main.url(forResource: "Damus", withExtension: "momd") else {
return nil
}
return NSManagedObjectModel(contentsOf: url)
}
private func set_up() {
guard let object_model else {
print("⚠️ Warning: ProfileDatabase failed to load its object model")
return
}
persistent_container = NSPersistentContainer(name: "Damus", managedObjectModel: object_model)
persistent_container?.persistentStoreDescriptions = [persistent_store_description]
persistent_container?.loadPersistentStores { _, error in
if let error {
print("WARNING: ProfileDatabase failed to load: \(error)")
}
}
persistent_container?.viewContext.automaticallyMergesChangesFromParent = true
persistent_container?.viewContext.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
background_context = persistent_container?.newBackgroundContext()
background_context?.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
}
private func get_persisted(id: String, context: NSManagedObjectContext) -> PersistedProfile? {
let request = NSFetchRequest<PersistedProfile>(entityName: entity_name)
request.predicate = NSPredicate(format: "id == %@", id)
request.fetchLimit = 1
return try? context.fetch(request).first
}
func get_network_pull_date(id: String) -> Date? {
var pull_date: Date?
queue.sync {
pull_date = network_pull_date_cache[id]
}
if let pull_date {
return pull_date
}
let request = NSFetchRequest<PersistedProfile>(entityName: entity_name)
request.predicate = NSPredicate(format: "id == %@", id)
request.fetchLimit = 1
request.propertiesToFetch = ["network_pull_date"]
guard let profile = try? persistent_container?.viewContext.fetch(request).first else {
return nil
}
queue.async(flags: .barrier) {
self.network_pull_date_cache[id] = profile.network_pull_date
}
return profile.network_pull_date
}
// MARK: - Public
/// Updates or inserts a new Profile into the local database. Rejects profiles whose update date
/// is older than one we already have. Database writes occur on a background context for best performance.
/// - Parameters:
/// - id: Profile id (pubkey)
/// - profile: Profile object to be stored
/// - last_update: Date that the Profile was updated
func upsert(id: String, profile: Profile, last_update: Date) async throws {
guard let context = background_context else {
throw ProfileDatabaseError.missing_context
}
try await context.perform {
var persisted_profile: PersistedProfile?
if let profile = self.get_persisted(id: id, context: context) {
if let existing_last_update = profile.last_update, last_update < existing_last_update {
throw ProfileDatabaseError.outdated_input
} else {
persisted_profile = profile
}
} else {
persisted_profile = NSEntityDescription.insertNewObject(forEntityName: self.entity_name, into: context) as? PersistedProfile
persisted_profile?.id = id
}
persisted_profile?.copyValues(from: profile)
persisted_profile?.last_update = last_update
let pull_date = Date.now
persisted_profile?.network_pull_date = pull_date
self.queue.async(flags: .barrier) {
self.network_pull_date_cache[id] = pull_date
}
try context.save()
}
}
func get(id: String) -> Profile? {
guard let container = persistent_container,
let profile = get_persisted(id: id, context: container.viewContext) else {
return nil
}
return Profile(persisted_profile: profile)
}
var count: Int {
let request = NSFetchRequest<PersistedProfile>(entityName: entity_name)
let count = try? persistent_container?.viewContext.count(for: request)
return count ?? 0
}
func remove_all_profiles() throws {
guard let context = background_context, let container = persistent_container else {
throw ProfileDatabaseError.missing_context
}
queue.async(flags: .barrier) {
self.network_pull_date_cache.removeAll()
}
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entity_name)
let batch_delete_request = NSBatchDeleteRequest(fetchRequest: request)
batch_delete_request.resultType = .resultTypeObjectIDs
let result = try container.persistentStoreCoordinator.execute(batch_delete_request, with: context) as! NSBatchDeleteResult
// NSBatchDeleteRequest is an NSPersistentStoreRequest, which operates on disk. So now we'll manually update our in-memory context.
if let object_ids = result.result as? [NSManagedObjectID] {
let changes: [AnyHashable: Any] = [
NSDeletedObjectsKey: object_ids
]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [context])
}
}
}
+10 -41
View File
@@ -6,58 +6,44 @@
// //
import Foundation import Foundation
import UIKit
class Profiles { class Profiles {
static let db_freshness_threshold: TimeInterval = 24 * 60 * 60
/// This queue is used to synchronize access to the profiles dictionary, which /// This queue is used to synchronize access to the profiles dictionary, which
/// prevents data races from crashing the app. /// prevents data races from crashing the app.
private var queue = DispatchQueue(label: "io.damus.profiles", private var queue = DispatchQueue(label: "io.damus.profiles",
qos: .userInteractive, qos: .userInteractive,
attributes: .concurrent) attributes: .concurrent)
private var profiles: [String: TimestampedProfile] = [:] var profiles: [String: TimestampedProfile] = [:]
var validated: [String: NIP05] = [:] var validated: [String: NIP05] = [:]
var nip05_pubkey: [String: String] = [:] var nip05_pubkey: [String: String] = [:]
var zappers: [String: String] = [:] var zappers: [String: String] = [:]
private let database = ProfileDatabase()
func is_validated(_ pk: String) -> NIP05? { func is_validated(_ pk: String) -> NIP05? {
validated[pk] return validated[pk]
}
func enumerated() -> EnumeratedSequence<[String: TimestampedProfile]> {
return queue.sync {
return profiles.enumerated()
}
} }
func lookup_zapper(pubkey: String) -> String? { func lookup_zapper(pubkey: String) -> String? {
zappers[pubkey] if let zapper = zappers[pubkey] {
return zapper
}
return nil
} }
func add(id: String, profile: TimestampedProfile) { func add(id: String, profile: TimestampedProfile) {
queue.async(flags: .barrier) { queue.async(flags: .barrier) {
self.profiles[id] = profile self.profiles[id] = profile
} }
Task {
do {
try await database.upsert(id: id, profile: profile.profile, last_update: Date(timeIntervalSince1970: TimeInterval(profile.timestamp)))
} catch {
print("⚠️ Warning: Profiles failed to save a profile: \(error)")
}
}
} }
func lookup(id: String) -> Profile? { func lookup(id: String) -> Profile? {
var profile: Profile?
queue.sync { queue.sync {
profile = profiles[id]?.profile return profiles[id]?.profile
} }
return profile ?? database.get(id: id)
} }
func lookup_with_timestamp(id: String) -> TimestampedProfile? { func lookup_with_timestamp(id: String) -> TimestampedProfile? {
@@ -65,23 +51,6 @@ class Profiles {
return profiles[id] return profiles[id]
} }
} }
func has_fresh_profile(id: String) -> Bool {
// check memory first
var profile: Profile?
queue.sync {
profile = profiles[id]?.profile
}
if profile != nil {
return true
}
// then disk
guard let pull_date = database.get_network_pull_date(id: id) else {
return false
}
return Date.now.timeIntervalSince(pull_date) < Profiles.db_freshness_threshold
}
} }
+3 -8
View File
@@ -37,9 +37,9 @@ public struct RelayURL: Hashable {
} }
} }
final class RelayConnection: ObservableObject { final class RelayConnection {
@Published private(set) var isConnected = false private(set) var isConnected = false
@Published private(set) var isConnecting = false private(set) var isConnecting = false
private(set) var last_connection_attempt: TimeInterval = 0 private(set) var last_connection_attempt: TimeInterval = 0
private(set) var last_pong: Date? = nil private(set) var last_pong: Date? = nil
@@ -129,11 +129,6 @@ final class RelayConnection: ObservableObject {
} }
case .error(let error): case .error(let error):
print("⚠️ Warning: RelayConnection (\(self.url)) error: \(error)") print("⚠️ Warning: RelayConnection (\(self.url)) error: \(error)")
let nserr = error as NSError
if nserr.domain == NSPOSIXErrorDomain && nserr.code == 57 {
// ignore socket not connected?
return
}
DispatchQueue.main.async { DispatchQueue.main.async {
self.isConnected = false self.isConnected = false
self.isConnecting = false self.isConnecting = false
+2 -7
View File
@@ -18,16 +18,11 @@ struct QueuedRequest {
let relay: String let relay: String
} }
struct SeenEvent: Hashable {
let relay_id: String
let evid: String
}
class RelayPool { class RelayPool {
var relays: [Relay] = [] var relays: [Relay] = []
var handlers: [RelayHandler] = [] var handlers: [RelayHandler] = []
var request_queue: [QueuedRequest] = [] var request_queue: [QueuedRequest] = []
var seen: Set<SeenEvent> = Set() var seen: Set<String> = Set()
var counts: [String: UInt64] = [:] var counts: [String: UInt64] = [:]
private let network_monitor = NWPathMonitor() private let network_monitor = NWPathMonitor()
@@ -238,7 +233,7 @@ class RelayPool {
func record_seen(relay_id: String, event: NostrConnectionEvent) { func record_seen(relay_id: String, event: NostrConnectionEvent) {
if case .nostr_event(let ev) = event { if case .nostr_event(let ev) = event {
if case .event(_, let nev) = ev { if case .event(_, let nev) = ev {
let k = SeenEvent(relay_id: relay_id, evid: nev.id) let k = relay_id + nev.id
if !seen.contains(k) { if !seen.contains(k) {
seen.insert(k) seen.insert(k)
if counts[relay_id] == nil { if counts[relay_id] == nil {
-13
View File
@@ -13,19 +13,6 @@ enum WebSocketEvent {
case message(URLSessionWebSocketTask.Message) case message(URLSessionWebSocketTask.Message)
case disconnected(URLSessionWebSocketTask.CloseCode, String?) case disconnected(URLSessionWebSocketTask.CloseCode, String?)
case error(Error) case error(Error)
var description: String? {
switch self {
case .connected:
return "Connected"
case .message(_):
return "Received message"
case .disconnected(let close_code, let reason):
return "Disconnected: Close code: \(close_code), reason: \(reason ?? "unknown")"
case .error(let error):
return "Error: \(error)"
}
}
} }
final class WebSocket: NSObject, URLSessionWebSocketDelegate { final class WebSocket: NSObject, URLSessionWebSocketDelegate {
+1 -1
View File
@@ -38,7 +38,7 @@ enum DisplayName {
func parse_display_name(profile: Profile?, pubkey: String) -> DisplayName { func parse_display_name(profile: Profile?, pubkey: String) -> DisplayName {
if pubkey == ANON_PUBKEY { if pubkey == "anon" {
return .one(NSLocalizedString("Anonymous", comment: "Placeholder display name of anonymous user.")) return .one(NSLocalizedString("Anonymous", comment: "Placeholder display name of anonymous user."))
} }
+6 -56
View File
@@ -62,7 +62,7 @@ class ZapsDataModel: ObservableObject {
} }
func confirm_nwc(reqid: String) { func confirm_nwc(reqid: String) {
guard let zap = zaps.first(where: { z in z.request.ev.id == reqid }), guard let zap = zaps.first(where: { z in z.request.id == reqid }),
case .pending(let pzap) = zap case .pending(let pzap) = zap
else { else {
return return
@@ -83,16 +83,16 @@ class ZapsDataModel: ObservableObject {
} }
func from(_ pubkey: String) -> [Zapping] { func from(_ pubkey: String) -> [Zapping] {
return self.zaps.filter { z in z.request.ev.pubkey == pubkey } return self.zaps.filter { z in z.request.pubkey == pubkey }
} }
@discardableResult @discardableResult
func remove(reqid: String) -> Bool { func remove(reqid: String) -> Bool {
guard zaps.first(where: { z in z.request.ev.id == reqid }) != nil else { guard zaps.first(where: { z in z.request.id == reqid }) != nil else {
return false return false
} }
self.zaps = zaps.filter { z in z.request.ev.id != reqid } self.zaps = zaps.filter { z in z.request.id != reqid }
return true return true
} }
} }
@@ -101,10 +101,6 @@ class RelativeTimeModel: ObservableObject {
@Published var value: String = "" @Published var value: String = ""
} }
class MediaMetaModel: ObservableObject {
@Published var fill: ImageFill? = nil
}
class EventData { class EventData {
var translations_model: TranslationModel var translations_model: TranslationModel
var artifacts_model: NoteArtifactsModel var artifacts_model: NoteArtifactsModel
@@ -112,7 +108,6 @@ class EventData {
var zaps_model : ZapsDataModel var zaps_model : ZapsDataModel
var relative_time: RelativeTimeModel = RelativeTimeModel() var relative_time: RelativeTimeModel = RelativeTimeModel()
var validated: ValidationResult var validated: ValidationResult
var media_metadata_model: MediaMetaModel
var translations: TranslateStatus { var translations: TranslateStatus {
return translations_model.state return translations_model.state
@@ -131,7 +126,6 @@ class EventData {
self.artifacts_model = .init(state: .not_loaded) self.artifacts_model = .init(state: .not_loaded)
self.zaps_model = .init(zaps) self.zaps_model = .init(zaps)
self.validated = .unknown self.validated = .unknown
self.media_metadata_model = MediaMetaModel()
self.preview_model = .init(state: .not_loaded) self.preview_model = .init(state: .not_loaded)
} }
} }
@@ -141,7 +135,6 @@ class EventCache {
private var replies = ReplyMap() private var replies = ReplyMap()
private var cancellable: AnyCancellable? private var cancellable: AnyCancellable?
private var image_metadata: [String: ImageMetadataState] = [:] private var image_metadata: [String: ImageMetadataState] = [:]
private var video_meta: [String: VideoPlayerModel] = [:]
private var event_data: [String: EventData] = [:] private var event_data: [String: EventData] = [:]
//private var thread_latest: [String: Int64] //private var thread_latest: [String: Int64]
@@ -175,9 +168,6 @@ class EventCache {
@discardableResult @discardableResult
func store_zap(zap: Zapping) -> Bool { func store_zap(zap: Zapping) -> Bool {
let data = get_cache_data(zap.target.id).zaps_model let data = get_cache_data(zap.target.id).zaps_model
if let ev = zap.event {
insert(ev)
}
return insert_uniq_sorted_zap_by_amount(zaps: &data.zaps, new_zap: zap) return insert_uniq_sorted_zap_by_amount(zaps: &data.zaps, new_zap: zap)
} }
@@ -185,7 +175,7 @@ class EventCache {
switch zap.target { switch zap.target {
case .note(let note_target): case .note(let note_target):
let zaps = get_cache_data(note_target.note_id).zaps_model let zaps = get_cache_data(note_target.note_id).zaps_model
zaps.remove(reqid: zap.request.ev.id) zaps.remove(reqid: zap.request.id)
case .profile: case .profile:
// these aren't stored anywhere yet // these aren't stored anywhere yet
break break
@@ -204,30 +194,6 @@ class EventCache {
return image_metadata[url.absoluteString.lowercased()] return image_metadata[url.absoluteString.lowercased()]
} }
@MainActor
func lookup_media_size(url: URL) -> CGSize? {
if let img_meta = lookup_img_metadata(url: url) {
return img_meta.meta.dim?.size
}
return get_video_player_model(url: url).size
}
func store_video_player_model(url: URL, meta: VideoPlayerModel) {
video_meta[url.absoluteString] = meta
}
@MainActor
func get_video_player_model(url: URL) -> VideoPlayerModel {
if let model = video_meta[url.absoluteString] {
return model
}
let model = VideoPlayerModel()
video_meta[url.absoluteString] = model
return model
}
func parent_events(event: NostrEvent) -> [NostrEvent] { func parent_events(event: NostrEvent) -> [NostrEvent] {
var parents: [NostrEvent] = [] var parents: [NostrEvent] = []
@@ -291,7 +257,6 @@ class EventCache {
private func prune() { private func prune() {
events = [:] events = [:]
video_meta = [:]
event_data = [:] event_data = [:]
replies.replies = [:] replies.replies = [:]
} }
@@ -400,19 +365,11 @@ func preload_image(url: URL) {
print("Preloading image \(url.absoluteString)") print("Preloading image \(url.absoluteString)")
KingfisherManager.shared.retrieveImage(with: Kingfisher.ImageResource(downloadURL: url)) { val in KingfisherManager.shared.retrieveImage(with: ImageResource(downloadURL: url)) { val in
print("Preloaded image \(url.absoluteString)") print("Preloaded image \(url.absoluteString)")
} }
} }
func is_animated_image(url: URL) -> Bool {
guard let ext = url.pathComponents.last?.split(separator: ".").last?.lowercased() else {
return false
}
return ext == "gif"
}
func preload_event(plan: PreloadPlan, state: DamusState) async { func preload_event(plan: PreloadPlan, state: DamusState) async {
var artifacts: NoteArtifacts? = plan.data.artifacts.artifacts var artifacts: NoteArtifacts? = plan.data.artifacts.artifacts
let settings = state.settings let settings = state.settings
@@ -431,13 +388,6 @@ func preload_event(plan: PreloadPlan, state: DamusState) async {
} }
for url in arts.images { for url in arts.images {
guard !is_animated_image(url: url) else {
// jb55: I have a theory that animated images are not working with the preloader due
// to some disk-cache write race condition. normal images need not apply
continue
}
preload_image(url: url) preload_image(url: url)
} }
} }
+1 -1
View File
@@ -11,7 +11,7 @@ import Foundation
class EventHolder: ObservableObject, ScrollQueue { class EventHolder: ObservableObject, ScrollQueue {
private var has_event: Set<String> private var has_event: Set<String>
@Published var events: [NostrEvent] @Published var events: [NostrEvent]
var incoming: [NostrEvent] @Published var incoming: [NostrEvent]
var should_queue: Bool var should_queue: Bool
var on_queue: ((NostrEvent) -> Void)? var on_queue: ((NostrEvent) -> Void)?
+1 -1
View File
@@ -40,7 +40,7 @@ extension KFOptionSetter {
func onFailure(fallbackUrl: URL?, cacheKey: String?) -> Self { func onFailure(fallbackUrl: URL?, cacheKey: String?) -> Self {
guard let url = fallbackUrl, let key = cacheKey else { return self } guard let url = fallbackUrl, let key = cacheKey else { return self }
let imageResource = Kingfisher.ImageResource(downloadURL: url, cacheKey: key) let imageResource = ImageResource(downloadURL: url, cacheKey: key)
let source = imageResource.convertToSource() let source = imageResource.convertToSource()
options.alternativeSources = [source] options.alternativeSources = [source]
+1 -1
View File
@@ -60,7 +60,7 @@ func hashtag_str(_ htag: String) -> CompatibleText {
} }
text = Text(attributedString) text = Text(attributedString)
let img = Image("\(name)-hashtag") let img = Image("\(name)-hashtag")
text = text + Text(img).baselineOffset(custom_hashtag.offset ?? 0.0) text = text + Text("\(img)").baselineOffset(custom_hashtag.offset ?? 0.0)
} else { } else {
attributedString.foregroundColor = DamusColors.purple attributedString.foregroundColor = DamusColors.purple
} }
+4 -2
View File
@@ -59,15 +59,17 @@ struct ImageMetadata: Equatable {
} }
func process_blurhash(blurhash: String, size: CGSize?) async -> UIImage? { func process_blurhash(blurhash: String, size: CGSize?) async -> UIImage? {
let res = Task.detached(priority: .low) { let res = Task.init {
let size = get_blurhash_size(img_size: size ?? CGSize(width: 100.0, height: 100.0)) let size = get_blurhash_size(img_size: size ?? CGSize(width: 100.0, height: 100.0))
guard let img = UIImage.init(blurHash: blurhash, size: size) else { guard let img = UIImage.init(blurHash: blurhash, size: size) else {
let noimg: UIImage? = nil let noimg: UIImage? = nil
return noimg return noimg
} }
return img return img
} }
return await res.value return await res.value
} }
@@ -144,7 +146,7 @@ func calculate_blurhash(img: UIImage) async -> String? {
return nil return nil
} }
let res = Task.detached(priority: .low) { let res = Task.init {
let bhs = get_blurhash_size(img_size: img.size) let bhs = get_blurhash_size(img_size: img.size)
let smaller = img.resized(to: bhs) let smaller = img.resized(to: bhs)
+1 -2
View File
@@ -11,10 +11,9 @@ func insert_uniq_sorted_zap(zaps: inout [Zapping], new_zap: Zapping, cmp: (Zappi
var i: Int = 0 var i: Int = 0
for zap in zaps { for zap in zaps {
if new_zap.request.ev.id == zap.request.ev.id { if new_zap.request.id == zap.request.id {
// replace pending // replace pending
if !new_zap.is_pending && zap.is_pending { if !new_zap.is_pending && zap.is_pending {
print("nwc: replacing pending with real zap \(new_zap.request.ev.id)")
zaps[i] = new_zap zaps[i] = new_zap
return true return true
} }
-1
View File
@@ -9,7 +9,6 @@ import Foundation
import secp256k1 import secp256k1
let PUBKEY_HRP = "npub" let PUBKEY_HRP = "npub"
let ANON_PUBKEY = "anon"
struct FullKeypair: Equatable { struct FullKeypair: Equatable {
let pubkey: String let pubkey: String
-1
View File
@@ -44,5 +44,4 @@ enum LocalNotificationType: String {
case mention case mention
case repost case repost
case zap case zap
case profile_zap
} }
-3
View File
@@ -77,9 +77,6 @@ extension Notification.Name {
static var update_stats: Notification.Name { static var update_stats: Notification.Name {
return Notification.Name("update_stats") return Notification.Name("update_stats")
} }
static var present_sheet: Notification.Name {
return Notification.Name("present_sheet")
}
static var zapping: Notification.Name { static var zapping: Notification.Name {
return Notification.Name("zapping") return Notification.Name("zapping")
} }
+4 -31
View File
@@ -22,27 +22,18 @@ class Relayer {
} }
} }
enum OnFlush {
case once((PostedEvent) -> Void)
case all((PostedEvent) -> Void)
}
class PostedEvent { class PostedEvent {
let event: NostrEvent let event: NostrEvent
let skip_ephemeral: Bool let skip_ephemeral: Bool
var remaining: [Relayer] var remaining: [Relayer]
let flush_after: Date? let flush_after: Date?
var flushed_once: Bool
let on_flush: OnFlush?
init(event: NostrEvent, remaining: [String], skip_ephemeral: Bool, flush_after: Date?, on_flush: OnFlush?) { init(event: NostrEvent, remaining: [String], skip_ephemeral: Bool, flush_after: Date? = nil) {
self.event = event self.event = event
self.skip_ephemeral = skip_ephemeral self.skip_ephemeral = skip_ephemeral
self.flush_after = flush_after self.flush_after = flush_after
self.on_flush = on_flush
self.flushed_once = false
self.remaining = remaining.map { self.remaining = remaining.map {
Relayer(relay: $0, attempts: 0, retry_after: 10.0) Relayer(relay: $0, attempts: 0, retry_after: 2.0)
} }
} }
} }
@@ -118,19 +109,6 @@ class PostBox {
guard let ev = self.events[event_id] else { guard let ev = self.events[event_id] else {
return false return false
} }
if let on_flush = ev.on_flush {
switch on_flush {
case .once(let cb):
if !ev.flushed_once {
ev.flushed_once = true
cb(ev)
}
case .all(let cb):
cb(ev)
}
}
let prev_count = ev.remaining.count let prev_count = ev.remaining.count
ev.remaining = ev.remaining.filter { $0.relay != relay_id } ev.remaining = ev.remaining.filter { $0.relay != relay_id }
let after_count = ev.remaining.count let after_count = ev.remaining.count
@@ -150,16 +128,11 @@ class PostBox {
relayer.attempts += 1 relayer.attempts += 1
relayer.last_attempt = Int64(Date().timeIntervalSince1970) relayer.last_attempt = Int64(Date().timeIntervalSince1970)
relayer.retry_after *= 1.5 relayer.retry_after *= 1.5
if pool.get_relay(relayer.relay) != nil {
print("flushing event \(event.event.id) to \(relayer.relay)")
} else {
print("could not find relay when flushing: \(relayer.relay)")
}
pool.send(.event(event.event), to: [relayer.relay], skip_ephemeral: event.skip_ephemeral) pool.send(.event(event.event), to: [relayer.relay], skip_ephemeral: event.skip_ephemeral)
} }
} }
func send(_ event: NostrEvent, to: [String]? = nil, skip_ephemeral: Bool = true, delay: TimeInterval? = nil, on_flush: OnFlush? = nil) { func send(_ event: NostrEvent, to: [String]? = nil, skip_ephemeral: Bool = true, delay: TimeInterval? = nil) {
// Don't add event if we already have it // Don't add event if we already have it
if events[event.id] != nil { if events[event.id] != nil {
return return
@@ -167,7 +140,7 @@ class PostBox {
let remaining = to ?? pool.our_descriptors.map { $0.url.id } let remaining = to ?? pool.our_descriptors.map { $0.url.id }
let after = delay.map { d in Date.now.addingTimeInterval(d) } let after = delay.map { d in Date.now.addingTimeInterval(d) }
let posted_ev = PostedEvent(event: event, remaining: remaining, skip_ephemeral: skip_ephemeral, flush_after: after, on_flush: on_flush) let posted_ev = PostedEvent(event: event, remaining: remaining, skip_ephemeral: skip_ephemeral, flush_after: after)
events[event.id] = posted_ev events[event.id] = posted_ev
+10
View File
@@ -66,12 +66,22 @@ enum PreviewState {
class PreviewCache { class PreviewCache {
private var previews: [String: Preview] private var previews: [String: Preview]
private var image_meta: [String: ImageFill]
func lookup(_ evid: String) -> Preview? { func lookup(_ evid: String) -> Preview? {
return previews[evid] return previews[evid]
} }
func lookup_image_meta(_ evid: String) -> ImageFill? {
return image_meta[evid]
}
func cache_image_meta(evid: String, image_fill: ImageFill) {
self.image_meta[evid] = image_fill
}
init() { init() {
self.previews = [:] self.previews = [:]
self.image_meta = [:]
} }
} }
-276
View File
@@ -1,276 +0,0 @@
//
// Router.swift
// damus
//
// Created by Scott Penrose on 5/7/23.
//
import SwiftUI
enum Route: Hashable {
case ProfileByKey(pubkey: String)
case Profile(profile: ProfileModel, followers: FollowersModel)
case Followers(followers: FollowersModel)
case Relay(relay: String, showActionButtons: Binding<Bool>)
case RelayDetail(relay: String, metadata: RelayMetadata)
case Following(following: FollowingModel)
case MuteList(users: [String])
case RelayConfig
case Bookmarks
case Config
case EditMetadata
case DMChat(dms: DirectMessageModel)
case UserRelays(relays: [String])
case KeySettings(keypair: Keypair)
case AppearanceSettings(settings: UserSettingsStore)
case NotificationSettings(settings: UserSettingsStore)
case ZapSettings(settings: UserSettingsStore)
case TranslationSettings(settings: UserSettingsStore)
case SearchSettings(settings: UserSettingsStore)
case Thread(thread: ThreadModel)
case Reposts(reposts: RepostsModel)
case Reactions(reactions: ReactionsModel)
case Zaps(target: ZapTarget)
case Search(search: SearchModel)
case EULA
case Login
case CreateAccount
case SaveKeys(account: CreateAccountModel)
case Wallet(wallet: WalletModel)
case WalletScanner(result: Binding<WalletScanResult>)
case FollowersYouKnow(friendedFollowers: [String], followers: FollowersModel)
@ViewBuilder
func view(navigationCordinator: NavigationCoordinator, damusState: DamusState) -> some View {
switch self {
case .ProfileByKey(let pubkey):
ProfileView(damus_state: damusState, pubkey: pubkey)
case .Profile(let profile, let followers):
ProfileView(damus_state: damusState, profile: profile, followers: followers)
case .Followers(let followers):
FollowersView(damus_state: damusState, followers: followers)
case .Relay(let relay, let showActionButtons):
RelayView(state: damusState, relay: relay, showActionButtons: showActionButtons)
case .RelayDetail(let relay, let metadata):
RelayDetailView(state: damusState, relay: relay, nip11: metadata)
case .Following(let following):
FollowingView(damus_state: damusState, following: following)
case .MuteList(let users):
MutelistView(damus_state: damusState, users: users)
case .RelayConfig:
RelayConfigView(state: damusState)
case .Bookmarks:
BookmarksView(state: damusState)
case .Config:
ConfigView(state: damusState)
case .EditMetadata:
EditMetadataView(damus_state: damusState)
case .DMChat(let dms):
DMChatView(damus_state: damusState, dms: dms)
case .UserRelays(let relays):
UserRelaysView(state: damusState, relays: relays)
case .KeySettings(let keypair):
KeySettingsView(keypair: keypair)
case .AppearanceSettings(let settings):
AppearanceSettingsView(settings: settings)
case .NotificationSettings(let settings):
NotificationSettingsView(settings: settings)
case .ZapSettings(let settings):
ZapSettingsView(settings: settings)
case .TranslationSettings(let settings):
NotificationSettingsView(settings: settings)
case .SearchSettings(let settings):
SearchSettingsView(settings: settings)
case .Thread(let thread):
ThreadView(state: damusState, thread: thread)
case .Reposts(let reposts):
RepostsView(damus_state: damusState, model: reposts)
case .Reactions(let reactions):
ReactionsView(damus_state: damusState, model: reactions)
case .Zaps(let target):
ZapsView(state: damusState, target: target)
case .Search(let search):
SearchView(appstate: damusState, search: search)
case .EULA:
EULAView(nav: navigationCordinator)
case .Login:
LoginView(nav: navigationCordinator)
case .CreateAccount:
CreateAccountView(nav: navigationCordinator)
case .SaveKeys(let account):
SaveKeysView(account: account)
case .Wallet(let walletModel):
WalletView(damus_state: damusState, model: walletModel)
case .WalletScanner(let walletScanResult):
WalletScannerView(result: walletScanResult)
case .FollowersYouKnow(let friendedFollowers, let followers):
FollowersYouKnowView(damus_state: damusState, friended_followers: friendedFollowers, followers: followers)
}
}
static func == (lhs: Route, rhs: Route) -> Bool {
switch (lhs, rhs) {
case (.ProfileByKey (let lhs_pubkey), .ProfileByKey(let rhs_pubkey)):
return lhs_pubkey == rhs_pubkey
case (.Profile (let lhs_profile, _), .Profile(let rhs_profile, _)):
return lhs_profile == rhs_profile
case (.Followers (_), .Followers (_)):
return true
case (.Relay (let lhs_relay, _), .Relay (let rhs_relay, _)):
return lhs_relay == rhs_relay
case (.RelayDetail(let lhs_relay, _), .RelayDetail(let rhs_relay, _)):
return lhs_relay == rhs_relay
case (.Following(_), .Following(_)):
return true
case (.MuteList(let lhs_users), .MuteList(let rhs_users)):
return lhs_users == rhs_users
case (.RelayConfig, .RelayConfig):
return true
case (.Bookmarks, .Bookmarks):
return true
case (.Config, .Config):
return true
case (.EditMetadata, .EditMetadata):
return true
case (.DMChat(let lhs_dms), .DMChat(let rhs_dms)):
return lhs_dms.our_pubkey == rhs_dms.our_pubkey
case (.UserRelays(let lhs_relays), .UserRelays(let rhs_relays)):
return lhs_relays == rhs_relays
case (.KeySettings(let lhs_keypair), .KeySettings(let rhs_keypair)):
return lhs_keypair.pubkey == rhs_keypair.pubkey
case (.AppearanceSettings(_), .AppearanceSettings(_)):
return true
case (.NotificationSettings(_), .NotificationSettings(_)):
return true
case (.ZapSettings(_), .ZapSettings(_)):
return true
case (.TranslationSettings(_), .TranslationSettings(_)):
return true
case (.SearchSettings(_), .SearchSettings(_)):
return true
case (.Thread(let lhs_threadModel), .Thread(thread: let rhs_threadModel)):
return lhs_threadModel.event.id == rhs_threadModel.event.id
case (.Reposts(let lhs_reposts), .Reposts(let rhs_reposts)):
return lhs_reposts.target == rhs_reposts.target
case (.Reactions(let lhs_reactions), .Reactions(let rhs_reactions)):
return lhs_reactions.target == rhs_reactions.target
case (.Zaps(let lhs_target), .Zaps(let rhs_target)):
return lhs_target == rhs_target
case (.Search(let lhs_search), .Search(let rhs_search)):
return lhs_search.sub_id == rhs_search.sub_id && lhs_search.profiles_subid == rhs_search.profiles_subid
case (.EULA, .EULA):
return true
case (.Login, .Login):
return true
case (.CreateAccount, .CreateAccount):
return true
case (.SaveKeys(let lhs_account), .SaveKeys(let rhs_account)):
return lhs_account.pubkey == rhs_account.pubkey
case (.Wallet(_), .Wallet(_)):
return true
case (.WalletScanner(_), .WalletScanner(_)):
return true
case (.FollowersYouKnow(_, _), .FollowersYouKnow(_, _)):
return true
default:
return false
}
}
func hash(into hasher: inout Hasher) {
switch self {
case .ProfileByKey(let pubkey):
hasher.combine("profilebykey")
hasher.combine(pubkey)
case .Profile(let profile, _):
hasher.combine("profile")
hasher.combine(profile.pubkey)
case .Followers(_):
hasher.combine("followers")
case .Relay(let relay, _):
hasher.combine("relay")
hasher.combine(relay)
case .RelayDetail(let relay, _):
hasher.combine("relayDetail")
hasher.combine(relay)
case .Following(_):
hasher.combine("following")
case .MuteList(let users):
hasher.combine("muteList")
hasher.combine(users)
case .RelayConfig:
hasher.combine("relayConfig")
case .Bookmarks:
hasher.combine("bookmarks")
case .Config:
hasher.combine("config")
case .EditMetadata:
hasher.combine("editMetadata")
case .DMChat(let dms):
hasher.combine("dms")
hasher.combine(dms.our_pubkey)
case .UserRelays(let relays):
hasher.combine("userRelays")
hasher.combine(relays)
case .KeySettings(let keypair):
hasher.combine("keySettings")
hasher.combine(keypair.pubkey)
case .AppearanceSettings(_):
hasher.combine("appearanceSettings")
case .NotificationSettings(_):
hasher.combine("notificationSettings")
case .ZapSettings(_):
hasher.combine("zapSettings")
case .TranslationSettings(_):
hasher.combine("translationSettings")
case .SearchSettings(_):
hasher.combine("searchSettings")
case .Thread(let threadModel):
hasher.combine("thread")
hasher.combine(threadModel.event.id)
case .Reposts(let reposts):
hasher.combine("reposts")
hasher.combine(reposts.target)
case .Zaps(let target):
hasher.combine("zaps")
hasher.combine(target.id)
hasher.combine(target.pubkey)
case .Reactions(let reactions):
hasher.combine("reactions")
hasher.combine(reactions.target)
case .Search(let search):
hasher.combine("search")
hasher.combine(search.sub_id)
hasher.combine(search.profiles_subid)
case .EULA:
hasher.combine("eula")
case .Login:
hasher.combine("login")
case .CreateAccount:
hasher.combine("createAccount")
case .SaveKeys(let account):
hasher.combine("saveKeys")
hasher.combine(account.pubkey)
case .Wallet(_):
hasher.combine("wallet")
case .WalletScanner(_):
hasher.combine("walletScanner")
case .FollowersYouKnow(let friendedFollowers, let followers):
hasher.combine("followersYouKnow")
hasher.combine(friendedFollowers)
hasher.combine(followers.sub_id)
}
}
}
class NavigationCoordinator: ObservableObject {
@Published var path = [Route]()
func push(route: Route) {
path.append(route)
}
func popToRoot() {
path = []
}
}
-34
View File
@@ -1,34 +0,0 @@
//
// StringUtil.swift
// damus
//
// Created by Terry Yiu on 6/4/23.
//
import Foundation
extension String {
/// Returns a copy of the String truncated to maxLength and "..." ellipsis appended to the end,
/// or if the String does not exceed maxLength, the String itself is returned without truncation or added ellipsis.
func truncate(maxLength: Int) -> String {
guard count > maxLength else {
return self
}
return self[...self.index(self.startIndex, offsetBy: maxLength - 1)] + "..."
}
}
extension AttributedString {
/// Returns a copy of the AttributedString truncated to maxLength and "..." ellipsis appended to the end,
/// or if the AttributedString does not exceed maxLength, nil is returned.
func truncateOrNil(maxLength: Int) -> AttributedString? {
let nsAttributedString = NSAttributedString(self)
if nsAttributedString.length < maxLength { return nil }
let range = NSRange(location: 0, length: maxLength)
let truncatedAttributedString = nsAttributedString.attributedSubstring(from: range)
return AttributedString(truncatedAttributedString) + "..."
}
}
+8 -26
View File
@@ -36,8 +36,7 @@ struct WalletConnectURL: Equatable {
} }
init?(str: String) { init?(str: String) {
guard let url = URL(string: str), guard let url = URL(string: str), url.scheme == "nostrwalletconnect",
url.scheme == "nostrwalletconnect" || url.scheme == "nostr+walletconnect",
let pk = url.host, pk.utf8.count == 64, let pk = url.host, pk.utf8.count == 64,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true), let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let items = components.queryItems, let items = components.queryItems,
@@ -175,16 +174,15 @@ func make_wallet_connect_request<T>(req: WalletRequest<T>, to_pk: String, keypai
} }
func subscribe_to_nwc(url: WalletConnectURL, pool: RelayPool) { func subscribe_to_nwc(url: WalletConnectURL, pool: RelayPool) {
var filter = NostrFilter(kinds: [.nwc_response]) var filter: NostrFilter = .filter_kinds([NostrKind.nwc_response.rawValue])
filter.authors = [url.pubkey] filter.authors = [url.pubkey]
filter.limit = 0 filter.limit = 0
let sub = NostrSubscribe(filters: [filter], sub_id: "nwc") let sub = NostrSubscribe(filters: [filter], sub_id: "nwc")
pool.send(.subscribe(sub), to: [url.relay.id], skip_ephemeral: false) pool.send(.subscribe(sub), to: [url.relay.id])
} }
@discardableResult func nwc_pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: String) -> NostrEvent? {
func nwc_pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: String, delay: TimeInterval? = 5.0, on_flush: OnFlush? = nil) -> NostrEvent? {
let req = make_wallet_pay_invoice_request(invoice: invoice) let req = make_wallet_pay_invoice_request(invoice: invoice)
guard let ev = make_wallet_connect_request(req: req, to_pk: url.pubkey, keypair: url.keypair) else { guard let ev = make_wallet_connect_request(req: req, to_pk: url.pubkey, keypair: url.keypair) else {
return nil return nil
@@ -192,14 +190,14 @@ func nwc_pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: Str
try? pool.add_relay(.nwc(url: url.relay)) try? pool.add_relay(.nwc(url: url.relay))
subscribe_to_nwc(url: url, pool: pool) subscribe_to_nwc(url: url, pool: pool)
post.send(ev, to: [url.relay.id], skip_ephemeral: false, delay: delay, on_flush: on_flush) post.send(ev, to: [url.relay.id], skip_ephemeral: false, delay: 5.0)
return ev return ev
} }
func nwc_success(state: DamusState, resp: FullWalletResponse) { func nwc_success(zapcache: Zaps, evcache: EventCache, resp: FullWalletResponse) {
// find the pending zap and mark it as pending-confirmed // find the pending zap and mark it as pending-confirmed
for kv in state.zaps.our_zaps { for kv in zapcache.our_zaps {
let zaps = kv.value let zaps = kv.value
for zap in zaps { for zap in zaps {
@@ -213,30 +211,14 @@ func nwc_success(state: DamusState, resp: FullWalletResponse) {
if nwc_state.update_state(state: .confirmed) { if nwc_state.update_state(state: .confirmed) {
// notify the zaps model of an update so it can mark them as paid // notify the zaps model of an update so it can mark them as paid
state.events.get_cache_data(pzap.target.id).zaps_model.objectWillChange.send() evcache.get_cache_data(pzap.target.id).zaps_model.objectWillChange.send()
print("NWC success confirmed") print("NWC success confirmed")
} }
return return
} }
} }
} }
func send_donation_zap(pool: RelayPool, postbox: PostBox, nwc: WalletConnectURL, percent: Int, base_msats: Int64) async {
let percent_f = Double(percent) / 100.0
let donations_msats = Int64(percent_f * Double(base_msats))
let payreq = LNUrlPayRequest(allowsNostr: true, commentAllowed: nil, nostrPubkey: "", callback: "https://sendsats.lol/@damus")
guard let invoice = await fetch_zap_invoice(payreq, zapreq: nil, msats: donations_msats, zap_type: .non_zap, comment: nil) else {
// we failed... oh well. no donation for us.
print("damus-donation failed to fetch invoice")
return
}
print("damus-donation donating...")
nwc_pay(url: nwc, pool: pool, post: postbox, invoice: invoice, delay: nil)
}
func nwc_error(zapcache: Zaps, evcache: EventCache, resp: FullWalletResponse) { func nwc_error(zapcache: Zaps, evcache: EventCache, resp: FullWalletResponse) {
// find a pending zap with the nwc request id associated with this response and remove it // find a pending zap with the nwc request id associated with this response and remove it
for kv in zapcache.our_zaps { for kv in zapcache.our_zaps {
+13 -31
View File
@@ -41,16 +41,7 @@ public enum ZapTarget: Equatable {
struct ZapRequest { struct ZapRequest {
let ev: NostrEvent let ev: NostrEvent
let marked_hidden: Bool
var is_in_thread: Bool {
return !self.ev.content.isEmpty && !marked_hidden
}
init(ev: NostrEvent) {
self.ev = ev
self.marked_hidden = ev.tags.first(where: { t in t.count > 0 && t[0] == "hidden" }) != nil
}
} }
enum ExtPendingZapStateType { enum ExtPendingZapStateType {
@@ -138,7 +129,7 @@ struct ZapRequestId: Equatable {
let reqid: String let reqid: String
init(from_zap: Zapping) { init(from_zap: Zapping) {
self.reqid = from_zap.request.ev.id self.reqid = from_zap.request.id
} }
init(from_makezap: MakeZapRequest) { init(from_makezap: MakeZapRequest) {
@@ -207,12 +198,12 @@ enum Zapping {
} }
} }
var request: ZapRequest { var request: NostrEvent {
switch self { switch self {
case .zap(let zap): case .zap(let zap):
return zap.request return zap.request_ev
case .pending(let pzap): case .pending(let pzap):
return pzap.request return pzap.request.ev
} }
} }
@@ -236,15 +227,6 @@ enum Zapping {
} }
} }
var is_in_thread: Bool {
switch self {
case .zap(let zap):
return zap.request.is_in_thread
case .pending(let pzap):
return pzap.request.is_in_thread
}
}
var is_anon: Bool { var is_anon: Bool {
switch self { switch self {
case .zap(let zap): case .zap(let zap):
@@ -260,12 +242,12 @@ struct Zap {
public let invoice: ZapInvoice public let invoice: ZapInvoice
public let zapper: String /// zap authorizer public let zapper: String /// zap authorizer
public let target: ZapTarget public let target: ZapTarget
public let raw_request: ZapRequest public let request: ZapRequest
public let is_anon: Bool public let is_anon: Bool
public let private_request: ZapRequest? public let private_request: NostrEvent?
var request: ZapRequest { var request_ev: NostrEvent {
return private_request ?? self.raw_request return private_request ?? self.request.ev
} }
public static func from_zap_event(zap_ev: NostrEvent, zapper: String, our_privkey: String?) -> Zap? { public static func from_zap_event(zap_ev: NostrEvent, zapper: String, our_privkey: String?) -> Zap? {
@@ -313,9 +295,8 @@ struct Zap {
} }
let is_anon = private_request == nil && event_is_anonymous(ev: zap_req) let is_anon = private_request == nil && event_is_anonymous(ev: zap_req)
let preq = private_request.map { pr in ZapRequest(ev: pr) }
return Zap(event: zap_ev, invoice: zap_invoice, zapper: zapper, target: target, raw_request: ZapRequest(ev: zap_req), is_anon: is_anon, private_request: preq) return Zap(event: zap_ev, invoice: zap_invoice, zapper: zapper, target: target, request: ZapRequest(ev: zap_req), is_anon: is_anon, private_request: private_request)
} }
} }
@@ -459,14 +440,15 @@ func fetch_static_payreq(_ lnurl: String) async -> LNUrlPayRequest? {
return endpoint return endpoint
} }
func fetch_zap_invoice(_ payreq: LNUrlPayRequest, zapreq: NostrEvent?, msats: Int64, zap_type: ZapType, comment: String?) async -> String? { func fetch_zap_invoice(_ payreq: LNUrlPayRequest, zapreq: NostrEvent, sats: Int, zap_type: ZapType, comment: String?) async -> String? {
guard var base_url = payreq.callback.flatMap({ URLComponents(string: $0) }) else { guard var base_url = payreq.callback.flatMap({ URLComponents(string: $0) }) else {
return nil return nil
} }
let zappable = payreq.allowsNostr ?? false let zappable = payreq.allowsNostr ?? false
let amount: Int64 = Int64(sats) * 1000
var query = [URLQueryItem(name: "amount", value: "\(msats)")] var query = [URLQueryItem(name: "amount", value: "\(amount)")]
if zappable && zap_type != .non_zap, let json = encode_json(zapreq) { if zappable && zap_type != .non_zap, let json = encode_json(zapreq) {
print("zapreq json: \(json)") print("zapreq json: \(json)")
@@ -507,7 +489,7 @@ func fetch_zap_invoice(_ payreq: LNUrlPayRequest, zapreq: NostrEvent?, msats: In
// make sure it's the correct amount // make sure it's the correct amount
guard let bolt11 = decode_bolt11(result.pr), guard let bolt11 = decode_bolt11(result.pr),
.specific(msats) == bolt11.amount .specific(amount) == bolt11.amount
else { else {
return nil return nil
} }
+8 -11
View File
@@ -12,8 +12,8 @@ class Zaps {
let our_pubkey: String let our_pubkey: String
var our_zaps: [String: [Zapping]] var our_zaps: [String: [Zapping]]
private(set) var event_counts: [String: Int] var event_counts: [String: Int]
private(set) var event_totals: [String: Int64] var event_totals: [String: Int64]
init(our_pubkey: String) { init(our_pubkey: String) {
self.zaps = [:] self.zaps = [:]
@@ -27,13 +27,13 @@ class Zaps {
var res: Zapping? = nil var res: Zapping? = nil
for kv in our_zaps { for kv in our_zaps {
let ours = kv.value let ours = kv.value
guard let zap = ours.first(where: { z in z.request.ev.id == reqid }) else { guard let zap = ours.first(where: { z in z.request.id == reqid }) else {
continue continue
} }
res = zap res = zap
our_zaps[kv.key] = ours.filter { z in z.request.ev.id != reqid } our_zaps[kv.key] = ours.filter { z in z.request.id != reqid }
if let count = event_counts[zap.target.id] { if let count = event_counts[zap.target.id] {
event_counts[zap.target.id] = count - 1 event_counts[zap.target.id] = count - 1
@@ -51,16 +51,13 @@ class Zaps {
} }
func add_zap(zap: Zapping) { func add_zap(zap: Zapping) {
if zaps[zap.request.ev.id] != nil { if zaps[zap.request.id] != nil {
return return
} }
self.zaps[zap.request.ev.id] = zap self.zaps[zap.request.id] = zap
if let zap_id = zap.event?.id {
self.zaps[zap_id] = zap
}
// record our zaps for an event // record our zaps for an event
if zap.request.ev.pubkey == our_pubkey { if zap.request.pubkey == our_pubkey {
switch zap.target { switch zap.target {
case .note(let note_target): case .note(let note_target):
if our_zaps[note_target.note_id] == nil { if our_zaps[note_target.note_id] == nil {
@@ -74,7 +71,7 @@ class Zaps {
} }
// don't count tips to self. lame. // don't count tips to self. lame.
guard zap.request.ev.pubkey != zap.target.pubkey else { guard zap.request.pubkey != zap.target.pubkey else {
return return
} }
+10 -18
View File
@@ -43,7 +43,7 @@ struct EventActionBar: View {
HStack { HStack {
if damus_state.keypair.privkey != nil { if damus_state.keypair.privkey != nil {
HStack(spacing: 4) { HStack(spacing: 4) {
EventActionButton(img: "bubble2", col: bar.replied ? DamusColors.purple : Color.gray) { EventActionButton(img: "bubble.left", col: bar.replied ? DamusColors.purple : Color.gray) {
notify(.compose, PostAction.replying_to(event)) notify(.compose, PostAction.replying_to(event))
} }
.accessibilityLabel(NSLocalizedString("Reply", comment: "Accessibility label for reply button")) .accessibilityLabel(NSLocalizedString("Reply", comment: "Accessibility label for reply button"))
@@ -55,14 +55,14 @@ struct EventActionBar: View {
Spacer() Spacer()
HStack(spacing: 4) { HStack(spacing: 4) {
EventActionButton(img: "repost", col: bar.boosted ? Color.green : nil) { EventActionButton(img: "arrow.2.squarepath", col: bar.boosted ? Color.green : nil) {
if bar.boosted { if bar.boosted {
notify(.delete, bar.our_boost) notify(.delete, bar.our_boost)
} else { } else {
self.show_repost_action = true self.show_repost_action = true
} }
} }
.accessibilityLabel(NSLocalizedString("Reposts", comment: "Accessibility label for boosts button")) .accessibilityLabel(NSLocalizedString("Boosts", comment: "Accessibility label for boosts button"))
Text(verbatim: "\(bar.boosts > 0 ? "\(bar.boosts)" : "")") Text(verbatim: "\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
.font(.footnote.weight(.medium)) .font(.footnote.weight(.medium))
.foregroundColor(bar.boosted ? Color.green : Color.gray) .foregroundColor(bar.boosted ? Color.green : Color.gray)
@@ -88,14 +88,14 @@ struct EventActionBar: View {
if let lnurl = self.lnurl { if let lnurl = self.lnurl {
Spacer() Spacer()
ZapButton(damus_state: damus_state, target: ZapTarget.note(id: event.id, author: event.pubkey), lnurl: lnurl, zaps: self.damus_state.events.get_cache_data(self.event.id).zaps_model) ZapButton(damus_state: damus_state, event: event, lnurl: lnurl, zaps: self.damus_state.events.get_cache_data(self.event.id).zaps_model)
} }
Spacer() Spacer()
EventActionButton(img: "upload", col: Color.gray) { EventActionButton(img: "square.and.arrow.up", col: Color.gray) {
show_share_action = true show_share_action = true
} }
.accessibilityLabel(NSLocalizedString("Share", comment: "Button to share a note")) .accessibilityLabel(NSLocalizedString("Share", comment: "Button to share a post"))
} }
.onAppear { .onAppear {
self.bar.update(damus: damus_state, evid: self.event.id) self.bar.update(damus: damus_state, evid: self.event.id)
@@ -161,12 +161,9 @@ struct EventActionBar: View {
func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View { func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View {
Button(action: action) { Button(action: action) {
Image(img) Image(systemName: img)
.resizable()
.foregroundColor(col == nil ? Color.gray : col!) .foregroundColor(col == nil ? Color.gray : col!)
.font(.footnote.weight(.medium)) .font(.footnote.weight(.medium))
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20)
} }
} }
@@ -191,16 +188,11 @@ struct LikeButton: View {
}) { }) {
if liked { if liked {
LINEAR_GRADIENT LINEAR_GRADIENT
.mask(Image("shaka.fill") .mask(Image("shaka-full")
.resizable() .resizable()
.aspectRatio(contentMode: .fit) ).frame(width: 14, height: 14)
)
.frame(width: 20, height: 20)
} else { } else {
Image("shaka") Image("shaka-line")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
} }
+4 -3
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(destination: RepostsView(damus_state: state, model: RepostsModel(state: state, target: target))) {
let noun = Text(verbatim: repostsCountString(bar.boosts)).foregroundColor(.gray) let noun = Text(verbatim: repostsCountString(bar.boosts)).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,7 +33,7 @@ struct EventDetailBar: View {
} }
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(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
let noun = Text(verbatim: reactionsCountString(bar.likes)).foregroundColor(.gray) let noun = Text(verbatim: reactionsCountString(bar.likes)).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'.")
} }
@@ -41,7 +41,8 @@ struct EventDetailBar: View {
} }
if bar.zaps > 0 { if bar.zaps > 0 {
NavigationLink(value: Route.Zaps(target: .note(id: target, author: target_pk))) { let dst = ZapsView(state: state, target: .note(id: target, author: target_pk))
NavigationLink(destination: dst) {
let noun = Text(verbatim: zapsCountString(bar.zaps)).foregroundColor(.gray) let noun = Text(verbatim: zapsCountString(bar.zaps)).foregroundColor(.gray)
Text("\(Text(verbatim: bar.zaps.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.") Text("\(Text(verbatim: bar.zaps.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.")
} }
+1 -1
View File
@@ -28,7 +28,7 @@ struct RepostAction: View {
damus_state.postbox.send(boost) damus_state.postbox.send(boost)
} label: { } label: {
Label(NSLocalizedString("Repost", comment: "Button to repost a note"), image: "repost") Label(NSLocalizedString("Repost", comment: "Button to repost a note"), systemImage: "arrow.2.squarepath")
.frame(maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .leading) .frame(maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .leading)
} }
+2 -2
View File
@@ -41,7 +41,7 @@ struct ShareAction: View {
UIPasteboard.general.string = "https://damus.io/" + (bech32_note_id(event.id) ?? event.id) UIPasteboard.general.string = "https://damus.io/" + (bech32_note_id(event.id) ?? event.id)
} }
let bookmarkImg = isBookmarked ? "bookmark.fill" : "bookmark" let bookmarkImg = isBookmarked ? "bookmark.slash" : "bookmark"
let bookmarkTxt = isBookmarked ? NSLocalizedString("Remove Bookmark", comment: "Button text to remove bookmark from a note.") : NSLocalizedString("Add Bookmark", comment: "Button text to add bookmark to a note.") let bookmarkTxt = isBookmarked ? NSLocalizedString("Remove Bookmark", comment: "Button text to remove bookmark from a note.") : NSLocalizedString("Add Bookmark", comment: "Button text to add bookmark to a note.")
ShareActionButton(img: bookmarkImg, text: bookmarkTxt) { ShareActionButton(img: bookmarkImg, text: bookmarkTxt) {
dismiss() dismiss()
@@ -54,7 +54,7 @@ struct ShareAction: View {
NotificationCenter.default.post(name: .broadcast_event, object: event) NotificationCenter.default.post(name: .broadcast_event, object: event)
} }
ShareActionButton(img: "upload", text: NSLocalizedString("Share Via...", comment: "Button to present iOS share sheet")) { ShareActionButton(img: "square.and.arrow.up", text: NSLocalizedString("Share Via...", comment: "Button to present iOS share sheet")) {
show_share = true show_share = true
dismiss() dismiss()
} }
@@ -27,7 +27,7 @@ struct ShareActionButton: View {
var body: some View { var body: some View {
Button(action: action) { Button(action: action) {
VStack() { VStack() {
Image(img) Image(systemName: img)
.foregroundColor(col) .foregroundColor(col)
.font(.system(size: 23, weight: .bold)) .font(.system(size: 23, weight: .bold))
.overlay { .overlay {
@@ -48,6 +48,6 @@ struct ShareActionButton: View {
struct ShareActionButton_Previews: PreviewProvider { struct ShareActionButton_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
ShareActionButton(img: "link", text: "Stretch", action: {}) ShareActionButton(img: "figure.flexibility", text: "Stretch", action: {})
} }
} }
+2 -2
View File
@@ -19,7 +19,7 @@ struct AddRelayView: View {
.autocorrectionDisabled(true) .autocorrectionDisabled(true)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
Label("", image: "close-circle") Label("", systemImage: "xmark.circle.fill")
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
.padding(.trailing, -25.0) .padding(.trailing, -25.0)
.opacity((relay == "") ? 0.0 : 1.0) .opacity((relay == "") ? 0.0 : 1.0)
@@ -28,7 +28,7 @@ struct AddRelayView: View {
} }
} }
Label("", image: "copy2") Label("", systemImage: "doc.on.clipboard")
.padding(.leading, -10) .padding(.leading, -10)
.onTapGesture { .onTapGesture {
if let pastedrelay = UIPasteboard.general.string { if let pastedrelay = UIPasteboard.general.string {
+2 -1
View File
@@ -29,7 +29,7 @@ struct BookmarksView: View {
Group { Group {
if bookmarks.isEmpty { if bookmarks.isEmpty {
VStack { VStack {
Image("bookmark") Image(systemName: "bookmark")
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 32.0, height: 32.0) .frame(width: 32.0, height: 32.0)
@@ -38,6 +38,7 @@ struct BookmarksView: View {
} else { } else {
ScrollView { ScrollView {
InnerTimelineView(events: EventHolder(events: bookmarks, incoming: []), damus: state, filter: noneFilter) InnerTimelineView(events: EventHolder(events: bookmarks, incoming: []), damus: state, filter: noneFilter)
} }
} }
} }
+1 -1
View File
@@ -23,7 +23,7 @@ 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")
} }
.offset(x: -25) .offset(x: -25)
.frame(minWidth: 300, maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .center) .frame(minWidth: 300, maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .center)
+4 -4
View File
@@ -21,13 +21,13 @@ struct FriendsButton: View {
}) { }) {
if filter == .friends { if filter == .friends {
LINEAR_GRADIENT LINEAR_GRADIENT
.mask(Image("user-added") .mask(Image(systemName: "person.2.fill")
.resizable() .resizable()
).frame(width: 28, height: 28) ).frame(width: 30, height: 20)
} else { } else {
Image("user-added") Image(systemName: "person.2.fill")
.resizable() .resizable()
.frame(width: 28, height: 28) .frame(width: 30, height: 20)
.foregroundColor(DamusColors.adaptableGrey) .foregroundColor(DamusColors.adaptableGrey)
} }
} }
+1 -1
View File
@@ -21,7 +21,7 @@ let carousel_items = [
CarouselItem(image: Image("undercover"), CarouselItem(image: Image("undercover"),
text: Text("\(Text("Private", comment: "Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading.").bold()). Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.", comment: "Explanation of what is done to keep personally identifiable information private. There is a heading that precedes this explanation which is a variable to this string.")), text: Text("\(Text("Private", comment: "Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading.").bold()). Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.", comment: "Explanation of what is done to keep personally identifiable information private. There is a heading that precedes this explanation which is a variable to this string.")),
CarouselItem(image: Image("bitcoin-p2p"), CarouselItem(image: Image("bitcoin-p2p"),
text: Text("\(Text("Earn Money", comment: "Heading indicating that this application allows users to earn money.").bold()). Tip your friends and stack sats with Bitcoin⚡️, the native currency of the internet.", comment: "Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string.")) text: Text("\(Text("Earn Money", comment: "Heading indicating that this application allows users to earn money.").bold()). Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet.", comment: "Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string."))
] ]
struct CarouselView: View { struct CarouselView: View {

Some files were not shown because too many files have changed in this diff Show More