Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
bc358dd164
|
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,7 +49,6 @@
|
|||||||
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 */; };
|
||||||
@@ -132,7 +128,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 +166,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 +193,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 +207,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 +234,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 */; };
|
||||||
@@ -304,8 +294,15 @@
|
|||||||
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 */; };
|
||||||
|
E4AE2AC92A227E6000680283 /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = E4AE2AC82A227E6000680283 /* LICENSE.txt */; };
|
||||||
|
E4AE2ACA2A227E7800680283 /* Inter-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = E4AE2AC62A227E4200680283 /* Inter-Regular.otf */; };
|
||||||
|
E4AE2ACB2A227E7E00680283 /* Inter-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = E4AE2AC52A227E3600680283 /* Inter-Bold.otf */; };
|
||||||
|
E4AE2ACC2A227E8300680283 /* Inter-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = E4AE2AC72A227E5400680283 /* Inter-Light.otf */; };
|
||||||
|
E4AE2ACE2A2286D000680283 /* FontManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4AE2ACD2A2286D000680283 /* FontManager.swift */; };
|
||||||
|
E4AE2AD32A228CB400680283 /* Inter-SemiBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = E4AE2AD12A228CA000680283 /* Inter-SemiBold.otf */; };
|
||||||
|
E4AE2AD42A228CB900680283 /* Inter-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = E4AE2AD22A228CA700680283 /* Inter-Medium.otf */; };
|
||||||
|
E4AE2AD52A228CC000680283 /* Inter-Italic.otf in Resources */ = {isa = PBXBuildFile; fileRef = E4AE2ACF2A228C7500680283 /* Inter-Italic.otf */; };
|
||||||
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.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 */; };
|
||||||
@@ -345,7 +342,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 +367,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 +384,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,7 +459,6 @@
|
|||||||
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>"; };
|
||||||
@@ -574,7 +567,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 +610,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 +637,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 +651,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 +681,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>"; };
|
||||||
@@ -754,8 +742,15 @@
|
|||||||
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>"; };
|
||||||
|
E4AE2AC52A227E3600680283 /* Inter-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inter-Bold.otf"; sourceTree = "<group>"; };
|
||||||
|
E4AE2AC62A227E4200680283 /* Inter-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inter-Regular.otf"; sourceTree = "<group>"; };
|
||||||
|
E4AE2AC72A227E5400680283 /* Inter-Light.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inter-Light.otf"; sourceTree = "<group>"; };
|
||||||
|
E4AE2AC82A227E6000680283 /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; };
|
||||||
|
E4AE2ACD2A2286D000680283 /* FontManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontManager.swift; sourceTree = "<group>"; };
|
||||||
|
E4AE2ACF2A228C7500680283 /* Inter-Italic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inter-Italic.otf"; sourceTree = "<group>"; };
|
||||||
|
E4AE2AD12A228CA000680283 /* Inter-SemiBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inter-SemiBold.otf"; sourceTree = "<group>"; };
|
||||||
|
E4AE2AD22A228CA700680283 /* Inter-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inter-Medium.otf"; sourceTree = "<group>"; };
|
||||||
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.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>"; };
|
||||||
@@ -776,7 +771,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 +872,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 +913,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>";
|
||||||
@@ -957,15 +949,6 @@
|
|||||||
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 +965,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 +975,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 */,
|
||||||
@@ -1156,8 +1137,7 @@
|
|||||||
4CA5588229F33F5B00DC6A45 /* StringCodable.swift */,
|
4CA5588229F33F5B00DC6A45 /* StringCodable.swift */,
|
||||||
50B5685229F97CB400A23243 /* CredentialHandler.swift */,
|
50B5685229F97CB400A23243 /* CredentialHandler.swift */,
|
||||||
4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */,
|
4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */,
|
||||||
3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */,
|
E4AE2ACD2A2286D000680283 /* FontManager.swift */,
|
||||||
D2277EE92A089BD5006C3807 /* Router.swift */,
|
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1171,14 +1151,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 +1160,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 +1191,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 +1202,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>";
|
||||||
@@ -1310,9 +1280,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;
|
||||||
@@ -1327,6 +1295,7 @@
|
|||||||
4CE6DEE527F7A08100C66700 /* damus */ = {
|
4CE6DEE527F7A08100C66700 /* damus */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E40B46262A2272E6005E70FD /* Fonts */,
|
||||||
F7F0BA23297892AE009531F3 /* Modifiers */,
|
F7F0BA23297892AE009531F3 /* Modifiers */,
|
||||||
4C4A3A5A288A1B2200453788 /* damus.entitlements */,
|
4C4A3A5A288A1B2200453788 /* damus.entitlements */,
|
||||||
4CE4F9DF285287A000C00DD9 /* Components */,
|
4CE4F9DF285287A000C00DD9 /* Components */,
|
||||||
@@ -1416,7 +1385,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>";
|
||||||
@@ -1484,6 +1452,20 @@
|
|||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E40B46262A2272E6005E70FD /* Fonts */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E4AE2AC52A227E3600680283 /* Inter-Bold.otf */,
|
||||||
|
E4AE2AC62A227E4200680283 /* Inter-Regular.otf */,
|
||||||
|
E4AE2AC72A227E5400680283 /* Inter-Light.otf */,
|
||||||
|
E4AE2AD12A228CA000680283 /* Inter-SemiBold.otf */,
|
||||||
|
E4AE2AD22A228CA700680283 /* Inter-Medium.otf */,
|
||||||
|
E4AE2ACF2A228C7500680283 /* Inter-Italic.otf */,
|
||||||
|
E4AE2AC82A227E6000680283 /* LICENSE.txt */,
|
||||||
|
);
|
||||||
|
path = Fonts;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
F7F0BA23297892AE009531F3 /* Modifiers */ = {
|
F7F0BA23297892AE009531F3 /* Modifiers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1519,7 +1501,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 +1605,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 = "";
|
||||||
@@ -1642,10 +1622,17 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
E4AE2AD52A228CC000680283 /* Inter-Italic.otf in Resources */,
|
||||||
|
E4AE2AD42A228CB900680283 /* Inter-Medium.otf in Resources */,
|
||||||
|
E4AE2AD32A228CB400680283 /* Inter-SemiBold.otf in Resources */,
|
||||||
|
E4AE2ACC2A227E8300680283 /* Inter-Light.otf in Resources */,
|
||||||
|
E4AE2ACB2A227E7E00680283 /* Inter-Bold.otf in Resources */,
|
||||||
|
E4AE2ACA2A227E7800680283 /* Inter-Regular.otf in Resources */,
|
||||||
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 */,
|
50DA11262A16A23F00236234 /* Launch.storyboard in Resources */,
|
||||||
|
E4AE2AC92A227E6000680283 /* LICENSE.txt 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 +1669,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 +1687,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 */,
|
||||||
@@ -1768,6 +1753,7 @@
|
|||||||
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
||||||
4C7D09592A05BEAD00943473 /* KeyboardVisible.swift in Sources */,
|
4C7D09592A05BEAD00943473 /* KeyboardVisible.swift in Sources */,
|
||||||
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
||||||
|
E4AE2ACE2A2286D000680283 /* FontManager.swift in Sources */,
|
||||||
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */,
|
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */,
|
||||||
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */,
|
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */,
|
||||||
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */,
|
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */,
|
||||||
@@ -1786,7 +1772,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 +1811,9 @@
|
|||||||
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 */,
|
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 */,
|
||||||
@@ -1865,8 +1848,6 @@
|
|||||||
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
|
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
|
||||||
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.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,7 +1895,6 @@
|
|||||||
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 */,
|
||||||
@@ -1929,12 +1909,11 @@
|
|||||||
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 */,
|
||||||
@@ -1946,7 +1925,6 @@
|
|||||||
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 */,
|
4C2859622A12A7F0004746F7 /* GoldSupportGradient.swift in Sources */,
|
||||||
@@ -2242,7 +2220,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 = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -2260,7 +2238,6 @@
|
|||||||
INFOPLIST_KEY_UILaunchStoryboardName = Launch.storyboard;
|
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 +2268,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 = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -2309,7 +2286,6 @@
|
|||||||
INFOPLIST_KEY_UILaunchStoryboardName = Launch.storyboard;
|
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 +2316,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 +2336,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 +2441,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,11 +2454,6 @@
|
|||||||
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 */
|
/* Begin XCVersionGroup section */
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -24,11 +24,9 @@ struct GradientButtonStyle: ButtonStyle {
|
|||||||
struct GradientButtonStyle_Previews: PreviewProvider {
|
struct GradientButtonStyle_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Button(action: {
|
Button("Dynamic Size", action: {
|
||||||
print("dynamic size")
|
print("dynamic size")
|
||||||
}) {
|
})
|
||||||
Text(verbatim: "Dynamic Size")
|
|
||||||
}
|
|
||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ struct GradientButtonStyle_Previews: PreviewProvider {
|
|||||||
print("infinite width")
|
print("infinite width")
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(verbatim: "Infinite Width")
|
Text("Infinite Width")
|
||||||
}
|
}
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,9 +53,8 @@ enum ImageShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Image Carousel
|
// MARK: - Image Carousel
|
||||||
@MainActor
|
|
||||||
struct ImageCarousel: View {
|
struct ImageCarousel: View {
|
||||||
var urls: [MediaUrl]
|
var urls: [URL]
|
||||||
|
|
||||||
let evid: String
|
let evid: String
|
||||||
|
|
||||||
@@ -70,13 +69,11 @@ struct ImageCarousel: View {
|
|||||||
@State private var firstImageHeight: CGFloat? = nil
|
@State private var firstImageHeight: CGFloat? = nil
|
||||||
@State private var currentImageHeight: CGFloat?
|
@State private var currentImageHeight: CGFloat?
|
||||||
@State private var selectedIndex = 0
|
@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
|
||||||
@@ -105,46 +102,21 @@ struct ImageCarousel: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.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 Images: some View {
|
||||||
return state.events.get_video_player_model(url: url)
|
TabView(selection: $selectedIndex) {
|
||||||
}
|
ForEach(urls.indices, id: \.self) { index in
|
||||||
|
let url = urls[index]
|
||||||
func Media(geo: GeometryProxy, url: MediaUrl, index: Int) -> some View {
|
GeometryReader { geo in
|
||||||
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)
|
KFAnimatedImage(url)
|
||||||
.callbackQueue(.dispatch(.global(qos:.background)))
|
.callbackQueue(.dispatch(.global(qos:.background)))
|
||||||
.backgroundDecode(true)
|
.backgroundDecode(true)
|
||||||
@@ -155,7 +127,7 @@ struct ImageCarousel: View {
|
|||||||
view.framePreloadCount = 3
|
view.framePreloadCount = 3
|
||||||
}
|
}
|
||||||
.imageFill(for: geo.size, max: maxHeight, fill: fillHeight) { fill in
|
.imageFill(for: geo.size, max: maxHeight, fill: fillHeight) { fill in
|
||||||
state.events.get_cache_data(evid).media_metadata_model.fill = fill
|
state.previews.cache_image_meta(evid: evid, image_fill: fill)
|
||||||
// blur hash can be discarded when we have the url
|
// blur hash can be discarded when we have the url
|
||||||
// NOTE: this is the wrong place for this... we need to remove
|
// NOTE: this is the wrong place for this... we need to remove
|
||||||
// it when the image is loaded in memory. This may happen
|
// it when the image is loaded in memory. This may happen
|
||||||
@@ -175,28 +147,23 @@ struct ImageCarousel: View {
|
|||||||
Placeholder(url: url, geo_size: geo.size, num_urls: urls.count)
|
Placeholder(url: url, geo_size: geo.size, num_urls: urls.count)
|
||||||
}
|
}
|
||||||
.aspectRatio(contentMode: filling ? .fill : .fit)
|
.aspectRatio(contentMode: filling ? .fill : .fit)
|
||||||
.position(x: geo.size.width / 2, y: geo.size.height / 2)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Text(url.absoluteString)
|
Text(url.absoluteString)
|
||||||
}
|
}
|
||||||
.id(url.absoluteString)
|
.id(url.absoluteString)
|
||||||
.padding(0)
|
.padding(0)
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var Medias: some View {
|
|
||||||
TabView(selection: $selectedIndex) {
|
|
||||||
ForEach(urls.indices, id: \.self) { index in
|
|
||||||
GeometryReader { geo in
|
|
||||||
Media(geo: geo, url: urls[index], index: index)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
.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: height)
|
||||||
|
.onTapGesture {
|
||||||
|
open_sheet = true
|
||||||
|
}
|
||||||
.onChange(of: selectedIndex) { value in
|
.onChange(of: selectedIndex) { value in
|
||||||
selectedIndex = value
|
selectedIndex = value
|
||||||
}
|
}
|
||||||
@@ -205,8 +172,7 @@ struct ImageCarousel: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Medias
|
Images
|
||||||
.onTapGesture { }
|
|
||||||
|
|
||||||
// This is our custom carousel image indicator
|
// This is our custom carousel image indicator
|
||||||
CarouselDotsView(urls: urls, selectedIndex: $selectedIndex)
|
CarouselDotsView(urls: urls, selectedIndex: $selectedIndex)
|
||||||
@@ -215,8 +181,8 @@ struct ImageCarousel: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Custom Carousel
|
// MARK: - Custom Carousel
|
||||||
struct CarouselDotsView<T>: View {
|
struct CarouselDotsView: View {
|
||||||
let urls: [T]
|
let urls: [URL]
|
||||||
@Binding var selectedIndex: Int
|
@Binding var selectedIndex: Int
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -288,8 +254,7 @@ public struct ImageFill {
|
|||||||
// MARK: - Preview Provider
|
// 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])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ struct Reposted: View {
|
|||||||
.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,20 +42,20 @@ 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
|
let blocks = parse_mentions(content: about, tags: [])
|
||||||
|
let about_string = render_blocks(blocks: blocks, profiles: damus_state.profiles).content.attributed
|
||||||
|
Text(about_string)
|
||||||
.lineLimit(3)
|
.lineLimit(3)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if spacer {
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
struct UserView_Previews: PreviewProvider {
|
struct UserView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
|
|||||||
@@ -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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,31 +10,36 @@ 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 {
|
||||||
@@ -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,7 +118,6 @@ 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(zap_img)
|
Image(zap_img)
|
||||||
@@ -116,7 +127,6 @@ struct ZapButton: View {
|
|||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.frame(width:20, height: 20)
|
.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 +136,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 +180,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 +196,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 {
|
||||||
@@ -194,7 +231,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
|
||||||
@@ -208,7 +245,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(.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,35 +258,24 @@ 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
|
var flusher: OnFlush? = nil
|
||||||
|
// Don't donate on custom zaps
|
||||||
// donations are only enabled on one-tap zaps and off appstore
|
if !is_custom && damus_state.settings.donation_percent > 0 {
|
||||||
if !damus_state.settings.nozaps && !is_custom && damus_state.settings.donation_percent > 0 {
|
|
||||||
flusher = .once({ pe in
|
flusher = .once({ pe in
|
||||||
// send donation zap when the pending zap is flushed, this allows user to cancel and not send a donation
|
// send donation zap when the pending zap is flushed, this allows user to cancel and not send a donation
|
||||||
Task { @MainActor in
|
Task.init { @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)
|
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 nwc_req = nwc_pay(url: nwc_state.url, pool: damus_state.pool, post: damus_state.postbox, invoice: inv, on_flush: flusher)
|
||||||
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 {
|
guard let nwc_req, case .nwc(let pzap_state) = pending_zap_state else {
|
||||||
print("nwc: failed to send nwc request for zapreq \(reqid.reqid)")
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,13 +284,9 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
|
|||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+95
-153
@@ -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(damus_state: damus, 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."), image: "filter")
|
||||||
.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, *) {
|
||||||
@@ -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!
|
||||||
|
|
||||||
@@ -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
|
|
||||||
let find_from: [String]?
|
|
||||||
|
|
||||||
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) {
|
if let ev = state.events.lookup(evid) {
|
||||||
callback(.event(ev))
|
callback(ev)
|
||||||
return
|
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(ids: [evid]) : NostrFilter(authors: [evid])
|
||||||
|
|
||||||
|
if search_type == .profile {
|
||||||
|
filter.kinds = [.metadata]
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
} else {
|
||||||
return
|
callback(ev)
|
||||||
}
|
|
||||||
}
|
|
||||||
case .event:
|
|
||||||
callback(.event(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,11 +902,12 @@ 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):
|
||||||
result(.filter(filt))
|
result(.filter(filt))
|
||||||
break
|
break
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,94 @@
|
|||||||
|
Copyright (c) 2016-2020 The Inter Project Authors.
|
||||||
|
"Inter" is trademark of Rasmus Andersson.
|
||||||
|
https://github.com/rsms/inter
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION AND CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
@@ -70,5 +70,16 @@
|
|||||||
<string>Damus needs access to your camera if you want to upload photos from it</string>
|
<string>Damus needs access to your camera if you want to upload photos from it</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Damus needs access to your microphone if you want to upload recorded videos from it</string>
|
<string>Damus needs access to your microphone if you want to upload recorded videos from it</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string></string>
|
||||||
|
<key>UIAppFonts</key>
|
||||||
|
<array>
|
||||||
|
<string>Inter-Regular.otf</string>
|
||||||
|
<string>Inter-Bold.otf</string>
|
||||||
|
<string>Inter-Light.otf</string>
|
||||||
|
<string>Inter-SemiBold.otf</string>
|
||||||
|
<string>Inter-Medium.otf</string>
|
||||||
|
<string>Inter-Italic.otf</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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? {
|
||||||
|
|||||||
@@ -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(settings: UserSettingsStore())) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+68
-137
@@ -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
|
||||||
@@ -165,38 +164,65 @@ class HomeModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 case .done(let zap) = zapres else { return }
|
|
||||||
|
|
||||||
guard zap.target.pubkey == self.damus_state.keypair.pubkey else {
|
guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.notifications.insert_zap(.zap(zap)) {
|
damus_state.add_zap(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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.notification_status.new_events = new_bits
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +397,12 @@ 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(kinds: [.contacts],
|
||||||
let subscription = NostrSubscribe(filters: [filter], sub_id: init_subid)
|
limit: 1,
|
||||||
pool.send(.subscribe(subscription), to: [relay_id])
|
authors: [damus_state.pubkey])
|
||||||
|
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
|
||||||
@@ -441,7 +467,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 +550,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 +583,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 +601,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 +611,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 = []
|
||||||
@@ -752,7 +778,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 +799,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 +811,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 +884,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 +1101,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 +1118,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 +1170,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1199,7 +1202,7 @@ func create_local_notification(profiles: Profiles, notify: LocalNotification) {
|
|||||||
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 +1224,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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
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]])
|
||||||
|
|||||||
@@ -7,27 +7,11 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum ReportType: String, CustomStringConvertible, CaseIterable {
|
enum ReportType: String {
|
||||||
case spam
|
case explicit
|
||||||
case nudity
|
|
||||||
case profanity
|
|
||||||
case illegal
|
case illegal
|
||||||
|
case spam
|
||||||
case impersonation
|
case impersonation
|
||||||
|
|
||||||
var description: String {
|
|
||||||
switch self {
|
|
||||||
case .spam:
|
|
||||||
return NSLocalizedString("Spam", comment: "Description of report type for spam.")
|
|
||||||
case .nudity:
|
|
||||||
return NSLocalizedString("Nudity", comment: "Description of report type for nudity.")
|
|
||||||
case .profanity:
|
|
||||||
return NSLocalizedString("Profanity", comment: "Description of report type for profanity.")
|
|
||||||
case .illegal:
|
|
||||||
return NSLocalizedString("Illegal Content", comment: "Description of report type for illegal content.")
|
|
||||||
case .impersonation:
|
|
||||||
return NSLocalizedString("Impersonation", comment: "Description of report type for impersonation.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ReportNoteTarget {
|
struct ReportNoteTarget {
|
||||||
@@ -47,12 +31,16 @@ struct Report {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func create_report_tags(target: ReportTarget, type: ReportType) -> [[String]] {
|
func create_report_tags(target: ReportTarget, type: ReportType) -> [[String]] {
|
||||||
|
var tags: [[String]]
|
||||||
switch target {
|
switch target {
|
||||||
case .user(let pubkey):
|
case .user(let pubkey):
|
||||||
return [["p", pubkey, type.rawValue]]
|
tags = [["p", pubkey]]
|
||||||
case .note(let notet):
|
case .note(let notet):
|
||||||
return [["e", notet.note_id, type.rawValue], ["p", notet.pubkey]]
|
tags = [["e", notet.note_id], ["p", notet.pubkey]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tags.append(["report", type.rawValue])
|
||||||
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
func create_report_event(privkey: String, report: Report) -> NostrEvent? {
|
func create_report_event(privkey: String, report: Report) -> NostrEvent? {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -84,7 +84,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 +126,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
|
||||||
|
|
||||||
|
|||||||
@@ -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 = ""
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ class Profile: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var damus_donation: Int? {
|
var damus_donation: Int? {
|
||||||
get { return int("damus_donation_v2"); }
|
get { return int("damus_donation"); }
|
||||||
set(s) { set_int("damus_donation_v2", s) }
|
set(s) { set_int("damus_donation", s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var picture: String? {
|
var picture: String? {
|
||||||
|
|||||||
@@ -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? {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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 {
|
||||||
|
|||||||
@@ -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."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,7 +365,7 @@ 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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)?
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// FontManager.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Ben Weeks on 27/05/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FontManager {
|
||||||
|
struct dynamicSize {
|
||||||
|
public static var largeTitle: CGFloat = UIFont.preferredFont(forTextStyle: .largeTitle).pointSize - 1
|
||||||
|
public static var title1: CGFloat = UIFont.preferredFont(forTextStyle: .title1).pointSize - 0
|
||||||
|
public static var title2: CGFloat = UIFont.preferredFont(forTextStyle: .title2).pointSize - 0
|
||||||
|
public static var title3: CGFloat = UIFont.preferredFont(forTextStyle: .title3).pointSize - 0
|
||||||
|
public static var body: CGFloat = UIFont.preferredFont(forTextStyle: .body).pointSize - 1
|
||||||
|
public static var callout: CGFloat = UIFont.preferredFont(forTextStyle: .callout).pointSize - 1
|
||||||
|
public static var caption1: CGFloat = UIFont.preferredFont(forTextStyle: .caption1).pointSize - 1
|
||||||
|
public static var caption2: CGFloat = UIFont.preferredFont(forTextStyle: .caption2).pointSize - 1
|
||||||
|
public static var footnote: CGFloat = UIFont.preferredFont(forTextStyle: .footnote).pointSize - 1
|
||||||
|
public static var headline: CGFloat = UIFont.preferredFont(forTextStyle: .headline).pointSize - 1
|
||||||
|
public static var subheadline: CGFloat = UIFont.preferredFont(forTextStyle: .subheadline).pointSize - 1
|
||||||
|
// repeat for all the dynamic sizes
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Inter {
|
||||||
|
static let familyRoot = "Inter"
|
||||||
|
|
||||||
|
static let bold = "\(familyRoot)-Bold"
|
||||||
|
static let regular = "\(familyRoot)-Regular"
|
||||||
|
static let light = "\(familyRoot)-Light"
|
||||||
|
static let medium = "\(familyRoot)-Medium"
|
||||||
|
static let semibold = "\(familyRoot)-SemiBold"
|
||||||
|
static let italic = "\(familyRoot)-Italic"
|
||||||
|
|
||||||
|
static let largeTitle: Font = Font.custom(FontManager.Inter.regular, size: FontManager.dynamicSize.largeTitle)
|
||||||
|
static let title1: Font = Font.custom(FontManager.Inter.semibold, size: FontManager.dynamicSize.title1)
|
||||||
|
static let title2: Font = Font.custom(FontManager.Inter.semibold, size: FontManager.dynamicSize.title2)
|
||||||
|
static let title3: Font = Font.custom(FontManager.Inter.semibold, size: FontManager.dynamicSize.title3)
|
||||||
|
static let body: Font = Font.custom(FontManager.Inter.regular, size: FontManager.dynamicSize.body)
|
||||||
|
static let caption1: Font = Font.custom(FontManager.Inter.regular, size: FontManager.dynamicSize.caption1)
|
||||||
|
static let caption2: Font = Font.custom(FontManager.Inter.regular, size: FontManager.dynamicSize.caption2)
|
||||||
|
static let footnote: Font = Font.custom(FontManager.Inter.regular, size: FontManager.dynamicSize.footnote)
|
||||||
|
static let headline: Font = Font.custom(FontManager.Inter.regular, size: FontManager.dynamicSize.headline)
|
||||||
|
static let subheadline: Font = Font.custom(FontManager.Inter.regular, size: FontManager.dynamicSize.subheadline)
|
||||||
|
// repeat for other sizes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Font {
|
||||||
|
public static var largeTitle = FontManager.Inter.largeTitle
|
||||||
|
public static var title1 = FontManager.Inter.title1
|
||||||
|
public static var title2 = FontManager.Inter.title2
|
||||||
|
public static var title3 = FontManager.Inter.title3
|
||||||
|
public static var body = FontManager.Inter.body
|
||||||
|
public static var caption1 = FontManager.Inter.caption1
|
||||||
|
public static var caption2 = FontManager.Inter.caption2
|
||||||
|
public static var footnote = FontManager.Inter.footnote
|
||||||
|
public static var headline = FontManager.Inter.headline
|
||||||
|
public static var subheadline = FontManager.Inter.subheadline
|
||||||
|
// repeat for the rest of the dynamic sizes
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ 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)")
|
print("nwc: replacing pending with real zap \(new_zap.request.id)")
|
||||||
zaps[i] = new_zap
|
zaps[i] = new_zap
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -44,5 +44,4 @@ enum LocalNotificationType: String {
|
|||||||
case mention
|
case mention
|
||||||
case repost
|
case repost
|
||||||
case zap
|
case zap
|
||||||
case profile_zap
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = [:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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) + "..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+9
-28
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-11
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ struct EventActionBar: View {
|
|||||||
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: "upload", 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)
|
||||||
|
|||||||
@@ -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'.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -36,31 +36,32 @@ struct ConfigView: View {
|
|||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
NavigationLink(value: Route.KeySettings(keypair: state.keypair)) {
|
NavigationLink(destination: KeySettingsView(keypair: state.keypair)) {
|
||||||
IconLabel(NSLocalizedString("Keys", comment: "Settings section for managing keys"), img_name: "key", color: .purple)
|
IconLabel(NSLocalizedString("Keys", comment: "Settings section for managing keys"), img_name: "key", color: .purple)
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(value: Route.AppearanceSettings(settings: settings)) {
|
NavigationLink(destination: AppearanceSettingsView(settings: settings)) {
|
||||||
IconLabel(NSLocalizedString("Appearance", comment: "Section header for text and appearance settings"), img_name: "eye", color: .red)
|
IconLabel(NSLocalizedString("Appearance", comment: "Section header for text and appearance settings"), img_name: "eye", color: .red)
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(value: Route.SearchSettings(settings: settings)) {
|
NavigationLink(destination: SearchSettingsView(settings: settings)) {
|
||||||
IconLabel(NSLocalizedString("Search/Universe", comment: "Section header for search/universe settings"), img_name: "magnifyingglass", color: .red)
|
IconLabel(NSLocalizedString("Search/Universe", comment: "Section header for search/universe settings"), img_name: "magnifyingglass", color: .red)
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(value: Route.NotificationSettings(settings: settings)) {
|
NavigationLink(destination: NotificationSettingsView(settings: settings)) {
|
||||||
IconLabel(NSLocalizedString("Notifications", comment: "Section header for Damus notifications"), img_name: "notification-bell-on", color: .blue)
|
IconLabel(NSLocalizedString("Notifications", comment: "Section header for Damus notifications"), img_name: "notification-bell-on", color: .blue)
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(value: Route.ZapSettings(settings: settings)) {
|
NavigationLink(destination: ZapSettingsView(settings: settings)) {
|
||||||
IconLabel(NSLocalizedString("Zaps", comment: "Section header for zap settings"), img_name: "zap.fill", color: .orange)
|
IconLabel(NSLocalizedString("Zaps", comment: "Section header for zap settings"), img_name: "zap.fill", color: .orange)
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(value: Route.TranslationSettings(settings: settings)) {
|
NavigationLink(destination: TranslationSettingsView(settings: settings)) {
|
||||||
IconLabel(NSLocalizedString("Translation", comment: "Section header for text and appearance settings"), img_name: "globe", color: .green)
|
IconLabel(NSLocalizedString("Translation", comment: "Section header for text and appearance settings"), img_name: "globe", color: .green)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
|
Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if state.keypair.privkey == nil {
|
if state.keypair.privkey == nil {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import SwiftUI
|
|||||||
struct CreateAccountView: View {
|
struct CreateAccountView: View {
|
||||||
@StateObject var account: CreateAccountModel = CreateAccountModel()
|
@StateObject var account: CreateAccountModel = CreateAccountModel()
|
||||||
@StateObject var profileUploadViewModel = ProfileUploadingViewModel()
|
@StateObject var profileUploadViewModel = ProfileUploadingViewModel()
|
||||||
var nav: NavigationCoordinator
|
|
||||||
|
@State var is_done: Bool = false
|
||||||
|
|
||||||
func SignupForm<FormContent: View>(@ViewBuilder content: () -> FormContent) -> some View {
|
func SignupForm<FormContent: View>(@ViewBuilder content: () -> FormContent) -> some View {
|
||||||
return VStack(alignment: .leading, spacing: 10.0, content: content)
|
return VStack(alignment: .leading, spacing: 10.0, content: content)
|
||||||
@@ -24,6 +25,10 @@ struct CreateAccountView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
|
NavigationLink(destination: SaveKeysView(account: account), isActive: $is_done) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
ProfilePictureSelector(pubkey: account.pubkey, viewModel: profileUploadViewModel, callback: uploadedProfilePicture(image_url:))
|
ProfilePictureSelector(pubkey: account.pubkey, viewModel: profileUploadViewModel, callback: uploadedProfilePicture(image_url:))
|
||||||
@@ -58,7 +63,7 @@ struct CreateAccountView: View {
|
|||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
nav.push(route: Route.SaveKeys(account: account))
|
self.is_done = true
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Create account now", comment: "Button to create account.")
|
Text("Create account now", comment: "Button to create account.")
|
||||||
@@ -91,7 +96,7 @@ struct LoginPrompt: View {
|
|||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Already on Nostr?", comment: "Ask the user if they already have an account on Nostr")
|
Text("Already on nostr?", comment: "Ask the user if they already have an account on nostr")
|
||||||
.foregroundColor(Color("DamusMediumGrey"))
|
.foregroundColor(Color("DamusMediumGrey"))
|
||||||
|
|
||||||
Button(NSLocalizedString("Login", comment: "Button to navigate to login view.")) {
|
Button(NSLocalizedString("Login", comment: "Button to navigate to login view.")) {
|
||||||
@@ -130,7 +135,7 @@ extension View {
|
|||||||
struct CreateAccountView_Previews: PreviewProvider {
|
struct CreateAccountView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let model = CreateAccountModel(real: "", nick: "jb55", about: "")
|
let model = CreateAccountModel(real: "", nick: "jb55", about: "")
|
||||||
return CreateAccountView(account: model, nav: .init())
|
return CreateAccountView(account: model)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +167,7 @@ func FormLabel(_ title: String, optional: Bool = false) -> some View {
|
|||||||
Text(title)
|
Text(title)
|
||||||
.bold()
|
.bold()
|
||||||
if optional {
|
if optional {
|
||||||
Text("optional", comment: "Label indicating that a form input is optional.")
|
Text("- optional", comment: "Label indicating that a form input is optional.")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ struct DMChatView: View, KeyboardReadable {
|
|||||||
|
|
||||||
var Header: some View {
|
var Header: some View {
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||||
return NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) {
|
let profile_page = ProfileView(damus_state: damus_state, pubkey: pubkey)
|
||||||
|
return NavigationLink(destination: profile_page) {
|
||||||
HStack {
|
HStack {
|
||||||
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ struct DirectMessagesView: View {
|
|||||||
|
|
||||||
func MainContent(requests: Bool) -> some View {
|
func MainContent(requests: Bool) -> some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
let chat = DMChatView(damus_state: damus_state, dms: model.active_model)
|
||||||
|
NavigationLink(destination: chat, isActive: $model.open_dm) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
LazyVStack(spacing: 0) {
|
LazyVStack(spacing: 0) {
|
||||||
if model.dms.isEmpty, !model.loading {
|
if model.dms.isEmpty, !model.loading {
|
||||||
EmptyTimelineView()
|
EmptyTimelineView()
|
||||||
@@ -50,8 +54,7 @@ struct DirectMessagesView: View {
|
|||||||
if ok, let ev = model.events.last {
|
if ok, let ev = model.events.last {
|
||||||
EventView(damus: damus_state, event: ev, pubkey: model.pubkey, options: options)
|
EventView(damus: damus_state, event: ev, pubkey: model.pubkey, options: options)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
self.model.set_active_dm_model(model)
|
self.model.open_dm_by_model(model)
|
||||||
damus_state.nav.push(route: Route.DMChat(dms: self.model.active_model))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|||||||
@@ -56,13 +56,18 @@ By using our Application, you signify your acceptance of this EULA. If you do no
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
struct EULAView: View {
|
struct EULAView: View {
|
||||||
|
@State private var login = false
|
||||||
|
@State var accepted = false
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
var nav: NavigationCoordinator
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
NavigationLink(destination: LoginView(accepted: $accepted), isActive: $login) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
|
||||||
Text(Markdown.parse(content: eula))
|
Text(Markdown.parse(content: eula))
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
@@ -91,7 +96,8 @@ struct EULAView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
nav.push(route: Route.Login)
|
accepted = true
|
||||||
|
login.toggle()
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Accept", comment: "Button to accept the end user license agreement before being allowed into the app.")
|
Text("Accept", comment: "Button to accept the end user license agreement before being allowed into the app.")
|
||||||
@@ -111,7 +117,7 @@ struct EULAView: View {
|
|||||||
.ignoresSafeArea(),
|
.ignoresSafeArea(),
|
||||||
alignment: .top
|
alignment: .top
|
||||||
)
|
)
|
||||||
.navigationTitle(NSLocalizedString("EULA", comment: "Navigation title of view that shows the EULA, an acronym for End User License Agreement."))
|
.navigationTitle("EULA")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.navigationBarItems(leading: BackNav())
|
.navigationBarItems(leading: BackNav())
|
||||||
@@ -120,6 +126,6 @@ struct EULAView: View {
|
|||||||
|
|
||||||
struct EULAView_Previews: PreviewProvider {
|
struct EULAView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
EULAView(nav: .init())
|
EULAView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,8 @@ struct EventView: View {
|
|||||||
}
|
}
|
||||||
} else if event.known_kind == .zap {
|
} else if event.known_kind == .zap {
|
||||||
if let zap = damus.zaps.zaps[event.id] {
|
if let zap = damus.zaps.zaps[event.id] {
|
||||||
ZapEvent(damus: damus, zap: zap, is_top_zap: options.contains(.top_zap))
|
ZapEvent(damus: damus, zap: zap)
|
||||||
} else {
|
} else {
|
||||||
Text("Invalid Zap", comment: "Text indicating that a zap event is malformed and could not be displayed.")
|
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ struct BuilderEventView: View {
|
|||||||
if let event {
|
if let event {
|
||||||
let ev = event.get_inner_event(cache: damus.events) ?? event
|
let ev = event.get_inner_event(cache: damus.events) ?? event
|
||||||
let thread = ThreadModel(event: ev, damus_state: damus)
|
let thread = ThreadModel(event: ev, damus_state: damus)
|
||||||
NavigationLink(value: Route.Thread(thread: thread)) {
|
let dest = ThreadView(state: damus, thread: thread)
|
||||||
|
NavigationLink(destination: dest) {
|
||||||
EventView(damus: damus, event: event, options: .embedded)
|
EventView(damus: damus, event: event, options: .embedded)
|
||||||
.padding([.top, .bottom], 8)
|
.padding([.top, .bottom], 8)
|
||||||
}.buttonStyle(.plain)
|
}.buttonStyle(.plain)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ struct EventProfile: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
VStack {
|
VStack {
|
||||||
NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) {
|
NavigationLink(destination: ProfileView(damus_state: damus_state, pubkey: pubkey)) {
|
||||||
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles, disable_animation: disable_animation)
|
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles, disable_animation: disable_animation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ struct MutedEventView: View {
|
|||||||
.foregroundColor(DamusColors.adaptableGrey)
|
.foregroundColor(DamusColors.adaptableGrey)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Note from a user you've muted", comment: "Text to indicate that what is being shown is a note from a user who has been muted.")
|
Text("Post from a user you've muted", comment: "Text to indicate that what is being shown is a post from a user who has been muted.")
|
||||||
Spacer()
|
Spacer()
|
||||||
Button(shown ? NSLocalizedString("Hide", comment: "Button to hide a note from a user who has been muted.") : NSLocalizedString("Show", comment: "Button to show a note from a user who has been muted.")) {
|
Button(shown ? NSLocalizedString("Hide", comment: "Button to hide a post from a user who has been muted.") : NSLocalizedString("Show", comment: "Button to show a post from a user who has been muted.")) {
|
||||||
shown.toggle()
|
shown.toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func reply_desc(profiles: Profiles, event: NostrEvent, locale: Locale = Locale.c
|
|||||||
|
|
||||||
let names: [String] = pubkeys.map {
|
let names: [String] = pubkeys.map {
|
||||||
let prof = profiles.lookup(id: $0)
|
let prof = profiles.lookup(id: $0)
|
||||||
return Profile.displayName(profile: prof, pubkey: $0).username.truncate(maxLength: 50)
|
return Profile.displayName(profile: prof, pubkey: $0).username
|
||||||
}
|
}
|
||||||
|
|
||||||
let uniqueNames = NSOrderedSet(array: names).array as! [String]
|
let uniqueNames = NSOrderedSet(array: names).array as! [String]
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ struct EventViewOptions: OptionSet {
|
|||||||
static let no_translate = EventViewOptions(rawValue: 1 << 6)
|
static let no_translate = EventViewOptions(rawValue: 1 << 6)
|
||||||
static let small_pfp = EventViewOptions(rawValue: 1 << 7)
|
static let small_pfp = EventViewOptions(rawValue: 1 << 7)
|
||||||
static let nested = EventViewOptions(rawValue: 1 << 8)
|
static let nested = EventViewOptions(rawValue: 1 << 8)
|
||||||
static let top_zap = EventViewOptions(rawValue: 1 << 9)
|
|
||||||
|
|
||||||
static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
|
static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
|
||||||
}
|
}
|
||||||
@@ -132,7 +131,7 @@ struct TextEvent: View {
|
|||||||
|
|
||||||
func ProfileName(is_anon: Bool) -> some View {
|
func ProfileName(is_anon: Bool) -> some View {
|
||||||
let profile = damus.profiles.lookup(id: pubkey)
|
let profile = damus.profiles.lookup(id: pubkey)
|
||||||
let pk = is_anon ? ANON_PUBKEY : pubkey
|
let pk = is_anon ? "anon" : pubkey
|
||||||
return EventProfileName(pubkey: pk, profile: profile, damus: damus, size: .normal)
|
return EventProfileName(pubkey: pk, profile: profile, damus: damus, size: .normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,23 +10,13 @@ import SwiftUI
|
|||||||
struct ZapEvent: View {
|
struct ZapEvent: View {
|
||||||
let damus: DamusState
|
let damus: DamusState
|
||||||
let zap: Zapping
|
let zap: Zapping
|
||||||
let is_top_zap: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Image("zap.fill")
|
Text("⚡️ \(format_msats(zap.amount))", comment: "Text indicating the zap amount. i.e. number of satoshis that were tipped to a user")
|
||||||
.foregroundColor(.orange)
|
|
||||||
|
|
||||||
Text(verbatim: format_msats(zap.amount))
|
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
|
|
||||||
if is_top_zap {
|
|
||||||
Text("Top Zap", comment: "Text indicating that this zap is the one with the highest amount of sats.")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.padding([.top], 2)
|
.padding([.top], 2)
|
||||||
}
|
|
||||||
|
|
||||||
if zap.is_private {
|
if zap.is_private {
|
||||||
Image("lock")
|
Image("lock")
|
||||||
@@ -41,7 +31,7 @@ struct ZapEvent: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEvent(damus: damus, event: zap.request.ev, pubkey: zap.request.ev.pubkey, options: [.no_action_bar, .no_replying_to])
|
TextEvent(damus: damus, event: zap.request, pubkey: zap.request.pubkey, options: [.no_action_bar, .no_replying_to])
|
||||||
.padding([.top], 1)
|
.padding([.top], 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,18 +41,18 @@ struct ZapEvent: View {
|
|||||||
let test_zap_invoice = ZapInvoice(description: .description("description"), amount: 10000, string: "lnbc1", expiry: 1000000, payment_hash: Data(), created_at: 1000000)
|
let test_zap_invoice = ZapInvoice(description: .description("description"), amount: 10000, string: "lnbc1", expiry: 1000000, payment_hash: Data(), created_at: 1000000)
|
||||||
let test_zap_request_ev = NostrEvent(content: "hi", pubkey: "pk", kind: 9734)
|
let test_zap_request_ev = NostrEvent(content: "hi", pubkey: "pk", kind: 9734)
|
||||||
let test_zap_request = ZapRequest(ev: test_zap_request_ev)
|
let test_zap_request = ZapRequest(ev: test_zap_request_ev)
|
||||||
let test_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: nil)
|
let test_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), request: test_zap_request, is_anon: false, private_request: nil)
|
||||||
|
|
||||||
let test_private_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: .init(ev: test_event))
|
let test_private_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), request: test_zap_request, is_anon: false, private_request: test_event)
|
||||||
|
|
||||||
let test_pending_zap = PendingZap(amount_msat: 10000, target: .note(id: "id", author: "pk"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
|
let test_pending_zap = PendingZap(amount_msat: 10000, target: .note(id: "id", author: "pk"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
|
||||||
|
|
||||||
struct ZapEvent_Previews: PreviewProvider {
|
struct ZapEvent_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack {
|
VStack {
|
||||||
ZapEvent(damus: test_damus_state(), zap: .zap(test_zap), is_top_zap: true)
|
ZapEvent(damus: test_damus_state(), zap: .zap(test_zap))
|
||||||
|
|
||||||
ZapEvent(damus: test_damus_state(), zap: .zap(test_private_zap), is_top_zap: false)
|
ZapEvent(damus: test_damus_state(), zap: .zap(test_private_zap))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,16 @@ struct FollowUserView: View {
|
|||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
|
||||||
static let markdown = Markdown()
|
static let markdown = Markdown()
|
||||||
|
@State var navigating: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let dest = ProfileView(damus_state: damus_state, pubkey: target.pubkey)
|
||||||
|
NavigationLink(destination: dest, isActive: $navigating) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
UserViewRow(damus_state: damus_state, pubkey: target.pubkey)
|
UserViewRow(damus_state: damus_state, pubkey: target.pubkey)
|
||||||
.onTapGesture {
|
|
||||||
damus_state.nav.push(route: Route.ProfileByKey(pubkey: target.pubkey))
|
|
||||||
}
|
|
||||||
|
|
||||||
FollowButtonView(target: target, follows_you: false, follow_state: damus_state.contacts.follow_state(target.pubkey))
|
FollowButtonView(target: target, follows_you: false, follow_state: damus_state.contacts.follow_state(target.pubkey))
|
||||||
}
|
}
|
||||||
@@ -26,27 +29,11 @@ struct FollowUserView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FollowersYouKnowView: View {
|
|
||||||
let damus_state: DamusState
|
|
||||||
let friended_followers: [String]
|
|
||||||
@ObservedObject var followers: FollowersModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ScrollView {
|
|
||||||
LazyVStack(alignment: .leading) {
|
|
||||||
ForEach(friended_followers, id: \.self) { pk in
|
|
||||||
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
.navigationBarTitle(NSLocalizedString("Followers You Know", comment: "Navigation bar title for view that shows who is following a user."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FollowersView: View {
|
struct FollowersView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
@ObservedObject var followers: FollowersModel
|
let whos: String
|
||||||
|
|
||||||
|
@EnvironmentObject var followers: FollowersModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@@ -71,7 +58,7 @@ struct FollowingView: View {
|
|||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
|
||||||
let following: FollowingModel
|
let following: FollowingModel
|
||||||
|
let whos: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import Kingfisher
|
|||||||
|
|
||||||
|
|
||||||
struct ImageContainerView: View {
|
struct ImageContainerView: View {
|
||||||
let cache: EventCache
|
let url: URL?
|
||||||
let url: MediaUrl
|
|
||||||
|
|
||||||
@State private var image: UIImage?
|
@State private var image: UIImage?
|
||||||
@State private var showShareSheet = false
|
@State private var showShareSheet = false
|
||||||
@@ -27,7 +26,8 @@ struct ImageContainerView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Img(url: URL) -> some View {
|
var body: some View {
|
||||||
|
|
||||||
KFAnimatedImage(url)
|
KFAnimatedImage(url)
|
||||||
.imageContext(.note, disable_animation: disable_animation)
|
.imageContext(.note, disable_animation: disable_animation)
|
||||||
.configure { view in
|
.configure { view in
|
||||||
@@ -40,23 +40,12 @@ struct ImageContainerView: View {
|
|||||||
ShareSheet(activityItems: [url])
|
ShareSheet(activityItems: [url])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Group {
|
|
||||||
switch url {
|
|
||||||
case .image(let url):
|
|
||||||
Img(url: url)
|
|
||||||
case .video(let url):
|
|
||||||
DamusVideoPlayer(url: url, model: cache.get_video_player_model(url: url), video_size: .constant(nil))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let test_image_url = URL(string: "https://jb55.com/red-me.jpg")!
|
let test_image_url = URL(string: "https://jb55.com/red-me.jpg")!
|
||||||
|
|
||||||
struct ImageContainerView_Previews: PreviewProvider {
|
struct ImageContainerView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ImageContainerView(cache: test_damus_state().events, url: .image(test_image_url), disable_animation: false)
|
ImageContainerView(url: test_image_url, disable_animation: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ImageView: View {
|
struct ImageView: View {
|
||||||
let cache: EventCache
|
|
||||||
let urls: [MediaUrl]
|
let urls: [URL?]
|
||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ struct ImageView: View {
|
|||||||
TabView(selection: $selectedIndex) {
|
TabView(selection: $selectedIndex) {
|
||||||
ForEach(urls.indices, id: \.self) { index in
|
ForEach(urls.indices, id: \.self) { index in
|
||||||
ZoomableScrollView {
|
ZoomableScrollView {
|
||||||
ImageContainerView(cache: cache, url: urls[index], disable_animation: disable_animation)
|
ImageContainerView(url: urls[index], disable_animation: disable_animation)
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.padding(.top, Theme.safeAreaInsets?.top)
|
.padding(.top, Theme.safeAreaInsets?.top)
|
||||||
.padding(.bottom, Theme.safeAreaInsets?.bottom)
|
.padding(.bottom, Theme.safeAreaInsets?.bottom)
|
||||||
@@ -79,7 +79,6 @@ struct ImageView: View {
|
|||||||
|
|
||||||
struct ImageView_Previews: PreviewProvider {
|
struct ImageView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!)
|
ImageView(urls: [URL(string: "https://jb55.com/red-me.jpg")], disable_animation: false)
|
||||||
ImageView(cache: test_damus_state().events, urls: [url], disable_animation: false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,13 @@ enum ParsedKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct LoginView: View {
|
struct LoginView: View {
|
||||||
|
@State private var create_account = false
|
||||||
@State var key: String = ""
|
@State var key: String = ""
|
||||||
@State var is_pubkey: Bool = false
|
@State var is_pubkey: Bool = false
|
||||||
@State var error: String? = nil
|
@State var error: String? = nil
|
||||||
@State private var credential_handler = CredentialHandler()
|
@State private var credential_handler = CredentialHandler()
|
||||||
var nav: NavigationCoordinator
|
|
||||||
|
@Binding var accepted: Bool
|
||||||
|
|
||||||
func get_error(parsed_key: ParsedKey?) -> String? {
|
func get_error(parsed_key: ParsedKey?) -> String? {
|
||||||
if self.error != nil {
|
if self.error != nil {
|
||||||
@@ -53,6 +55,12 @@ struct LoginView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
|
if accepted {
|
||||||
|
NavigationLink(destination: CreateAccountView(), isActive: $create_account) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
SignInHeader()
|
SignInHeader()
|
||||||
.padding(.top, 100)
|
.padding(.top, 100)
|
||||||
@@ -72,10 +80,9 @@ struct LoginView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if parsed?.is_pub ?? false {
|
if parsed?.is_pub ?? false {
|
||||||
Text("This is a public key, you will not be able to make notes or interact in any way. This is used for viewing accounts from their perspective.", comment: "Warning that the inputted account key is a public key and the result of what happens because of it.")
|
Text("This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective.", comment: "Warning that the inputted account key is a public key and the result of what happens because of it.")
|
||||||
.foregroundColor(Color.orange)
|
.foregroundColor(Color.orange)
|
||||||
.bold()
|
.bold()
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let p = parsed {
|
if let p = parsed {
|
||||||
@@ -99,7 +106,7 @@ struct LoginView: View {
|
|||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateAccountPrompt(nav: nav)
|
CreateAccountPrompt(create_account: $create_account)
|
||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -329,14 +336,14 @@ struct SignInEntry: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct CreateAccountPrompt: View {
|
struct CreateAccountPrompt: View {
|
||||||
var nav: NavigationCoordinator
|
@Binding var create_account: Bool
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text("New to Nostr?", comment: "Ask the user if they are new to Nostr")
|
Text("New to nostr?", comment: "Ask the user if they are new to nostr")
|
||||||
.foregroundColor(Color("DamusMediumGrey"))
|
.foregroundColor(Color("DamusMediumGrey"))
|
||||||
|
|
||||||
Button(NSLocalizedString("Create account", comment: "Button to navigate to create account view.")) {
|
Button(NSLocalizedString("Create account", comment: "Button to navigate to create account view.")) {
|
||||||
nav.push(route: Route.CreateAccount)
|
create_account.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -350,8 +357,8 @@ struct LoginView_Previews: PreviewProvider {
|
|||||||
let pubkey = "npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955"
|
let pubkey = "npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955"
|
||||||
let bech32_pubkey = "KeyInput"
|
let bech32_pubkey = "KeyInput"
|
||||||
Group {
|
Group {
|
||||||
LoginView(key: pubkey, nav: .init())
|
LoginView(key: pubkey, accepted: .constant(true))
|
||||||
LoginView(key: bech32_pubkey, nav: .init())
|
LoginView(key: bech32_pubkey, accepted: .constant(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ struct TabButton: View {
|
|||||||
let timeline: Timeline
|
let timeline: Timeline
|
||||||
let img: String
|
let img: String
|
||||||
@Binding var selected: Timeline
|
@Binding var selected: Timeline
|
||||||
@ObservedObject var nstatus: NotificationStatusModel
|
@Binding var new_events: NewEventsBits
|
||||||
|
|
||||||
let settings: UserSettingsStore
|
let settings: UserSettingsStore
|
||||||
let action: (Timeline) -> ()
|
let action: (Timeline) -> ()
|
||||||
@@ -38,7 +38,7 @@ struct TabButton: View {
|
|||||||
ZStack(alignment: .center) {
|
ZStack(alignment: .center) {
|
||||||
Tab
|
Tab
|
||||||
|
|
||||||
if show_indicator(timeline: timeline, current: nstatus.new_events, indicator_setting: settings.notification_indicators) {
|
if show_indicator(timeline: timeline, current: new_events, indicator_setting: settings.notification_indicators) {
|
||||||
Circle()
|
Circle()
|
||||||
.size(CGSize(width: 8, height: 8))
|
.size(CGSize(width: 8, height: 8))
|
||||||
.frame(width: 10, height: 10, alignment: .topTrailing)
|
.frame(width: 10, height: 10, alignment: .topTrailing)
|
||||||
@@ -53,7 +53,7 @@ struct TabButton: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
action(timeline)
|
action(timeline)
|
||||||
let bits = timeline_to_notification_bits(timeline, ev: nil)
|
let bits = timeline_to_notification_bits(timeline, ev: nil)
|
||||||
nstatus.new_events = NewEventsBits(rawValue: nstatus.new_events.rawValue & ~bits.rawValue)
|
new_events = NewEventsBits(rawValue: new_events.rawValue & ~bits.rawValue)
|
||||||
}) {
|
}) {
|
||||||
Image(selected != timeline ? img : "\(img).fill")
|
Image(selected != timeline ? img : "\(img).fill")
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
@@ -65,7 +65,7 @@ struct TabButton: View {
|
|||||||
|
|
||||||
|
|
||||||
struct TabBar: View {
|
struct TabBar: View {
|
||||||
var nstatus: NotificationStatusModel
|
@Binding var new_events: NewEventsBits
|
||||||
@Binding var selected: Timeline
|
@Binding var selected: Timeline
|
||||||
|
|
||||||
let settings: UserSettingsStore
|
let settings: UserSettingsStore
|
||||||
@@ -75,10 +75,10 @@ struct TabBar: View {
|
|||||||
VStack {
|
VStack {
|
||||||
Divider()
|
Divider()
|
||||||
HStack {
|
HStack {
|
||||||
TabButton(timeline: .home, img: "home", selected: $selected, nstatus: nstatus, settings: settings, action: action).keyboardShortcut("1")
|
TabButton(timeline: .home, img: "home", selected: $selected, new_events: $new_events, settings: settings, action: action).keyboardShortcut("1")
|
||||||
TabButton(timeline: .dms, img: "messages", selected: $selected, nstatus: nstatus, settings: settings, action: action).keyboardShortcut("2")
|
TabButton(timeline: .dms, img: "messages", selected: $selected, new_events: $new_events, settings: settings, action: action).keyboardShortcut("2")
|
||||||
TabButton(timeline: .search, img: "search", selected: $selected, nstatus: nstatus, settings: settings, action: action).keyboardShortcut("3")
|
TabButton(timeline: .search, img: "search", selected: $selected, new_events: $new_events, settings: settings, action: action).keyboardShortcut("3")
|
||||||
TabButton(timeline: .notifications, img: "notification-bell", selected: $selected, nstatus: nstatus, settings: settings, action: action).keyboardShortcut("4")
|
TabButton(timeline: .notifications, img: "notification-bell", selected: $selected, new_events: $new_events, settings: settings, action: action).keyboardShortcut("4")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,11 +130,11 @@ struct NoteContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if show_images && artifacts.media.count > 0 {
|
if show_images && artifacts.images.count > 0 {
|
||||||
ImageCarousel(state: damus_state, evid: event.id, urls: artifacts.media)
|
ImageCarousel(state: damus_state, evid: event.id, urls: artifacts.images)
|
||||||
} else if !show_images && artifacts.media.count > 0 {
|
} else if !show_images && artifacts.images.count > 0 {
|
||||||
ZStack {
|
ZStack {
|
||||||
ImageCarousel(state: damus_state, evid: event.id, urls: artifacts.media)
|
ImageCarousel(state: damus_state, evid: event.id, urls: artifacts.images)
|
||||||
Blur()
|
Blur()
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText {
|
|||||||
case .pubkey:
|
case .pubkey:
|
||||||
let pk = m.ref.ref_id
|
let pk = m.ref.ref_id
|
||||||
let profile = profiles.lookup(id: pk)
|
let profile = profiles.lookup(id: pk)
|
||||||
let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
|
let disp = Profile.displayName(profile: profile, pubkey: pk).username
|
||||||
var attributedString = AttributedString(stringLiteral: "@\(disp)")
|
var attributedString = AttributedString(stringLiteral: "@\(disp)")
|
||||||
attributedString.link = URL(string: "damus:\(encode_pubkey_uri(m.ref))")
|
attributedString.link = URL(string: "damus:\(encode_pubkey_uri(m.ref))")
|
||||||
attributedString.foregroundColor = DamusColors.purple
|
attributedString.foregroundColor = DamusColors.purple
|
||||||
@@ -261,24 +261,13 @@ struct NoteArtifacts: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let content: CompatibleText
|
let content: CompatibleText
|
||||||
let urls: [UrlType]
|
let images: [URL]
|
||||||
let invoices: [Invoice]
|
let invoices: [Invoice]
|
||||||
|
let links: [URL]
|
||||||
var media: [MediaUrl] {
|
|
||||||
return urls.compactMap { url in url.is_media }
|
|
||||||
}
|
|
||||||
|
|
||||||
var images: [URL] {
|
|
||||||
return urls.compactMap { url in url.is_img }
|
|
||||||
}
|
|
||||||
|
|
||||||
var links: [URL] {
|
|
||||||
return urls.compactMap { url in url.is_link }
|
|
||||||
}
|
|
||||||
|
|
||||||
static func just_content(_ content: String) -> NoteArtifacts {
|
static func just_content(_ content: String) -> NoteArtifacts {
|
||||||
let txt = CompatibleText(attributed: AttributedString(stringLiteral: content))
|
let txt = CompatibleText(attributed: AttributedString(stringLiteral: content))
|
||||||
return NoteArtifacts(content: txt, urls: [], invoices: [])
|
return NoteArtifacts(content: txt, images: [], invoices: [], links: [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +304,8 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -
|
|||||||
|
|
||||||
func render_blocks(blocks: [Block], profiles: Profiles) -> NoteArtifacts {
|
func render_blocks(blocks: [Block], profiles: Profiles) -> NoteArtifacts {
|
||||||
var invoices: [Invoice] = []
|
var invoices: [Invoice] = []
|
||||||
var urls: [UrlType] = []
|
var img_urls: [URL] = []
|
||||||
|
var link_urls: [URL] = []
|
||||||
|
|
||||||
let one_note_ref = blocks
|
let one_note_ref = blocks
|
||||||
.filter({ $0.is_note_mention })
|
.filter({ $0.is_note_mention })
|
||||||
@@ -333,14 +323,12 @@ func render_blocks(blocks: [Block], profiles: Profiles) -> NoteArtifacts {
|
|||||||
return str + mention_str(m, profiles: profiles)
|
return str + mention_str(m, profiles: profiles)
|
||||||
case .text(let txt):
|
case .text(let txt):
|
||||||
var trimmed = txt
|
var trimmed = txt
|
||||||
if let prev = blocks[safe: ind-1],
|
if let prev = blocks[safe: ind-1], case .url(let u) = prev, is_image_url(u) {
|
||||||
case .url(let u) = prev,
|
|
||||||
classify_url(u).is_media != nil {
|
|
||||||
trimmed = " " + trim_prefix(trimmed)
|
trimmed = " " + trim_prefix(trimmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let next = blocks[safe: ind+1] {
|
if let next = blocks[safe: ind+1] {
|
||||||
if case .url(let u) = next, classify_url(u).is_media != nil {
|
if case .url(let u) = next, is_image_url(u) {
|
||||||
trimmed = trim_suffix(trimmed)
|
trimmed = trim_suffix(trimmed)
|
||||||
} else if case .mention(let m) = next, m.type == .event, one_note_ref {
|
} else if case .mention(let m) = next, m.type == .event, one_note_ref {
|
||||||
trimmed = trim_suffix(trimmed)
|
trimmed = trim_suffix(trimmed)
|
||||||
@@ -357,112 +345,25 @@ func render_blocks(blocks: [Block], profiles: Profiles) -> NoteArtifacts {
|
|||||||
invoices.append(invoice)
|
invoices.append(invoice)
|
||||||
return str
|
return str
|
||||||
case .url(let url):
|
case .url(let url):
|
||||||
let url_type = classify_url(url)
|
// Handle Image URLs
|
||||||
switch url_type {
|
if is_image_url(url) {
|
||||||
case .media:
|
// Append Image
|
||||||
urls.append(url_type)
|
img_urls.append(url)
|
||||||
return str
|
return str
|
||||||
case .link(let url):
|
} else {
|
||||||
urls.append(url_type)
|
link_urls.append(url)
|
||||||
return str + url_str(url)
|
return str + url_str(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NoteArtifacts(content: txt, urls: urls, invoices: invoices)
|
return NoteArtifacts(content: txt, images: img_urls, invoices: invoices, links: link_urls)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MediaUrl {
|
func is_image_url(_ url: URL) -> Bool {
|
||||||
case image(URL)
|
|
||||||
case video(URL)
|
|
||||||
|
|
||||||
var url: URL {
|
|
||||||
switch self {
|
|
||||||
case .image(let url):
|
|
||||||
return url
|
|
||||||
case .video(let url):
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UrlType {
|
|
||||||
case media(MediaUrl)
|
|
||||||
case link(URL)
|
|
||||||
|
|
||||||
var url: URL {
|
|
||||||
switch self {
|
|
||||||
case .media(let media_url):
|
|
||||||
switch media_url {
|
|
||||||
case .image(let url):
|
|
||||||
return url
|
|
||||||
case .video(let url):
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
case .link(let url):
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_video: URL? {
|
|
||||||
switch self {
|
|
||||||
case .media(let media_url):
|
|
||||||
switch media_url {
|
|
||||||
case .image:
|
|
||||||
return nil
|
|
||||||
case .video(let url):
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
case .link:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_img: URL? {
|
|
||||||
switch self {
|
|
||||||
case .media(let media_url):
|
|
||||||
switch media_url {
|
|
||||||
case .image(let url):
|
|
||||||
return url
|
|
||||||
case .video:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case .link:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_link: URL? {
|
|
||||||
switch self {
|
|
||||||
case .media:
|
|
||||||
return nil
|
|
||||||
case .link(let url):
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_media: MediaUrl? {
|
|
||||||
switch self {
|
|
||||||
case .media(let murl):
|
|
||||||
return murl
|
|
||||||
case .link:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func classify_url(_ url: URL) -> UrlType {
|
|
||||||
let str = url.lastPathComponent.lowercased()
|
let str = url.lastPathComponent.lowercased()
|
||||||
|
let isUrl = str.hasSuffix(".png") || str.hasSuffix(".jpg") || str.hasSuffix(".jpeg") || str.hasSuffix(".gif") || str.hasSuffix(".webp")
|
||||||
if str.hasSuffix(".png") || str.hasSuffix(".jpg") || str.hasSuffix(".jpeg") || str.hasSuffix(".gif") || str.hasSuffix(".webp") {
|
return isUrl
|
||||||
return .media(.image(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
if str.hasSuffix(".mp4") || str.hasSuffix(".mov") {
|
|
||||||
return .media(.video(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
return .link(url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_cached_preview_size(previews: PreviewCache, evid: String) -> CGFloat? {
|
func lookup_cached_preview_size(previews: PreviewCache, evid: String) -> CGFloat? {
|
||||||
|
|||||||
@@ -14,15 +14,6 @@ enum EventGroupType {
|
|||||||
case zap(ZapGroup)
|
case zap(ZapGroup)
|
||||||
case profile_zap(ZapGroup)
|
case profile_zap(ZapGroup)
|
||||||
|
|
||||||
var is_note_zap: Bool {
|
|
||||||
switch self {
|
|
||||||
case .repost: return false
|
|
||||||
case .reaction: return false
|
|
||||||
case .zap: return true
|
|
||||||
case .profile_zap: return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var zap_group: ZapGroup? {
|
var zap_group: ZapGroup? {
|
||||||
switch self {
|
switch self {
|
||||||
case .profile_zap(let grp):
|
case .profile_zap(let grp):
|
||||||
@@ -51,7 +42,7 @@ enum EventGroupType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ReactingTo {
|
enum ReactingTo {
|
||||||
case your_note
|
case your_post
|
||||||
case tagged_in
|
case tagged_in
|
||||||
case your_profile
|
case your_profile
|
||||||
}
|
}
|
||||||
@@ -62,7 +53,7 @@ func determine_reacting_to(our_pubkey: String, ev: NostrEvent?) -> ReactingTo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ev.pubkey == our_pubkey {
|
if ev.pubkey == our_pubkey {
|
||||||
return .your_note
|
return .your_post
|
||||||
}
|
}
|
||||||
|
|
||||||
return .tagged_in
|
return .tagged_in
|
||||||
@@ -70,45 +61,22 @@ func determine_reacting_to(our_pubkey: String, ev: NostrEvent?) -> ReactingTo {
|
|||||||
|
|
||||||
func event_author_name(profiles: Profiles, pubkey: String) -> String {
|
func event_author_name(profiles: Profiles, pubkey: String) -> String {
|
||||||
let alice_prof = profiles.lookup(id: pubkey)
|
let alice_prof = profiles.lookup(id: pubkey)
|
||||||
return Profile.displayName(profile: alice_prof, pubkey: pubkey).username.truncate(maxLength: 50)
|
return Profile.displayName(profile: alice_prof, pubkey: pubkey).username
|
||||||
}
|
}
|
||||||
|
|
||||||
func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [String] {
|
func event_group_author_name(profiles: Profiles, ind: Int, group: EventGroupType) -> String {
|
||||||
var seen = Set<String>()
|
|
||||||
var sorted = [String]()
|
|
||||||
|
|
||||||
if let zapgrp = group.zap_group {
|
if let zapgrp = group.zap_group {
|
||||||
let zaps = zapgrp.zaps
|
let zap = zapgrp.zaps[ind]
|
||||||
|
|
||||||
for i in 0..<zaps.count {
|
|
||||||
let zap = zapgrp.zaps[i]
|
|
||||||
let pubkey: String
|
|
||||||
|
|
||||||
if zap.is_anon {
|
if zap.is_anon {
|
||||||
pubkey = ANON_PUBKEY
|
return NSLocalizedString("Anonymous", comment: "Placeholder author name of the anonymous person who zapped an event.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return event_author_name(profiles: profiles, pubkey: zap.request.pubkey)
|
||||||
} else {
|
} else {
|
||||||
pubkey = zap.request.ev.pubkey
|
let ev = group.events[ind]
|
||||||
|
return event_author_name(profiles: profiles, pubkey: ev.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !seen.contains(pubkey) {
|
|
||||||
seen.insert(pubkey)
|
|
||||||
sorted.append(pubkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let events = group.events
|
|
||||||
|
|
||||||
for i in 0..<events.count {
|
|
||||||
let ev = events[i]
|
|
||||||
let pubkey = ev.pubkey
|
|
||||||
if !seen.contains(pubkey) {
|
|
||||||
seen.insert(pubkey)
|
|
||||||
sorted.append(pubkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sorted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,9 +89,9 @@ func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [S
|
|||||||
"reacted_tagged_in_1" - returned when 1 reaction occurred to a post that the current user was tagged in
|
"reacted_tagged_in_1" - returned when 1 reaction occurred to a post that the current user was tagged in
|
||||||
"reacted_tagged_in_2" - returned when 2 reactions occurred to a post that the current user was tagged in
|
"reacted_tagged_in_2" - returned when 2 reactions occurred to a post that the current user was tagged in
|
||||||
"reacted_tagged_in_3" - returned when 3 or more reactions occurred to a post that the current user was tagged in
|
"reacted_tagged_in_3" - returned when 3 or more reactions occurred to a post that the current user was tagged in
|
||||||
"reacted_your_note_1" - returned when 1 reaction occurred to the current user's post
|
"reacted_your_post_1" - returned when 1 reaction occurred to the current user's post
|
||||||
"reacted_your_note_2" - returned when 2 reactions occurred to the current user's post
|
"reacted_your_post_2" - returned when 2 reactions occurred to the current user's post
|
||||||
"reacted_your_note_3" - returned when 3 or more reactions occurred to the current user's post
|
"reacted_your_post_3" - returned when 3 or more reactions occurred to the current user's post
|
||||||
"reacted_your_profile_1" - returned when 1 reaction occurred to the current user's profile
|
"reacted_your_profile_1" - returned when 1 reaction occurred to the current user's profile
|
||||||
"reacted_your_profile_2" - returned when 2 reactions occurred to the current user's profile
|
"reacted_your_profile_2" - returned when 2 reactions occurred to the current user's profile
|
||||||
"reacted_your_profile_3" - returned when 3 or more reactions occurred to the current user's profile
|
"reacted_your_profile_3" - returned when 3 or more reactions occurred to the current user's profile
|
||||||
@@ -131,9 +99,9 @@ func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [S
|
|||||||
"reposted_tagged_in_1" - returned when 1 repost occurred to a post that the current user was tagged in
|
"reposted_tagged_in_1" - returned when 1 repost occurred to a post that the current user was tagged in
|
||||||
"reposted_tagged_in_2" - returned when 2 reposts occurred to a post that the current user was tagged in
|
"reposted_tagged_in_2" - returned when 2 reposts occurred to a post that the current user was tagged in
|
||||||
"reposted_tagged_in_3" - returned when 3 or more reposts occurred to a post that the current user was tagged in
|
"reposted_tagged_in_3" - returned when 3 or more reposts occurred to a post that the current user was tagged in
|
||||||
"reposted_your_note_1" - returned when 1 repost occurred to the current user's post
|
"reposted_your_post_1" - returned when 1 repost occurred to the current user's post
|
||||||
"reposted_your_note_2" - returned when 2 reposts occurred to the current user's post
|
"reposted_your_post_2" - returned when 2 reposts occurred to the current user's post
|
||||||
"reposted_your_note_3" - returned when 3 or more reposts occurred to the current user's post
|
"reposted_your_post_3" - returned when 3 or more reposts occurred to the current user's post
|
||||||
"reposted_your_profile_1" - returned when 1 repost occurred to the current user's profile
|
"reposted_your_profile_1" - returned when 1 repost occurred to the current user's profile
|
||||||
"reposted_your_profile_2" - returned when 2 reposts occurred to the current user's profile
|
"reposted_your_profile_2" - returned when 2 reposts occurred to the current user's profile
|
||||||
"reposted_your_profile_3" - returned when 3 or more reposts occurred to the current user's profile
|
"reposted_your_profile_3" - returned when 3 or more reposts occurred to the current user's profile
|
||||||
@@ -141,36 +109,36 @@ func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [S
|
|||||||
"zapped_tagged_in_1" - returned when 1 zap occurred to a post that the current user was tagged in
|
"zapped_tagged_in_1" - returned when 1 zap occurred to a post that the current user was tagged in
|
||||||
"zapped_tagged_in_2" - returned when 2 zaps occurred to a post that the current user was tagged in
|
"zapped_tagged_in_2" - returned when 2 zaps occurred to a post that the current user was tagged in
|
||||||
"zapped_tagged_in_3" - returned when 3 or more zaps occurred to a post that the current user was tagged in
|
"zapped_tagged_in_3" - returned when 3 or more zaps occurred to a post that the current user was tagged in
|
||||||
"zapped_your_note_1" - returned when 1 zap occurred to the current user's post
|
"zapped_your_post_1" - returned when 1 zap occurred to the current user's post
|
||||||
"zapped_your_note_2" - returned when 2 zaps occurred to the current user's post
|
"zapped_your_post_2" - returned when 2 zaps occurred to the current user's post
|
||||||
"zapped_your_note_3" - returned when 3 or more zaps occurred to the current user's post
|
"zapped_your_post_3" - returned when 3 or more zaps occurred to the current user's post
|
||||||
"zapped_your_profile_1" - returned when 1 zap occurred to the current user's profile
|
"zapped_your_profile_1" - returned when 1 zap occurred to the current user's profile
|
||||||
"zapped_your_profile_2" - returned when 2 zaps occurred to the current user's profile
|
"zapped_your_profile_2" - returned when 2 zaps occurred to the current user's profile
|
||||||
"zapped_your_profile_3" - returned when 3 or more zaps occurred to the current user's profile
|
"zapped_your_profile_3" - returned when 3 or more zaps occurred to the current user's profile
|
||||||
*/
|
*/
|
||||||
func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?, pubkeys: [String], locale: Locale? = nil) -> String {
|
func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?, locale: Locale? = nil) -> String {
|
||||||
if group.events.count == 0 {
|
if group.events.count == 0 {
|
||||||
return "??"
|
return "??"
|
||||||
}
|
}
|
||||||
|
|
||||||
let verb = reacting_to_verb(group: group)
|
let verb = reacting_to_verb(group: group)
|
||||||
let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev)
|
let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev)
|
||||||
let localization_key = "\(verb)_\(reacting_to)_\(min(pubkeys.count, 3))"
|
let localization_key = "\(verb)_\(reacting_to)_\(min(group.events.count, 3))"
|
||||||
let format = localizedStringFormat(key: localization_key, locale: locale)
|
let format = localizedStringFormat(key: localization_key, locale: locale)
|
||||||
|
|
||||||
switch pubkeys.count {
|
switch group.events.count {
|
||||||
case 1:
|
case 1:
|
||||||
let display_name = event_author_name(profiles: profiles, pubkey: pubkeys[0])
|
let display_name = event_group_author_name(profiles: profiles, ind: 0, group: group)
|
||||||
|
|
||||||
return String(format: format, locale: locale, display_name)
|
return String(format: format, locale: locale, display_name)
|
||||||
case 2:
|
case 2:
|
||||||
let alice_name = event_author_name(profiles: profiles, pubkey: pubkeys[0])
|
let alice_name = event_group_author_name(profiles: profiles, ind: 0, group: group)
|
||||||
let bob_name = event_author_name(profiles: profiles, pubkey: pubkeys[1])
|
let bob_name = event_group_author_name(profiles: profiles, ind: 1, group: group)
|
||||||
|
|
||||||
return String(format: format, locale: locale, alice_name, bob_name)
|
return String(format: format, locale: locale, alice_name, bob_name)
|
||||||
default:
|
default:
|
||||||
let alice_name = event_author_name(profiles: profiles, pubkey: pubkeys[0])
|
let alice_name = event_group_author_name(profiles: profiles, ind: 0, group: group)
|
||||||
let count = pubkeys.count - 1
|
let count = group.events.count - 1
|
||||||
|
|
||||||
return String(format: format, locale: locale, count, alice_name)
|
return String(format: format, locale: locale, count, alice_name)
|
||||||
}
|
}
|
||||||
@@ -193,8 +161,8 @@ struct EventGroupView: View {
|
|||||||
let event: NostrEvent?
|
let event: NostrEvent?
|
||||||
let group: EventGroupType
|
let group: EventGroupType
|
||||||
|
|
||||||
func GroupDescription(_ pubkeys: [String]) -> some View {
|
var GroupDescription: some View {
|
||||||
Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event, pubkeys: pubkeys))")
|
Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event))")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ZapIcon(_ zapgrp: ZapGroup) -> some View {
|
func ZapIcon(_ zapgrp: ZapGroup) -> some View {
|
||||||
@@ -234,15 +202,14 @@ struct EventGroupView: View {
|
|||||||
.frame(width: PFP_SIZE + 10)
|
.frame(width: PFP_SIZE + 10)
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
let unique_pubkeys = event_group_unique_pubkeys(profiles: state.profiles, group: group)
|
ProfilePicturesView(state: state, pubkeys: group.events.map { $0.pubkey })
|
||||||
|
|
||||||
ProfilePicturesView(state: state, pubkeys: unique_pubkeys)
|
|
||||||
|
|
||||||
if let event {
|
if let event {
|
||||||
let thread = ThreadModel(event: event, damus_state: state)
|
let thread = ThreadModel(event: event, damus_state: state)
|
||||||
NavigationLink(value: Route.Thread(thread: thread)) {
|
let dest = ThreadView(state: state, thread: thread)
|
||||||
|
NavigationLink(destination: dest) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
GroupDescription(unique_pubkeys)
|
GroupDescription
|
||||||
EventBody(damus_state: state, event: event, size: .normal, options: [.truncate_content])
|
EventBody(damus_state: state, event: event, size: .normal, options: [.truncate_content])
|
||||||
.padding([.top], 1)
|
.padding([.top], 1)
|
||||||
.padding([.trailing])
|
.padding([.trailing])
|
||||||
@@ -251,7 +218,7 @@ struct EventGroupView: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
} else {
|
} else {
|
||||||
GroupDescription(unique_pubkeys)
|
GroupDescription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ struct NotificationItemView: View {
|
|||||||
EventGroupView(state: state, event: ev, group: .reaction(evgrp))
|
EventGroupView(state: state, event: ev, group: .reaction(evgrp))
|
||||||
|
|
||||||
case .reply(let ev):
|
case .reply(let ev):
|
||||||
NavigationLink(value: Route.Thread(thread: ThreadModel(event: ev, damus_state: state))) {
|
NavigationLink(destination: ThreadView(state: state, thread: ThreadModel(event: ev, damus_state: state))) {
|
||||||
EventView(damus: state, event: ev, options: options)
|
EventView(damus: state, event: ev, options: options)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ struct NotificationsView: View {
|
|||||||
|
|
||||||
var mystery: some View {
|
var mystery: some View {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
Text("Wake up, \(Profile.displayName(profile: state.profiles.lookup(id: state.pubkey), pubkey: state.pubkey).display_name.truncate(maxLength: 50))", comment: "Text telling the user to wake up, where the argument is their display name.")
|
Text("Wake up, \(Profile.displayName(profile: state.profiles.lookup(id: state.pubkey), pubkey: state.pubkey).display_name)", comment: "Text telling the user to wake up, where the argument is their display name.")
|
||||||
Text("You are dreaming...", comment: "Text telling the user that they are dreaming.")
|
Text("You are dreaming...", comment: "Text telling the user that they are dreaming.")
|
||||||
}
|
}
|
||||||
.id("what")
|
.id("what")
|
||||||
|
|||||||
@@ -11,12 +11,19 @@ struct ProfilePicturesView: View {
|
|||||||
let state: DamusState
|
let state: DamusState
|
||||||
let pubkeys: [String]
|
let pubkeys: [String]
|
||||||
|
|
||||||
|
@State var nav_target: String? = nil
|
||||||
|
@State var navigating: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
NavigationLink(destination: ProfileView(damus_state: state, pubkey: nav_target ?? ""), isActive: $navigating) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
HStack {
|
HStack {
|
||||||
ForEach(pubkeys.prefix(8), id: \.self) { pubkey in
|
ForEach(pubkeys.prefix(8), id: \.self) { pubkey in
|
||||||
ProfilePicView(pubkey: pubkey, size: 32.0, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: 32.0, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
nav_target = pubkey
|
||||||
|
navigating = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,21 @@ struct ParticipantsView: View {
|
|||||||
ForEach(originalReferences.pRefs) { participant in
|
ForEach(originalReferences.pRefs) { participant in
|
||||||
let pubkey = participant.id
|
let pubkey = participant.id
|
||||||
HStack {
|
HStack {
|
||||||
UserView(damus_state: damus_state, pubkey: pubkey)
|
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||||
|
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_nip5_domain: false)
|
||||||
|
if let about = profile?.about {
|
||||||
|
let blocks = parse_mentions(content: about, tags: [])
|
||||||
|
let about_string = render_blocks(blocks: blocks, profiles: damus_state.profiles).content.attributed
|
||||||
|
Text(about_string)
|
||||||
|
.lineLimit(3)
|
||||||
|
.font(.footnote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
Image("check-circle.fill")
|
Image("check-circle.fill")
|
||||||
.font(.system(size: 30))
|
.font(.system(size: 30))
|
||||||
|
|||||||
+27
-70
@@ -13,21 +13,16 @@ enum NostrPostResult {
|
|||||||
case cancel
|
case cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
let POST_PLACEHOLDER = NSLocalizedString("Type your note here...", comment: "Text box prompt to ask user to type their note.")
|
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
|
||||||
|
|
||||||
class TagModel: ObservableObject {
|
class TagModel: ObservableObject {
|
||||||
var diff = 0
|
var diff = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PostTarget {
|
|
||||||
case none
|
|
||||||
case user(String)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PostAction {
|
enum PostAction {
|
||||||
case replying_to(NostrEvent)
|
case replying_to(NostrEvent)
|
||||||
case quoting(NostrEvent)
|
case quoting(NostrEvent)
|
||||||
case posting(PostTarget)
|
case posting
|
||||||
|
|
||||||
var ev: NostrEvent? {
|
var ev: NostrEvent? {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -84,27 +79,11 @@ struct PostView: View {
|
|||||||
|
|
||||||
post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in
|
post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in
|
||||||
if let link = attributes[.link] as? String {
|
if let link = attributes[.link] as? String {
|
||||||
let normalized_link: String
|
post.replaceCharacters(in: range, with: link)
|
||||||
if link.hasPrefix("damus:nostr:") {
|
|
||||||
// Replace damus:nostr: URI prefix with nostr: since the former is for internal navigation and not meant to be posted.
|
|
||||||
normalized_link = String(link.dropFirst(6))
|
|
||||||
} else {
|
|
||||||
normalized_link = link
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add zero-width space in case text preceding the mention is not a whitespace.
|
|
||||||
// In the case where the character preceding the mention is a whitespace, the added zero-width space will be stripped out.
|
|
||||||
post.replaceCharacters(in: range, with: "\u{200B}\(normalized_link)\u{200B}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var content = self.post.string
|
var content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||||
// If two zero-width spaces are next to each other, normalize it to just one zero-width space.
|
|
||||||
.replacingOccurrences(of: "\u{200B}\u{200B}", with: "\u{200B}")
|
|
||||||
// If zero-width space is next to an actual whitespace, remove the zero-width space.
|
|
||||||
.replacingOccurrences(of: " \u{200B}", with: " ")
|
|
||||||
.replacingOccurrences(of: "\u{200B} ", with: " ")
|
|
||||||
.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
|
||||||
|
|
||||||
let imagesString = uploadedMedias.map { $0.uploadedURL.absoluteString }.joined(separator: " ")
|
let imagesString = uploadedMedias.map { $0.uploadedURL.absoluteString }.joined(separator: " ")
|
||||||
|
|
||||||
@@ -131,14 +110,6 @@ struct PostView: View {
|
|||||||
return post.string.allSatisfy { $0.isWhitespace } && uploadedMedias.isEmpty
|
return post.string.allSatisfy { $0.isWhitespace } && uploadedMedias.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
var uploading_disabled: Bool {
|
|
||||||
return image_upload.progress != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var posting_disabled: Bool {
|
|
||||||
return is_post_empty || uploading_disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
var ImageButton: some View {
|
var ImageButton: some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
attach_media = true
|
attach_media = true
|
||||||
@@ -162,7 +133,7 @@ struct PostView: View {
|
|||||||
ImageButton
|
ImageButton
|
||||||
CameraButton
|
CameraButton
|
||||||
}
|
}
|
||||||
.disabled(uploading_disabled)
|
.disabled(image_upload.progress != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var PostButton: some View {
|
var PostButton: some View {
|
||||||
@@ -173,29 +144,18 @@ struct PostView: View {
|
|||||||
self.send_post()
|
self.send_post()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disabled(posting_disabled)
|
.disabled(is_post_empty)
|
||||||
.font(.system(size: 14, weight: .bold))
|
.font(.system(size: 14, weight: .bold))
|
||||||
.frame(width: 80, height: 30)
|
.frame(width: 80, height: 30)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.background(LINEAR_GRADIENT)
|
.background(LINEAR_GRADIENT)
|
||||||
.opacity(posting_disabled ? 0.5 : 1.0)
|
.opacity(is_post_empty ? 0.5 : 1.0)
|
||||||
.clipShape(Capsule())
|
.clipShape(Capsule())
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEmpty() -> Bool {
|
var isEmpty: Bool {
|
||||||
return self.uploadedMedias.count == 0 &&
|
self.uploadedMedias.count == 0 &&
|
||||||
self.post.mutableString.trimmingCharacters(in: .whitespacesAndNewlines) ==
|
self.post.mutableString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||||
initialString().mutableString.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initialString() -> NSMutableAttributedString {
|
|
||||||
guard case .posting(let target) = action,
|
|
||||||
case .user(let pubkey) = target else {
|
|
||||||
return .init(string: "")
|
|
||||||
}
|
|
||||||
|
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
|
||||||
return user_tag_attr_string(profile: profile, pubkey: pubkey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func clear_draft() {
|
func clear_draft() {
|
||||||
@@ -210,17 +170,15 @@ struct PostView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func load_draft() -> Bool {
|
func load_draft() {
|
||||||
guard let draft = load_draft_for_post(drafts: self.damus_state.drafts, action: self.action) else {
|
guard let draft = load_draft_for_post(drafts: self.damus_state.drafts, action: self.action) else {
|
||||||
self.post = NSMutableAttributedString("")
|
self.post = NSMutableAttributedString("")
|
||||||
self.uploadedMedias = []
|
self.uploadedMedias = []
|
||||||
|
return
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.uploadedMedias = draft.media
|
self.uploadedMedias = draft.media
|
||||||
self.post = draft.content
|
self.post = draft.content
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func post_changed(post: NSMutableAttributedString, media: [UploadedMedia]) {
|
func post_changed(post: NSMutableAttributedString, media: [UploadedMedia]) {
|
||||||
@@ -249,13 +207,16 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var TextEntry: some View {
|
func TextEntry(scrollViewGeometry: GeometryProxy) -> some View {
|
||||||
|
GeometryReader { (geometry: GeometryProxy) in
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
TextViewWrapper(attributedText: $post, postTextViewCanScroll: $postTextViewCanScroll, cursorIndex: newCursorIndex, getFocusWordForMention: { word, range in
|
TextViewWrapper(attributedText: $post, postTextViewCanScroll: $postTextViewCanScroll, cursorIndex: newCursorIndex, getFocusWordForMention: { word, range in
|
||||||
focusWordAttributes = (word, range)
|
focusWordAttributes = (word, range)
|
||||||
self.newCursorIndex = nil
|
self.newCursorIndex = nil
|
||||||
})
|
})
|
||||||
.environmentObject(tagModel)
|
.environmentObject(tagModel)
|
||||||
|
.frame(maxHeight: scrollViewGeometry.size.height)
|
||||||
|
.position(x: geometry.frame(in: .local).midX, y: scrollViewGeometry.frame(in: .local).midY)
|
||||||
.focused($focus)
|
.focused($focus)
|
||||||
.textInputAutocapitalization(.sentences)
|
.textInputAutocapitalization(.sentences)
|
||||||
.onChange(of: post) { p in
|
.onChange(of: post) { p in
|
||||||
@@ -271,6 +232,7 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var TopBar: some View {
|
var TopBar: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@@ -339,12 +301,12 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Editor(deviceSize: GeometryProxy) -> some View {
|
func Editor(deviceSize: GeometryProxy, scrollViewGeometry: GeometryProxy) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||||
|
|
||||||
TextEntry
|
TextEntry(scrollViewGeometry: scrollViewGeometry)
|
||||||
}
|
}
|
||||||
.frame(height: deviceSize.size.height * multiply_factor)
|
.frame(height: deviceSize.size.height * multiply_factor)
|
||||||
.id("post")
|
.id("post")
|
||||||
@@ -361,11 +323,6 @@ struct PostView: View {
|
|||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fill_target_content(target: PostTarget) {
|
|
||||||
self.post = initialString()
|
|
||||||
self.tagModel.diff = post.string.count
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { (deviceSize: GeometryProxy) in
|
GeometryReader { (deviceSize: GeometryProxy) in
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
@@ -374,18 +331,20 @@ struct PostView: View {
|
|||||||
TopBar
|
TopBar
|
||||||
|
|
||||||
ScrollViewReader { scroller in
|
ScrollViewReader { scroller in
|
||||||
|
GeometryReader { (geometry: GeometryProxy) in
|
||||||
ScrollView {
|
ScrollView {
|
||||||
if case .replying_to(let replying_to) = self.action {
|
if case .replying_to(let replying_to) = self.action {
|
||||||
ReplyView(replying_to: replying_to, damus: damus_state, originalReferences: $originalReferences, references: $references)
|
ReplyView(replying_to: replying_to, damus: damus_state, originalReferences: $originalReferences, references: $references)
|
||||||
}
|
}
|
||||||
|
|
||||||
Editor(deviceSize: deviceSize)
|
Editor(deviceSize: deviceSize, scrollViewGeometry: geometry)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: searching == nil ? .infinity : 70)
|
.frame(maxHeight: searching == nil ? .infinity : 70)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
scroll_to_event(scroller: scroller, id: "post", delay: 1.0, animate: true, anchor: .top)
|
scroll_to_event(scroller: scroller, id: "post", delay: 1.0, animate: true, anchor: .top)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This if-block observes @ for tagging
|
// This if-block observes @ for tagging
|
||||||
if let searching {
|
if let searching {
|
||||||
@@ -435,7 +394,7 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
let loaded_draft = load_draft()
|
load_draft()
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .replying_to(let replying_to):
|
case .replying_to(let replying_to):
|
||||||
@@ -444,10 +403,8 @@ struct PostView: View {
|
|||||||
case .quoting(let quoting):
|
case .quoting(let quoting):
|
||||||
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
|
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
|
||||||
originalReferences = references
|
originalReferences = references
|
||||||
case .posting(let target):
|
case .posting:
|
||||||
guard !loaded_draft else { break }
|
break
|
||||||
|
|
||||||
fill_target_content(target: target)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
@@ -455,7 +412,7 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
if isEmpty() {
|
if isEmpty {
|
||||||
clear_draft()
|
clear_draft()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,7 +452,7 @@ func get_searching_string(_ word: String?) -> String? {
|
|||||||
|
|
||||||
struct PostView_Previews: PreviewProvider {
|
struct PostView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PostView(action: .posting(.none), damus_state: test_damus_state())
|
PostView(action: .posting, damus_state: test_damus_state())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ struct UserSearch: View {
|
|||||||
guard let pk = bech32_pubkey(user.pubkey) else {
|
guard let pk = bech32_pubkey(user.pubkey) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let tagAttributedString = user_tag_attr_string(profile: user.profile, pubkey: pk)
|
let tagAttributedString = createUserTag(for: user, with: pk)
|
||||||
appendUserTag(withTag: tagAttributedString)
|
appendUserTag(withTag: tagAttributedString)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +57,26 @@ struct UserSearch: View {
|
|||||||
newCursorIndex = wordRange.location + tagAttributedString.string.count
|
newCursorIndex = wordRange.location + tagAttributedString.string.count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func createUserTag(for user: SearchedUser, with pk: String) -> NSMutableAttributedString {
|
||||||
|
let name = Profile.displayName(profile: user.profile, pubkey: pk).username
|
||||||
|
let tagString = "@\(name)\u{200B} "
|
||||||
|
|
||||||
|
let tagAttributedString = NSMutableAttributedString(string: tagString,
|
||||||
|
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
||||||
|
NSAttributedString.Key.link: "@\(pk)"])
|
||||||
|
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||||
|
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||||
|
|
||||||
|
return tagAttributedString
|
||||||
|
}
|
||||||
|
|
||||||
|
private func appendUserTag(_ tagAttributedString: NSMutableAttributedString) {
|
||||||
|
let mutableString = NSMutableAttributedString()
|
||||||
|
mutableString.append(post)
|
||||||
|
mutableString.append(tagAttributedString)
|
||||||
|
post = mutableString
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Divider()
|
Divider()
|
||||||
@@ -148,18 +168,3 @@ func search_users_for_autocomplete(profiles: Profiles, tags: [[String]], search
|
|||||||
|
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
func user_tag_attr_string(profile: Profile?, pubkey: String) -> NSMutableAttributedString {
|
|
||||||
let display_name = Profile.displayName(profile: profile, pubkey: pubkey)
|
|
||||||
let name = display_name.username.truncate(maxLength: 50)
|
|
||||||
let tagString = "@\(name)\u{200B} "
|
|
||||||
|
|
||||||
let tagAttributedString = NSMutableAttributedString(string: tagString,
|
|
||||||
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
|
||||||
NSAttributedString.Key.link: "nostr:\(pubkey)"])
|
|
||||||
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
|
||||||
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
|
||||||
|
|
||||||
return tagAttributedString
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
//
|
|
||||||
// AboutView.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-06-18.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct AboutView: View {
|
|
||||||
let state: DamusState
|
|
||||||
let about: String
|
|
||||||
let max_about_length = 280
|
|
||||||
@State var show_full_about: Bool = false
|
|
||||||
@State private var about_string: AttributedString? = nil
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Group {
|
|
||||||
if let about_string {
|
|
||||||
let truncated_about = show_full_about ? about_string : about_string.truncateOrNil(maxLength: max_about_length)
|
|
||||||
SelectableText(attributedString: truncated_about ?? about_string, size: .subheadline)
|
|
||||||
|
|
||||||
if truncated_about != nil {
|
|
||||||
if show_full_about {
|
|
||||||
Button(NSLocalizedString("Show less", comment: "Button to show less of a long profile description.")) {
|
|
||||||
show_full_about = false
|
|
||||||
}
|
|
||||||
.font(.footnote)
|
|
||||||
} else {
|
|
||||||
Button(NSLocalizedString("Show more", comment: "Button to show more of a long profile description.")) {
|
|
||||||
show_full_about = true
|
|
||||||
}
|
|
||||||
.font(.footnote)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Text(verbatim: "")
|
|
||||||
.font(.subheadline)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
let blocks = parse_mentions(content: about, tags: [])
|
|
||||||
about_string = render_blocks(blocks: blocks, profiles: state.profiles).content.attributed
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
#Preview {
|
|
||||||
AboutView()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
//
|
|
||||||
// CondensedProfilePicturesView.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Terry Yiu on 6/19/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct CondensedProfilePicturesView: View {
|
|
||||||
let state: DamusState
|
|
||||||
let pubkeys: [String]
|
|
||||||
let maxPictures: Int
|
|
||||||
|
|
||||||
init(state: DamusState, pubkeys: [String], maxPictures: Int) {
|
|
||||||
self.state = state
|
|
||||||
self.pubkeys = pubkeys
|
|
||||||
self.maxPictures = min(maxPictures, pubkeys.count)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
// Using ZStack to make profile pictures floating and stacked on top of each other.
|
|
||||||
ZStack {
|
|
||||||
ForEach((0..<maxPictures).reversed(), id: \.self) { index in
|
|
||||||
ProfilePicView(pubkey: pubkeys[index], size: 32.0, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation)
|
|
||||||
.offset(x: CGFloat(index) * 20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Padding is needed so that other components drawn adjacent to this view don't get drawn on top.
|
|
||||||
.padding(.trailing, CGFloat((maxPictures - 1) * 20))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CondensedProfilePicturesView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
CondensedProfilePicturesView(state: test_damus_state(), pubkeys: ["a", "b", "c", "d"], maxPictures: 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -103,15 +103,13 @@ struct EditMetadataView: View {
|
|||||||
TopSection
|
TopSection
|
||||||
Form {
|
Form {
|
||||||
Section(NSLocalizedString("Your Name", comment: "Label for Your Name section of user profile form.")) {
|
Section(NSLocalizedString("Your Name", comment: "Label for Your Name section of user profile form.")) {
|
||||||
let display_name_placeholder = "Satoshi Nakamoto"
|
TextField("Satoshi Nakamoto", text: $display_name)
|
||||||
TextField(display_name_placeholder, text: $display_name)
|
|
||||||
.autocorrectionDisabled(true)
|
.autocorrectionDisabled(true)
|
||||||
.textInputAutocapitalization(.never)
|
.textInputAutocapitalization(.never)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(NSLocalizedString("Username", comment: "Label for Username section of user profile form.")) {
|
Section(NSLocalizedString("Username", comment: "Label for Username section of user profile form.")) {
|
||||||
let username_placeholder = "satoshi"
|
TextField("satoshi", text: $name)
|
||||||
TextField(username_placeholder, text: $name)
|
|
||||||
.autocorrectionDisabled(true)
|
.autocorrectionDisabled(true)
|
||||||
.textInputAutocapitalization(.never)
|
.textInputAutocapitalization(.never)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ struct MaybeAnonPfpView: View {
|
|||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
} else {
|
} else {
|
||||||
NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) {
|
NavigationLink(destination: ProfileView(damus_state: state, pubkey: pubkey)) {
|
||||||
ProfilePicView(pubkey: pubkey, size: size, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: size, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,6 @@ struct MaybeAnonPfpView: View {
|
|||||||
|
|
||||||
struct MaybeAnonPfpView_Previews: PreviewProvider {
|
struct MaybeAnonPfpView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: ANON_PUBKEY, size: PFP_SIZE)
|
MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: "anon", size: PFP_SIZE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ struct ProfileName: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var name_choice: String {
|
var name_choice: String {
|
||||||
return prefix == "@" ? current_display_name.username.truncate(maxLength: 50) : current_display_name.display_name.truncate(maxLength: 50)
|
return prefix == "@" ? current_display_name.username : current_display_name.display_name
|
||||||
}
|
}
|
||||||
|
|
||||||
var onlyzapper: Bool {
|
var onlyzapper: Bool {
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ struct InnerProfilePicView: View {
|
|||||||
var Placeholder: some View {
|
var Placeholder: some View {
|
||||||
Circle()
|
Circle()
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
|
||||||
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
|
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
|
||||||
.padding(2)
|
.padding(2)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,38 +46,13 @@ func relaysCountString(_ count: Int, locale: Locale = Locale.current) -> String
|
|||||||
return String(format: format, locale: locale, count)
|
return String(format: format, locale: locale, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func followedByString(_ friend_intersection: [String], profiles: Profiles, locale: Locale = Locale.current) -> String {
|
|
||||||
let bundle = bundleForLocale(locale: locale)
|
|
||||||
let names: [String] = friend_intersection.prefix(3).map {
|
|
||||||
let profile = profiles.lookup(id: $0)
|
|
||||||
return Profile.displayName(profile: profile, pubkey: $0).username.truncate(maxLength: 20)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch friend_intersection.count {
|
|
||||||
case 0:
|
|
||||||
return ""
|
|
||||||
case 1:
|
|
||||||
let format = NSLocalizedString("Followed by %@", bundle: bundle, comment: "Text to indicate that the user is followed by one of our follows.")
|
|
||||||
return String(format: format, locale: locale, names[0])
|
|
||||||
case 2:
|
|
||||||
let format = NSLocalizedString("Followed by %@ & %@", bundle: bundle, comment: "Text to indicate that the user is followed by two of our follows.")
|
|
||||||
return String(format: format, locale: locale, names[0], names[1])
|
|
||||||
case 3:
|
|
||||||
let format = NSLocalizedString("Followed by %@, %@ & %@", bundle: bundle, comment: "Text to indicate that the user is followed by three of our follows.")
|
|
||||||
return String(format: format, locale: locale, names[0], names[1], names[2])
|
|
||||||
default:
|
|
||||||
let format = localizedStringFormat(key: "followed_by_three_and_others", locale: locale)
|
|
||||||
return String(format: format, locale: locale, friend_intersection.count - 3, names[0], names[1], names[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EditButton: View {
|
struct EditButton: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationLink(value: Route.EditMetadata) {
|
NavigationLink(destination: EditMetadataView(damus_state: damus_state)) {
|
||||||
Text("Edit", comment: "Button to edit user's profile.")
|
Text("Edit", comment: "Button to edit user's profile.")
|
||||||
.frame(height: 30)
|
.frame(height: 30)
|
||||||
.padding(.horizontal,25)
|
.padding(.horizontal,25)
|
||||||
@@ -121,6 +96,7 @@ struct ProfileView: View {
|
|||||||
|
|
||||||
static let markdown = Markdown()
|
static let markdown = Markdown()
|
||||||
|
|
||||||
|
@State var showing_select_wallet: Bool = false
|
||||||
@State var is_zoomed: Bool = false
|
@State var is_zoomed: Bool = false
|
||||||
@State var show_share_sheet: Bool = false
|
@State var show_share_sheet: Bool = false
|
||||||
@State var show_qr_code: Bool = false
|
@State var show_qr_code: Bool = false
|
||||||
@@ -130,7 +106,6 @@ struct ProfileView: View {
|
|||||||
|
|
||||||
@StateObject var profile: ProfileModel
|
@StateObject var profile: ProfileModel
|
||||||
@StateObject var followers: FollowersModel
|
@StateObject var followers: FollowersModel
|
||||||
@StateObject var zap_button_model: ZapButtonModel = ZapButtonModel()
|
|
||||||
|
|
||||||
init(damus_state: DamusState, profile: ProfileModel, followers: FollowersModel) {
|
init(damus_state: DamusState, profile: ProfileModel, followers: FollowersModel) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
@@ -269,7 +244,11 @@ struct ProfileView: View {
|
|||||||
func lnButton(lnurl: String, profile: Profile) -> some View {
|
func lnButton(lnurl: String, profile: Profile) -> some View {
|
||||||
let button_img = profile.reactions == false ? "zap.fill" : "zap"
|
let button_img = profile.reactions == false ? "zap.fill" : "zap"
|
||||||
return Button(action: {
|
return Button(action: {
|
||||||
present_sheet(.zap(target: .profile(self.profile.pubkey), lnurl: lnurl))
|
if damus_state.settings.show_wallet_selector {
|
||||||
|
showing_select_wallet = true
|
||||||
|
} else {
|
||||||
|
open_with_wallet(wallet: damus_state.settings.default_wallet.model, invoice: lnurl)
|
||||||
|
}
|
||||||
}) {
|
}) {
|
||||||
Image(button_img)
|
Image(button_img)
|
||||||
.foregroundColor(button_img == "zap.fill" ? .orange : Color.primary)
|
.foregroundColor(button_img == "zap.fill" ? .orange : Color.primary)
|
||||||
@@ -296,11 +275,15 @@ struct ProfileView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.cornerRadius(24)
|
.cornerRadius(24)
|
||||||
|
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
|
||||||
|
SelectWalletView(default_wallet: damus_state.settings.default_wallet, showingSelectWallet: $showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: lnurl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var dmButton: some View {
|
var dmButton: some View {
|
||||||
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
|
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
|
||||||
return NavigationLink(value: Route.DMChat(dms: dm_model)) {
|
let dmview = DMChatView(damus_state: damus_state, dms: dm_model)
|
||||||
|
return NavigationLink(destination: dmview) {
|
||||||
Image("messages")
|
Image("messages")
|
||||||
.profile_button_style(scheme: colorScheme)
|
.profile_button_style(scheme: colorScheme)
|
||||||
}
|
}
|
||||||
@@ -324,7 +307,7 @@ struct ProfileView: View {
|
|||||||
follow_state: damus_state.contacts.follow_state(profile.pubkey)
|
follow_state: damus_state.contacts.follow_state(profile.pubkey)
|
||||||
)
|
)
|
||||||
} else if damus_state.keypair.privkey != nil {
|
} else if damus_state.keypair.privkey != nil {
|
||||||
NavigationLink(value: Route.EditMetadata) {
|
NavigationLink(destination: EditMetadataView(damus_state: damus_state)) {
|
||||||
EditButton(damus_state: damus_state)
|
EditButton(damus_state: damus_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,7 +375,12 @@ struct ProfileView: View {
|
|||||||
nameSection(profile_data: profile_data)
|
nameSection(profile_data: profile_data)
|
||||||
|
|
||||||
if let about = profile_data?.about {
|
if let about = profile_data?.about {
|
||||||
AboutView(state: damus_state, about: about)
|
let blocks = parse_mentions(content: about, tags: [])
|
||||||
|
let about_string = render_blocks(blocks: blocks, profiles: damus_state.profiles).content.attributed
|
||||||
|
SelectableText(attributedString: about_string, size: .subheadline)
|
||||||
|
} else {
|
||||||
|
Text(verbatim: "")
|
||||||
|
.font(.subheadline)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let url = profile_data?.website_url {
|
if let url = profile_data?.website_url {
|
||||||
@@ -403,7 +391,7 @@ struct ProfileView: View {
|
|||||||
if let contact = profile.contacts {
|
if let contact = profile.contacts {
|
||||||
let contacts = contact.referenced_pubkeys.map { $0.ref_id }
|
let contacts = contact.referenced_pubkeys.map { $0.ref_id }
|
||||||
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
|
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
|
||||||
NavigationLink(value: Route.Following(following: following_model)) {
|
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) {
|
||||||
HStack {
|
HStack {
|
||||||
let noun_text = Text(verbatim: "\(followingCountString(profile.following))").font(.subheadline).foregroundColor(.gray)
|
let noun_text = Text(verbatim: "\(followingCountString(profile.following))").font(.subheadline).foregroundColor(.gray)
|
||||||
Text("\(Text(verbatim: profile.following.formatted()).font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
|
Text("\(Text(verbatim: profile.following.formatted()).font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
|
||||||
@@ -411,9 +399,10 @@ struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
}
|
}
|
||||||
|
let fview = FollowersView(damus_state: damus_state, whos: profile.pubkey)
|
||||||
|
.environmentObject(followers)
|
||||||
if followers.contacts != nil {
|
if followers.contacts != nil {
|
||||||
NavigationLink(value: Route.Followers(followers: followers)) {
|
NavigationLink(destination: fview) {
|
||||||
followersCount
|
followersCount
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
@@ -431,40 +420,23 @@ struct ProfileView: View {
|
|||||||
let noun_text = Text(verbatim: relaysCountString(relays.keys.count)).font(.subheadline).foregroundColor(.gray)
|
let noun_text = Text(verbatim: relaysCountString(relays.keys.count)).font(.subheadline).foregroundColor(.gray)
|
||||||
let relay_text = Text("\(Text(verbatim: relays.keys.count.formatted()).font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
|
let relay_text = Text("\(Text(verbatim: relays.keys.count.formatted()).font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
|
||||||
if profile.pubkey == damus_state.pubkey && damus_state.is_privkey_user {
|
if profile.pubkey == damus_state.pubkey && damus_state.is_privkey_user {
|
||||||
NavigationLink(value: Route.RelayConfig) {
|
NavigationLink(destination: RelayConfigView(state: damus_state)) {
|
||||||
relay_text
|
relay_text
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
} else {
|
} else {
|
||||||
NavigationLink(value: Route.UserRelays(relays: Array(relays.keys).sorted())) {
|
NavigationLink(destination: UserRelaysView(state: damus_state, relays: Array(relays.keys).sorted())) {
|
||||||
relay_text
|
relay_text
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile.pubkey != damus_state.pubkey {
|
|
||||||
let friended_followers = damus_state.contacts.get_friended_followers(profile.pubkey)
|
|
||||||
if !friended_followers.isEmpty {
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
NavigationLink(value: Route.FollowersYouKnow(friendedFollowers: friended_followers, followers: followers)) {
|
|
||||||
HStack {
|
|
||||||
CondensedProfilePicturesView(state: damus_state, pubkeys: friended_followers, maxPictures: 3)
|
|
||||||
Text(followedByString(friended_followers, profiles: damus_state.profiles))
|
|
||||||
.font(.subheadline).foregroundColor(.gray)
|
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
|
||||||
ScrollView(.vertical) {
|
ScrollView(.vertical) {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
bannerSection
|
bannerSection
|
||||||
@@ -475,8 +447,8 @@ struct ProfileView: View {
|
|||||||
|
|
||||||
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 your notes (instead of notes and replies).").tag(FilterState.posts)
|
Text("Posts", comment: "Label for filter for seeing only your posts (instead of posts and replies).").tag(FilterState.posts)
|
||||||
Text("Notes & Replies", comment: "Label for filter for seeing your notes and replies (instead of only your notes).").tag(FilterState.posts_and_replies)
|
Text("Posts & Replies", comment: "Label for filter for seeing your posts and replies (instead of only your posts).").tag(FilterState.posts_and_replies)
|
||||||
})
|
})
|
||||||
Divider()
|
Divider()
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
@@ -520,13 +492,6 @@ struct ProfileView: View {
|
|||||||
.fullScreenCover(isPresented: $show_qr_code) {
|
.fullScreenCover(isPresented: $show_qr_code) {
|
||||||
QRCodeView(damus_state: damus_state, pubkey: profile.pubkey)
|
QRCodeView(damus_state: damus_state, pubkey: profile.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if damus_state.is_privkey_user {
|
|
||||||
PostButtonContainer(is_left_handed: damus_state.settings.left_handed) {
|
|
||||||
notify(.compose, PostAction.posting(.user(profile.pubkey)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+31
-202
@@ -8,54 +8,12 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CoreImage.CIFilterBuiltins
|
import CoreImage.CIFilterBuiltins
|
||||||
|
|
||||||
struct ProfileScanResult: Equatable {
|
|
||||||
let pubkey: String
|
|
||||||
|
|
||||||
init(hex: String) {
|
|
||||||
self.pubkey = hex
|
|
||||||
}
|
|
||||||
|
|
||||||
init?(string: String) {
|
|
||||||
var str = string
|
|
||||||
guard str.count != 0 else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if str.hasPrefix("nostr:") {
|
|
||||||
str.removeFirst("nostr:".count)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let _ = hex_decode(str), str.count == 64 {
|
|
||||||
self = .init(hex: str)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if str.starts(with: "npub"), let b32 = try? bech32_decode(str) {
|
|
||||||
let hex = hex_encode(b32.data)
|
|
||||||
self = .init(hex: hex)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct QRCodeView: View {
|
struct QRCodeView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
@State var pubkey: String
|
@State var pubkey: String
|
||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
@State private var selectedTab = 0
|
|
||||||
@State var scanResult: ProfileScanResult? = nil
|
|
||||||
@State var profile: Profile? = nil
|
|
||||||
@State var error: String? = nil
|
|
||||||
@State private var outerTrimEnd: CGFloat = 0
|
|
||||||
|
|
||||||
var animationDuration: Double = 0.5
|
|
||||||
|
|
||||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
|
||||||
|
|
||||||
var maybe_key: String? {
|
var maybe_key: String? {
|
||||||
guard let key = bech32_pubkey(pubkey) else {
|
guard let key = bech32_pubkey(pubkey) else {
|
||||||
return nil
|
return nil
|
||||||
@@ -64,80 +22,44 @@ struct QRCodeView: View {
|
|||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
var body: some View {
|
||||||
func navImage(systemImage: String) -> some View {
|
ZStack(alignment: .center) {
|
||||||
Image(systemName: systemImage)
|
|
||||||
.frame(width: 33, height: 33)
|
|
||||||
.background(Color.black.opacity(0.6))
|
|
||||||
.clipShape(Circle())
|
|
||||||
}
|
|
||||||
|
|
||||||
var navBackButton: some View {
|
ZStack(alignment: .topLeading) {
|
||||||
|
DamusGradient()
|
||||||
Button {
|
Button {
|
||||||
presentationMode.wrappedValue.dismiss()
|
presentationMode.wrappedValue.dismiss()
|
||||||
} label: {
|
} label: {
|
||||||
navImage(systemImage: "chevron.left")
|
Image("close")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.subheadline)
|
||||||
|
.padding(.leading, 20)
|
||||||
}
|
}
|
||||||
|
.zIndex(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var customNavbar: some View {
|
|
||||||
HStack {
|
|
||||||
navBackButton
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding(.top, 5)
|
|
||||||
.padding(.horizontal)
|
|
||||||
.accentColor(DamusColors.white)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationView {
|
|
||||||
ZStack(alignment: .center) {
|
|
||||||
ZStack(alignment: .topLeading) {
|
|
||||||
DamusGradient()
|
|
||||||
}
|
|
||||||
TabView(selection: $selectedTab) {
|
|
||||||
QRView
|
|
||||||
.tag(0)
|
|
||||||
if pubkey == damus_state.pubkey {
|
|
||||||
QRCameraView()
|
|
||||||
.tag(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
|
||||||
.onAppear {
|
|
||||||
UIScrollView.appearance().isScrollEnabled = false
|
|
||||||
}
|
|
||||||
.gesture(
|
|
||||||
DragGesture()
|
|
||||||
.onChanged { _ in }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("")
|
|
||||||
.navigationBarHidden(true)
|
|
||||||
.overlay(customNavbar, alignment: .top)
|
|
||||||
}
|
|
||||||
|
|
||||||
var QRView: some View {
|
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
|
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||||
|
|
||||||
if (damus_state.profiles.lookup(id: damus_state.pubkey)?.picture) != nil {
|
if (damus_state.profiles.lookup(id: pubkey)?.picture) != nil {
|
||||||
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 4.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||||
.padding(.top, 50)
|
.padding(.top, 50)
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "person.fill")
|
Image(systemName: "person.fill")
|
||||||
.font(.system(size: 60))
|
.font(.system(size: 60))
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
.padding(.top, 50)
|
.padding(.top, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let display_name = profile?.display_name {
|
if let display_name = profile?.display_name {
|
||||||
Text(display_name)
|
Text(display_name)
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
.font(.system(size: 24, weight: .heavy))
|
.font(.system(size: 24, weight: .heavy))
|
||||||
}
|
}
|
||||||
if let name = profile?.name {
|
if let name = profile?.name {
|
||||||
Text("@" + name)
|
Text("@" + name)
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,131 +70,39 @@ struct QRCodeView: View {
|
|||||||
.interpolation(.none)
|
.interpolation(.none)
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 300, height: 300)
|
.frame(width: 200, height: 200)
|
||||||
|
.padding()
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(RoundedRectangle(cornerRadius: 10)
|
.overlay(RoundedRectangle(cornerRadius: 10)
|
||||||
.stroke(DamusColors.white, lineWidth: 5.0))
|
.stroke(DamusColors.white, lineWidth: 1))
|
||||||
.shadow(radius: 10)
|
.shadow(radius: 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text("Follow me on Nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")
|
if (pubkey == damus_state.pubkey) {
|
||||||
|
Text("Follow me on nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
.font(.system(size: 24, weight: .heavy))
|
.font(.system(size: 24, weight: .heavy))
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
|
} else {
|
||||||
|
Text("Follow them on nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user (someone else).")
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
|
.font(.system(size: 24, weight: .heavy))
|
||||||
|
.padding(.top)
|
||||||
|
}
|
||||||
|
|
||||||
Text("Scan the code", comment: "Text on QR code view to prompt viewer to scan the QR code on screen with their device camera.")
|
Text("Scan the code", comment: "Text on QR code view to prompt viewer to scan the QR code on screen with their device camera.")
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
.font(.system(size: 18, weight: .ultraLight))
|
.font(.system(size: 18, weight: .ultraLight))
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
selectedTab = 1
|
|
||||||
}) {
|
|
||||||
HStack {
|
|
||||||
Text("Scan Code", comment: "Button to switch to scan QR Code page.")
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
}
|
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
|
||||||
}
|
|
||||||
.buttonStyle(GradientButtonStyle())
|
|
||||||
.padding(50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func QRCameraView() -> some View {
|
|
||||||
return VStack(alignment: .center) {
|
|
||||||
Text("Scan a user's pubkey", comment: "Text to prompt scanning a QR code of a user's pubkey to open their profile.")
|
|
||||||
.padding(.top, 50)
|
|
||||||
.font(.system(size: 24, weight: .heavy))
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
CodeScannerView(codeTypes: [.qr], scanMode: .continuous, simulatedData: "npub1k92qsr95jcumkpu6dffurkvwwycwa2euvx4fthv78ru7gqqz0nrs2ngfwd", shouldVibrateOnSuccess: false) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let success):
|
|
||||||
handleProfileScan(success.string)
|
|
||||||
case .failure(let failure):
|
|
||||||
self.error = failure.localizedDescription
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: 300, height: 300)
|
|
||||||
.cornerRadius(10)
|
|
||||||
.overlay(RoundedRectangle(cornerRadius: 10).stroke(DamusColors.white, lineWidth: 5.0))
|
|
||||||
.overlay(RoundedRectangle(cornerRadius: 10).trim(from: 0.0, to: outerTrimEnd).stroke(DamusColors.black, lineWidth: 5.5)
|
|
||||||
.rotationEffect(.degrees(-90)))
|
|
||||||
.shadow(radius: 10)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
selectedTab = 0
|
|
||||||
}) {
|
|
||||||
HStack {
|
|
||||||
Text("View QR Code", comment: "Button to switch to view users QR Code")
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
}
|
|
||||||
.frame( maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
|
||||||
}
|
|
||||||
.buttonStyle(GradientButtonStyle())
|
|
||||||
.padding(50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleProfileScan(_ scanned_str: String) {
|
|
||||||
guard let result = ProfileScanResult(string: scanned_str) else {
|
|
||||||
self.error = "Invalid profile QR"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.error = nil
|
|
||||||
|
|
||||||
guard result != self.scanResult else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
generator.impactOccurred()
|
|
||||||
cameraAnimate {
|
|
||||||
scanResult = result
|
|
||||||
|
|
||||||
find_event(state: damus_state, query: .profile(pubkey: result.pubkey)) { res in
|
|
||||||
guard let res else {
|
|
||||||
error = "Profile not found"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch res {
|
|
||||||
case .invalid_profile:
|
|
||||||
error = "Profile was found but was corrupt."
|
|
||||||
|
|
||||||
case .profile:
|
|
||||||
show_profile_after_delay()
|
|
||||||
|
|
||||||
case .event:
|
|
||||||
print("invalid search result")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
.modifier(SwipeToDismissModifier(minDistance: nil, onDismiss: {
|
||||||
}
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}))
|
||||||
func show_profile_after_delay() {
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration) {
|
|
||||||
if let scanResult {
|
|
||||||
damus_state.nav.push(route: Route.ProfileByKey(pubkey: scanResult.pubkey))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cameraAnimate(completion: @escaping () -> Void) {
|
|
||||||
outerTrimEnd = 0.0
|
|
||||||
withAnimation(.easeInOut(duration: animationDuration)) {
|
|
||||||
outerTrimEnd = 1.05 // Set to 1.05 instead of 1.0 since sometimes `completion()` runs before the value reaches 1.0. This ensures the animation is done.
|
|
||||||
}
|
|
||||||
completion()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateQRCode(pubkey: String) -> UIImage {
|
func generateQRCode(pubkey: String) -> UIImage {
|
||||||
@@ -300,4 +130,3 @@ struct QRCodeView_Previews: PreviewProvider {
|
|||||||
QRCodeView(damus_state: test_damus_state(), pubkey: test_event.pubkey)
|
QRCodeView(damus_state: test_damus_state(), pubkey: test_event.pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import SwiftUI
|
|||||||
struct RelayFilterView: View {
|
struct RelayFilterView: View {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
let timeline: Timeline
|
let timeline: Timeline
|
||||||
|
//@State var relays: [RelayDescriptor]
|
||||||
|
//@EnvironmentObject var user_settings: UserSettingsStore
|
||||||
|
//@State var relays: [RelayDescriptor]
|
||||||
|
|
||||||
init(state: DamusState, timeline: Timeline) {
|
init(state: DamusState, timeline: Timeline) {
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ struct RecommendedRelayView: View {
|
|||||||
Text(relay).layoutPriority(1)
|
Text(relay).layoutPriority(1)
|
||||||
|
|
||||||
if let meta = damus.relay_metadata.lookup(relay_id: relay) {
|
if let meta = damus.relay_metadata.lookup(relay_id: relay) {
|
||||||
NavigationLink(value: Route.RelayDetail(relay: relay, metadata: meta)){
|
NavigationLink ( destination:
|
||||||
|
RelayDetailView(state: damus, relay: relay, nip11: meta)
|
||||||
|
){
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
.opacity(0.0)
|
.opacity(0.0)
|
||||||
|
|||||||
@@ -72,18 +72,14 @@ struct RelayDetailView: View {
|
|||||||
|
|
||||||
if let pubkey = nip11.pubkey {
|
if let pubkey = nip11.pubkey {
|
||||||
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
|
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
|
||||||
NavigationLink(value: Route.ProfileByKey(pubkey: pubkey), label: {
|
|
||||||
UserViewRow(damus_state: state, pubkey: pubkey)
|
UserViewRow(damus_state: state, pubkey: pubkey)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let relay_connection {
|
|
||||||
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
|
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(relay)
|
Text(relay)
|
||||||
Spacer()
|
Spacer()
|
||||||
RelayStatusView(connection: relay_connection)
|
RelayStatus(pool: state.pool, relay: relay)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if nip11.is_paid {
|
if nip11.is_paid {
|
||||||
@@ -92,7 +88,7 @@ struct RelayDetailView: View {
|
|||||||
}, header: {
|
}, header: {
|
||||||
Text("Paid Relay", comment: "Section header that indicates the relay server requires payment.")
|
Text("Paid Relay", comment: "Section header that indicates the relay server requires payment.")
|
||||||
}, footer: {
|
}, footer: {
|
||||||
Text("This is a paid relay, you must pay for notes to be accepted.", comment: "Footer description that explains that the relay server requires payment to post.")
|
Text("This is a paid relay, you must pay for posts to be accepted.", comment: "Footer description that explains that the relay server requires payment to post.")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,10 +134,6 @@ struct RelayDetailView: View {
|
|||||||
}
|
}
|
||||||
return attrString
|
return attrString
|
||||||
}
|
}
|
||||||
|
|
||||||
private var relay_connection: RelayConnection? {
|
|
||||||
state.pool.get_relay(relay)?.connection
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RelayDetailView_Previews: PreviewProvider {
|
struct RelayDetailView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// RelayStatus.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct RelayStatus: View {
|
||||||
|
let pool: RelayPool
|
||||||
|
let relay: String
|
||||||
|
|
||||||
|
let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
|
||||||
|
|
||||||
|
@State var conn_color: Color = .gray
|
||||||
|
@State var conn_image: String = "network"
|
||||||
|
@State var connecting: Bool = false
|
||||||
|
|
||||||
|
func update_connection() {
|
||||||
|
for relay in pool.relays {
|
||||||
|
if relay.id == self.relay {
|
||||||
|
let c = relay.connection
|
||||||
|
if c.isConnected {
|
||||||
|
conn_image = "globe"
|
||||||
|
conn_color = .green
|
||||||
|
} else if c.isConnecting {
|
||||||
|
connecting = true
|
||||||
|
} else {
|
||||||
|
conn_image = "warning.fill"
|
||||||
|
conn_color = .red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
if connecting {
|
||||||
|
ProgressView()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing, 5)
|
||||||
|
} else {
|
||||||
|
Image(conn_image)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.foregroundColor(conn_color)
|
||||||
|
.padding(.trailing, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onReceive(timer) { _ in
|
||||||
|
update_connection()
|
||||||
|
}
|
||||||
|
.onAppear() {
|
||||||
|
update_connection()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RelayStatus_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
RelayStatus(pool: test_damus_state().pool, relay: "relay")
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user