Compare commits
154 Commits
localizati
...
localizabl
| Author | SHA1 | Date | |
|---|---|---|---|
|
a87ba73160
|
|||
|
|
4324b185fe | ||
|
|
1ab9b30b85 | ||
|
|
81cf6ad297 | ||
|
|
1b3be3a13b | ||
|
|
3a2ce04d6b | ||
|
|
981821a6bc | ||
|
|
98f83769bd | ||
|
|
7684f53281 | ||
|
|
15af686a58 | ||
|
|
aad8f9e8d4 | ||
| b2ee44c0ab | |||
|
|
a696ac5084 | ||
|
|
28237c3a63 | ||
|
|
1cae4640c0 | ||
|
|
21a07d54cb | ||
|
|
1efd07b852 | ||
|
|
e5eb7d44a2 | ||
|
|
ec9a89ee4d | ||
|
|
4741c2a3e8 | ||
|
|
0111c5e2dc | ||
|
|
bed4e00b53 | ||
|
|
bf14d7138a | ||
|
|
0c5da08a42 | ||
|
|
a6e123e928 | ||
|
|
69b1173e08 | ||
|
|
c3326213e9 | ||
|
325109d7b8
|
|||
|
|
f16d76605b
|
||
|
|
3eee1b205a
|
||
|
|
9545c6446d
|
||
|
|
40a75f65ab
|
||
|
|
98f42c9896
|
||
|
|
5c22989675
|
||
|
999f16f6a4
|
|||
|
|
3f5fd6eee8
|
||
|
|
7c195aa75c
|
||
|
|
2071efc129
|
||
|
|
9db2e9b464
|
||
|
|
5f6cb568ff
|
||
|
|
045399a065
|
||
|
|
1b526143d0
|
||
|
|
8a046c0d1b
|
||
|
|
2893e4234d
|
||
|
|
973a5ce2cb
|
||
|
|
1e81e90341
|
||
| 9e7943e0e9 | |||
|
|
bb7ac4fea5 | ||
|
|
05d0e15359 | ||
|
|
d4d17fcbad | ||
|
|
c21d29a897 | ||
|
|
6e117ac39c | ||
|
|
79407f17e8 | ||
|
|
72c19fc411 | ||
|
|
24c3e61a4b | ||
|
|
74d5bee1f6 | ||
|
|
8066fa1bf8 | ||
| 26df547605 | |||
| a97532b90d | |||
|
|
e8ba1ec806 | ||
|
|
e8c265a4d8 | ||
|
|
b33dc63fe4 | ||
|
|
c4852f1309 | ||
|
|
39a4be7076 | ||
|
|
50c7edc420 | ||
|
|
67fa3c1ce5 | ||
|
|
cd671da3e7 | ||
|
|
3b60ca04f1 | ||
|
|
e2e58499f5 | ||
|
5cadf09665
|
|||
|
|
1ca7b3462f
|
||
|
|
8a552d2b0f
|
||
|
|
9fa0f18f78
|
||
|
|
db672ca048
|
||
|
|
18ad73cd35
|
||
|
|
5719e9b37e
|
||
|
|
9fb2b3c0e5
|
||
|
|
5ec66feb06
|
||
|
|
ccc301cfcc
|
||
|
|
c1b9d0b55e
|
||
|
|
d9daa27016
|
||
|
|
fa3b5d57ed
|
||
|
|
7c3e598ca6
|
||
|
|
563d5c7881
|
||
|
|
b8cba0ee17
|
||
|
|
8556586af4
|
||
|
|
5fc52bb31b
|
||
|
|
a92c9f2c38
|
||
|
|
61e137696e
|
||
|
|
8fc3b124da
|
||
|
|
7852822295
|
||
|
|
85e55953b3
|
||
|
|
077f633f33
|
||
|
|
1c3d1598a3
|
||
|
|
314608627e
|
||
|
|
aeecc04b29
|
||
|
|
341389d438
|
||
|
|
fbeae64123
|
||
|
|
7a4af31859 | ||
|
|
e106be1412 | ||
|
|
282bf80daa | ||
|
|
bcb861a61b | ||
|
|
bb0ad18913 | ||
|
|
81830c7540 | ||
|
|
68128b5ff1 | ||
|
|
aebeb26bc6 | ||
|
|
79cf3db279 | ||
|
|
dcae0d2cc7 | ||
|
|
2b12dc5920 | ||
|
|
51930e7a12 | ||
|
|
b04e09d2e0 | ||
| b6c4213515 | |||
|
|
8230c6eded | ||
|
|
e79590f795 | ||
|
|
79bced1246 | ||
|
|
896f4b55e3 | ||
|
|
52e65f9429 | ||
|
|
a22cc532e2 | ||
|
|
823227920c | ||
|
|
3e2bbce25e | ||
|
|
e05b2d9ecf | ||
|
|
d7b31a1cd8 | ||
|
|
70f01c0880 | ||
|
|
2cf5f21f78 | ||
|
|
96e8f8b6b2 | ||
|
|
370cfd1b08 | ||
|
|
046af15734 | ||
|
|
9e4ab2d54c | ||
|
|
7cf12e2e0d | ||
|
|
a63a81b387 | ||
|
|
d994cd13dc | ||
|
|
95e985cfce | ||
|
|
3a69de9274 | ||
|
|
64f5acf98c | ||
|
|
5167ab264d | ||
|
|
e02895b29f | ||
|
|
0009d11025 | ||
|
|
afc317bb52 | ||
|
|
629212ea23 | ||
|
|
ec1252200f | ||
|
|
54ea1ab803 | ||
|
|
4cf8097de4 | ||
|
|
2c7384b0a9 | ||
|
|
19e312a8fb | ||
|
|
3986308638 | ||
| fa7740948b | |||
| 892a1420f3 | |||
| ee4cbf7363 | |||
| a1b1ce949b | |||
| 902e8c3950 | |||
| b776788b38 | |||
|
|
78066773f4 | ||
|
|
0bac284eee | ||
|
|
07c95d1003 |
52
.github/ISSUE_TEMPLATE/app_release.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: App release process
|
||||
about: Begin preparing for a new app release
|
||||
title: 'Release: '
|
||||
labels: release-tasks
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
A new version release. Please attempt to follow the release process steps below in the order they are shown.
|
||||
|
||||
## TestFlight release candidates
|
||||
|
||||
### Release candidate 1
|
||||
|
||||
**Version:** _[Enter full build information for the release candidate, including major and minor version number, build number, and commit hash]_
|
||||
|
||||
1. [ ] Merge in all needed changes to `master`
|
||||
2. [ ] Check CI, make sure it is passing
|
||||
3. [ ] Prepare preliminary changelog as a draft PR: _[Enter PR link to changelog here]_
|
||||
4. [ ] Make a _release_ build and submit to the internal TestFlight group via our new Release candidate workflow in Xcode Cloud.
|
||||
5. [ ] Prepare short screencast style video with main changes for the announcement
|
||||
6. [ ] Publish release build to these TestFlight groups:
|
||||
- [ ] Alpha testers group
|
||||
- [ ] Translators group
|
||||
- [ ] Purple group
|
||||
7. [ ] Publish announcement on Nostr
|
||||
|
||||
|
||||
_[Duplicate this release candidate section if there is more than one release candidate]_
|
||||
|
||||
|
||||
## App Store release
|
||||
|
||||
1. [ ] Release candidate checks:
|
||||
- [ ] Release candidate has been on Purple TestFlight for at least one week
|
||||
- [ ] No blocker issues came from feedback from Purple users (double-check)
|
||||
- [ ] Check with stakeholders
|
||||
- [ ] Check with developers & product for any release showstoppers (e.g., critical newfound bugs)
|
||||
2. [ ] Thorough check on release notes
|
||||
3. [ ] Submit to App Store review (with manual publishing setting enabled)
|
||||
4. [ ] Get App Store approval from Apple
|
||||
5. [ ] Prepare announcement
|
||||
7. [ ] Publish on the App Store and make announcement
|
||||
8. [ ] Publish changelog and tag commit hash corresponding to the release
|
||||
9. [ ] Perform a version bump on the repository, in preparation for the next release
|
||||
|
||||
|
||||
## Notes/others
|
||||
|
||||
_Enter any relevant notes here_
|
||||
|
||||
5
ACKNOWLEDGEMENTS.md
Normal file
@@ -0,0 +1,5 @@
|
||||
### Acknowledgements and licenses
|
||||
|
||||
1. This product contains code derived from [Nostr SDK iOS](https://github.com/nostr-sdk/nostr-sdk-ios). [License](https://github.com/nostr-sdk/nostr-sdk-ios/blob/40df800c6749d7ce0b6fd7328e76cbc0dc71c87b/LICENSE)
|
||||
2. This product includes software developed by the "Marcin Krzyzanowski" (http://krzyzanowskim.com/). [License](https://github.com/krzyzanowskim/CryptoSwift/blob/e74bbbfbef939224b242ae7c342a90e60b88b5ce/LICENSE)
|
||||
|
||||
53
CHANGELOG.md
@@ -1,3 +1,56 @@
|
||||
## [1.12.3] - 2025-02-06
|
||||
|
||||
### Added
|
||||
|
||||
- Purple members who have been active for more than a year now get a special badge (Daniel D’Aquino)
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved clarity of the mute button to indicate it can be used for blocking a user (Daniel D’Aquino)
|
||||
- Made the microphone access request message more clear to users (Daniel D’Aquino)
|
||||
|
||||
[v1.12.3]: https://github.com/damus-io/damus/releases/tag/v1.12.3
|
||||
|
||||
|
||||
## [1.12](https://github.com/damus-io/damus/releases/tag/v1.12) - 2024-12-20
|
||||
|
||||
### Added
|
||||
|
||||
- Render Gif and video files while composing posts (Swift Coder)
|
||||
- Add profile info text in stretchable banner with follow button (Swift Coder)
|
||||
- Paste Gif image similar to jpeg and png files (Swift Coder)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved UX around the label for searching words (Daniel D’Aquino)
|
||||
- Improved accessibility support on some elements (Daniel D’Aquino)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where the "next" button would appear hidden and hard to click on the create account view (Daniel D’Aquino)
|
||||
- Fix non scrollable wallet screen (Swift Coder)
|
||||
- Fixed suggested users category titles to be localizable (Terry Yiu)
|
||||
- Fixed GradientFollowButton to have consistent width and autoscale text limited to 1 line (Terry Yiu)
|
||||
- Fixed right-to-left localization issues (Terry Yiu)
|
||||
- Fixed AddMuteItemView to trim leading and trailing whitespaces from mute text and disallow adding text with only whitespaces (Terry Yiu)
|
||||
- Fixed SideMenuView text to autoscale and limit to 1 line (Terry Yiu)
|
||||
- Fixed an issue where a profile would need to be input twice in the search to be found (Daniel D’Aquino)
|
||||
- Fixed non-breaking spaces in localized strings (Terry Yiu)
|
||||
- Fixed localization issue on Add mute item button (Terry Yiu)
|
||||
- Replace non-breaking spaces with regular spaces as Apple's NSLocalizedString macro does not seem to work with it (Terry Yiu)
|
||||
- Fixed localization issues in RelayConfigView (Terry Yiu)
|
||||
- Fix duplicate uploads (Swift Coder)
|
||||
- Remove duplicate pubkey from Follow Suggestion list (Swift Coder)
|
||||
- Fix Page control indicator (Swift Coder)
|
||||
- Fix damus sharing issues (Swift Coder)
|
||||
- Fixed issue where banner edit button is unclickable (Daniel D’Aquino)
|
||||
- Handle empty notification pages by displaying suitable text (Swift Coder)
|
||||
|
||||
[v1.12](https://github.com/damus-io/damus/releases/tag/v1.12): [https://github.com/damus-io/damus/releases/tag/v1.12]
|
||||
|
||||
|
||||
## [v1.11(10)](https://github.com/damus-io/damus/releases/tag/v1.11-10) - 2024-11-18
|
||||
|
||||
### Added
|
||||
|
||||
@@ -8,7 +8,7 @@ A twitter-like [nostr][nostr] client for iPhone, iPad and MacOS.
|
||||
|
||||
[nostr]: https://github.com/fiatjaf/nostr
|
||||
|
||||
## How is Damus better than twitter?
|
||||
## How is Damus better than X/Twitter?
|
||||
There are no toxic algorithms.\
|
||||
You can send or receive zaps (satoshis) without asking for permission.\
|
||||
[There is no central database](https://fiatjaf.com/nostr.html). Therefore, Damus is censorship resistant.\
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */; };
|
||||
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; };
|
||||
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
|
||||
3A96E3FE2D6BCE3800AE1630 /* RepostedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A96E3FD2D6BCE3800AE1630 /* RepostedTests.swift */; };
|
||||
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
|
||||
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
|
||||
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA59D1C2999B0400061C48E /* DraftsModel.swift */; };
|
||||
@@ -406,9 +407,10 @@
|
||||
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; };
|
||||
5C6E1DAF2A194075008FC15A /* PinkGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAE2A194075008FC15A /* PinkGradient.swift */; };
|
||||
5C7389B12B6EFA7100781E0A /* ProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B02B6EFA7100781E0A /* ProxyView.swift */; };
|
||||
5C7389B72B9E692E00781E0A /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; };
|
||||
5C7389B92B9E69ED00781E0A /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; };
|
||||
5C8711DE2C460C06007879C2 /* PostingTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C8711DD2C460C06007879C2 /* PostingTimelineView.swift */; };
|
||||
5CB017212D2D985E00A9ED05 /* CoinosButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB017202D2D985800A9ED05 /* CoinosButton.swift */; };
|
||||
5CB017222D2D985E00A9ED05 /* CoinosButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB017202D2D985800A9ED05 /* CoinosButton.swift */; };
|
||||
5CB017232D2D985E00A9ED05 /* CoinosButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB017202D2D985800A9ED05 /* CoinosButton.swift */; };
|
||||
5CC8529D2BD741CD0039FFC5 /* HighlightEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */; };
|
||||
5CC8529F2BD744F60039FFC5 /* HighlightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529E2BD744F60039FFC5 /* HighlightView.swift */; };
|
||||
5CC852A22BDED9B90039FFC5 /* HighlightDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */; };
|
||||
@@ -535,7 +537,6 @@
|
||||
82D6FB0F2CD99F7900C925F4 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
|
||||
82D6FB102CD99F7900C925F4 /* DamusBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C687C202A5F7ED00092C550 /* DamusBackground.swift */; };
|
||||
82D6FB112CD99F7900C925F4 /* DamusLightGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */; };
|
||||
82D6FB122CD99F7900C925F4 /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; };
|
||||
82D6FB132CD99F7900C925F4 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
|
||||
82D6FB142CD99F7900C925F4 /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
||||
82D6FB152CD99F7900C925F4 /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; };
|
||||
@@ -733,7 +734,6 @@
|
||||
82D6FBD82CD99F7900C925F4 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; };
|
||||
82D6FBD92CD99F7900C925F4 /* GradientFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694F32A6732B7001F4053 /* GradientFollowButton.swift */; };
|
||||
82D6FBDA2CD99F7900C925F4 /* AlbyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09652A0AE62100943473 /* AlbyButton.swift */; };
|
||||
82D6FBDB2CD99F7900C925F4 /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; };
|
||||
82D6FBDC2CD99F7900C925F4 /* DamusVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2929DDF54400516EAC /* DamusVideoPlayerView.swift */; };
|
||||
82D6FBDD2CD99F7900C925F4 /* DamusVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayer.swift */; };
|
||||
82D6FBDE2CD99F7900C925F4 /* DamusVideoCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFE2AA76A0900DFEC1F /* DamusVideoCoordinator.swift */; };
|
||||
@@ -876,7 +876,6 @@
|
||||
82D6FC682CD99F7900C925F4 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA128296A7E006E126D /* SearchView.swift */; };
|
||||
82D6FC692CD99F7900C925F4 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||
82D6FC6A2CD99F7900C925F4 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3AC7A02835A81400E1F516 /* SetupView.swift */; };
|
||||
82D6FC6B2CD99F7900C925F4 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
|
||||
82D6FC6C2CD99F7900C925F4 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
||||
82D6FC6D2CD99F7900C925F4 /* UserRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */; };
|
||||
82D6FC6E2CD99F7900C925F4 /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
||||
@@ -1047,6 +1046,12 @@
|
||||
D703D7B62C67118200A400EA /* String+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9472A9AD44700DC3548 /* String+extension.swift */; };
|
||||
D703D7B72C67118F00A400EA /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
|
||||
D703D7B82C6711A000A400EA /* NativeObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9462A9AD44700DC3548 /* NativeObject.swift */; };
|
||||
D706C5AF2D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D706C5AE2D5D31B20027C627 /* AutoSaveIndicatorView.swift */; };
|
||||
D706C5B02D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D706C5AE2D5D31B20027C627 /* AutoSaveIndicatorView.swift */; };
|
||||
D706C5B12D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D706C5AE2D5D31B20027C627 /* AutoSaveIndicatorView.swift */; };
|
||||
D706C5B72D602A110027C627 /* QueueableNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D706C5B62D602A050027C627 /* QueueableNotify.swift */; };
|
||||
D706C5B82D602A110027C627 /* QueueableNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D706C5B62D602A050027C627 /* QueueableNotify.swift */; };
|
||||
D706C5B92D602A110027C627 /* QueueableNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D706C5B62D602A050027C627 /* QueueableNotify.swift */; };
|
||||
D70A3B172B02DCE5008BD568 /* NotificationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */; };
|
||||
D70D90982CDED61800CD0534 /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = D70D90972CDED61800CD0534 /* CodeScanner */; };
|
||||
D70D909C2CDED7B200CD0534 /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = D70D909B2CDED7B200CD0534 /* CodeScanner */; };
|
||||
@@ -1127,7 +1132,6 @@
|
||||
D73E5E442C6A97F4007EB227 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
|
||||
D73E5E452C6A97F4007EB227 /* DamusBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C687C202A5F7ED00092C550 /* DamusBackground.swift */; };
|
||||
D73E5E462C6A97F4007EB227 /* DamusLightGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */; };
|
||||
D73E5E472C6A97F4007EB227 /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; };
|
||||
D73E5E482C6A97F4007EB227 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
|
||||
D73E5E492C6A97F4007EB227 /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
||||
D73E5E4D2C6A97F4007EB227 /* NIP05Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838A296F6E1E00DC99E7 /* NIP05Badge.swift */; };
|
||||
@@ -1255,7 +1259,6 @@
|
||||
D73E5ED42C6A97F4007EB227 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; };
|
||||
D73E5ED52C6A97F4007EB227 /* GradientFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694F32A6732B7001F4053 /* GradientFollowButton.swift */; };
|
||||
D73E5ED62C6A97F4007EB227 /* AlbyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09652A0AE62100943473 /* AlbyButton.swift */; };
|
||||
D73E5ED72C6A97F4007EB227 /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; };
|
||||
D73E5ED82C6A97F4007EB227 /* DamusVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2929DDF54400516EAC /* DamusVideoPlayerView.swift */; };
|
||||
D73E5ED92C6A97F4007EB227 /* DamusVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayer.swift */; };
|
||||
D73E5EDA2C6A97F4007EB227 /* DamusVideoCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFE2AA76A0900DFEC1F /* DamusVideoCoordinator.swift */; };
|
||||
@@ -1391,7 +1394,6 @@
|
||||
D73E5F602C6A97F5007EB227 /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */; };
|
||||
D73E5F612C6A97F5007EB227 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA128296A7E006E126D /* SearchView.swift */; };
|
||||
D73E5F622C6A97F5007EB227 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||
D73E5F642C6A97F5007EB227 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
|
||||
D73E5F652C6A97F5007EB227 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
||||
D73E5F662C6A97F5007EB227 /* UserRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */; };
|
||||
D73E5F682C6A97F5007EB227 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
|
||||
@@ -1448,10 +1450,22 @@
|
||||
D74AAFD22B155E78006CF0F4 /* WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09612A098D0E00943473 /* WalletConnect.swift */; };
|
||||
D74AAFD42B155ECB006CF0F4 /* Zaps+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD32B155ECB006CF0F4 /* Zaps+.swift */; };
|
||||
D74AAFD62B155F0C006CF0F4 /* WalletConnect+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */; };
|
||||
D74EA08A2D2BF2A7002290DD /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D767066E2C8BB3CE00F09726 /* URLHandler.swift */; };
|
||||
D74EA08E2D2E271E002290DD /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA08D2D2E271E002290DD /* ErrorView.swift */; };
|
||||
D74EA08F2D2E271E002290DD /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA08D2D2E271E002290DD /* ErrorView.swift */; };
|
||||
D74EA0902D2E271E002290DD /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA08D2D2E271E002290DD /* ErrorView.swift */; };
|
||||
D74EA0912D2E3464002290DD /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D767066E2C8BB3CE00F09726 /* URLHandler.swift */; };
|
||||
D74EA0932D2E77B9002290DD /* LoadableNostrEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA0922D2E77B9002290DD /* LoadableNostrEventView.swift */; };
|
||||
D74EA0942D2E77B9002290DD /* LoadableNostrEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA0922D2E77B9002290DD /* LoadableNostrEventView.swift */; };
|
||||
D74EA0952D2E77B9002290DD /* LoadableNostrEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA0922D2E77B9002290DD /* LoadableNostrEventView.swift */; };
|
||||
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F43092B23F0BE00425B75 /* DamusPurple.swift */; };
|
||||
D74F430C2B23FB9B00425B75 /* StoreObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F430B2B23FB9B00425B75 /* StoreObserver.swift */; };
|
||||
D753CEAA2BE9DE04001C3A5D /* MutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D753CEA92BE9DE04001C3A5D /* MutingTests.swift */; };
|
||||
D755B28D2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */ = {isa = PBXBuildFile; fileRef = D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */; };
|
||||
D755B28E2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */ = {isa = PBXBuildFile; fileRef = D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */; };
|
||||
D755B28F2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */ = {isa = PBXBuildFile; fileRef = D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */; };
|
||||
D76556D62B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */; };
|
||||
D767066F2C8BB3CF00F09726 /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D767066E2C8BB3CE00F09726 /* URLHandler.swift */; };
|
||||
D76874F32AE3632B00FB0F68 /* ProfileZapLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */; };
|
||||
D773BC5F2C6D538500349F0A /* CommentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D773BC5E2C6D538500349F0A /* CommentItem.swift */; };
|
||||
D773BC602C6D538500349F0A /* CommentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D773BC5E2C6D538500349F0A /* CommentItem.swift */; };
|
||||
@@ -1485,6 +1499,7 @@
|
||||
D798D22E2B086E4800234419 /* NostrResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFB028049D510006080F /* NostrResponse.swift */; };
|
||||
D79C4C172AFEB061003A41B4 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79C4C162AFEB061003A41B4 /* NotificationService.swift */; };
|
||||
D79C4C1B2AFEB061003A41B4 /* DamusNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D79C4C142AFEB061003A41B4 /* DamusNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
D7A0D8752D1FE67900DCBE59 /* EditPictureControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A0D8742D1FE66A00DCBE59 /* EditPictureControlTests.swift */; };
|
||||
D7A343EE2AD0D77C00CED48B /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343ED2AD0D77C00CED48B /* InlineSnapshotTesting */; };
|
||||
D7A343F02AD0D77C00CED48B /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343EF2AD0D77C00CED48B /* SnapshotTesting */; };
|
||||
D7ADD3DE2B53854300F104C4 /* DamusPurpleURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3DD2B53854300F104C4 /* DamusPurpleURL.swift */; };
|
||||
@@ -1492,6 +1507,10 @@
|
||||
D7ADD3E22B538E3500F104C4 /* DamusPurpleVerifyNpubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3E12B538E3500F104C4 /* DamusPurpleVerifyNpubView.swift */; };
|
||||
D7B76C902C825042003A16CB /* PushNotificationClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */; };
|
||||
D7B76C912C82507F003A16CB /* NIP98AuthenticatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */; };
|
||||
D7BEE6F92D37B37400CF659F /* DraftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7BEE6F82D37B37400CF659F /* DraftTests.swift */; };
|
||||
D7C48C0B2D12DE0C00A3BACF /* SwiftyCrop in Frameworks */ = {isa = PBXBuildFile; productRef = D7C48C0A2D12DE0C00A3BACF /* SwiftyCrop */; };
|
||||
D7C48C0D2D12E34900A3BACF /* SwiftyCrop in Frameworks */ = {isa = PBXBuildFile; productRef = D7C48C0C2D12E34900A3BACF /* SwiftyCrop */; };
|
||||
D7C48C0F2D12E35600A3BACF /* SwiftyCrop in Frameworks */ = {isa = PBXBuildFile; productRef = D7C48C0E2D12E35600A3BACF /* SwiftyCrop */; };
|
||||
D7C6787E2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */; };
|
||||
D7C9701E2C890FC500C56602 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685D297633BC00C46468 /* Localizable.strings */; };
|
||||
D7C9701F2C890FEB00C56602 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
|
||||
@@ -1592,6 +1611,19 @@
|
||||
D7D2A3812BF815D000E4B42B /* PushNotificationClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */; };
|
||||
D7D68FF92C9E01BE0015A515 /* KFClickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D68FF82C9E01B60015A515 /* KFClickable.swift */; };
|
||||
D7D68FFA2C9E01BE0015A515 /* KFClickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D68FF82C9E01B60015A515 /* KFClickable.swift */; };
|
||||
D7DB1FDE2D5A78CE00CF06DA /* NIP44.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DB1FDD2D5A78CE00CF06DA /* NIP44.swift */; };
|
||||
D7DB1FDF2D5A78CE00CF06DA /* NIP44.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DB1FDD2D5A78CE00CF06DA /* NIP44.swift */; };
|
||||
D7DB1FE02D5A78CE00CF06DA /* NIP44.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DB1FDD2D5A78CE00CF06DA /* NIP44.swift */; };
|
||||
D7DB1FE42D5A9AC900CF06DA /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = D7DB1FE32D5A9AC900CF06DA /* CryptoSwift */; };
|
||||
D7DB1FE82D5A9F5300CF06DA /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = D7DB1FE72D5A9F5300CF06DA /* CryptoSwift */; };
|
||||
D7DB1FEA2D5A9F5A00CF06DA /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = D7DB1FE92D5A9F5A00CF06DA /* CryptoSwift */; };
|
||||
D7DB1FEC2D5A9F6500CF06DA /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = D7DB1FEB2D5A9F6500CF06DA /* CryptoSwift */; };
|
||||
D7DB1FEE2D5AC51B00CF06DA /* NIP44v2EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DB1FED2D5AC50F00CF06DA /* NIP44v2EncryptionTests.swift */; };
|
||||
D7DB1FF12D5AC5D700CF06DA /* nip44.vectors.json in Resources */ = {isa = PBXBuildFile; fileRef = D7DB1FF02D5AC5D700CF06DA /* nip44.vectors.json */; };
|
||||
D7DB1FF32D5AC5EA00CF06DA /* LICENSES in Resources */ = {isa = PBXBuildFile; fileRef = D7DB1FF22D5AC5E400CF06DA /* LICENSES */; };
|
||||
D7DB93052D66A44100DA1EE5 /* Undistractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */; };
|
||||
D7DB93062D66A44100DA1EE5 /* Undistractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */; };
|
||||
D7DB93072D66A44100DA1EE5 /* Undistractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */; };
|
||||
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
||||
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
||||
D7EB00B02CD59C8D00660C07 /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; };
|
||||
@@ -1638,7 +1670,6 @@
|
||||
E0EE9DD42B8E5FEA00F3002D /* ImageProcessing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0EE9DD32B8E5FEA00F3002D /* ImageProcessing.swift */; };
|
||||
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; };
|
||||
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
|
||||
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
|
||||
F71694EA2A662232001F4053 /* OnboardingSuggestionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694E92A662232001F4053 /* OnboardingSuggestionsView.swift */; };
|
||||
F71694EC2A662292001F4053 /* SuggestedUsersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694EB2A662292001F4053 /* SuggestedUsersViewModel.swift */; };
|
||||
F71694EE2A6624F9001F4053 /* suggested_users.json in Resources */ = {isa = PBXBuildFile; fileRef = F71694ED2A6624F9001F4053 /* suggested_users.json */; };
|
||||
@@ -1775,6 +1806,7 @@
|
||||
3A96D41A298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
3A96D41B298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3A96D41C298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
3A96E3FD2D6BCE3800AE1630 /* RepostedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostedTests.swift; sourceTree = "<group>"; };
|
||||
3A994C4C2BE5B9370019F632 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = th; path = th.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
3A994C4D2BE5B9370019F632 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
3A994C4E2BE5B9370019F632 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@@ -2335,9 +2367,8 @@
|
||||
5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButtonStyle.swift; sourceTree = "<group>"; };
|
||||
5C6E1DAE2A194075008FC15A /* PinkGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinkGradient.swift; sourceTree = "<group>"; };
|
||||
5C7389B02B6EFA7100781E0A /* ProxyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyView.swift; sourceTree = "<group>"; };
|
||||
5C7389B62B9E692E00781E0A /* MutinyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyButton.swift; sourceTree = "<group>"; };
|
||||
5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyGradient.swift; sourceTree = "<group>"; };
|
||||
5C8711DD2C460C06007879C2 /* PostingTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostingTimelineView.swift; sourceTree = "<group>"; };
|
||||
5CB017202D2D985800A9ED05 /* CoinosButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinosButton.swift; sourceTree = "<group>"; };
|
||||
5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightEvent.swift; sourceTree = "<group>"; };
|
||||
5CC8529E2BD744F60039FFC5 /* HighlightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightView.swift; sourceTree = "<group>"; };
|
||||
5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDescription.swift; sourceTree = "<group>"; };
|
||||
@@ -2393,6 +2424,8 @@
|
||||
D703D7222C66E47100A400EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D703D7262C66E47100A400EA /* highlighter action extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "highlighter action extension.entitlements"; sourceTree = "<group>"; };
|
||||
D703D72A2C66F29500A400EA /* getSelection.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = getSelection.js; sourceTree = "<group>"; };
|
||||
D706C5AE2D5D31B20027C627 /* AutoSaveIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoSaveIndicatorView.swift; sourceTree = "<group>"; };
|
||||
D706C5B62D602A050027C627 /* QueueableNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueableNotify.swift; sourceTree = "<group>"; };
|
||||
D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFormatter.swift; sourceTree = "<group>"; };
|
||||
D7100C552B76F8E600C59298 /* PurpleViewPrimitives.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleViewPrimitives.swift; sourceTree = "<group>"; };
|
||||
D7100C572B76FC8400C59298 /* MarketingContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketingContentView.swift; sourceTree = "<group>"; };
|
||||
@@ -2425,10 +2458,14 @@
|
||||
D74AAFCE2B155D8C006CF0F4 /* ZapDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapDataModel.swift; sourceTree = "<group>"; };
|
||||
D74AAFD32B155ECB006CF0F4 /* Zaps+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Zaps+.swift"; sourceTree = "<group>"; };
|
||||
D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WalletConnect+.swift"; sourceTree = "<group>"; };
|
||||
D74EA08D2D2E271E002290DD /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = "<group>"; };
|
||||
D74EA0922D2E77B9002290DD /* LoadableNostrEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableNostrEventView.swift; sourceTree = "<group>"; };
|
||||
D74F43092B23F0BE00425B75 /* DamusPurple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurple.swift; sourceTree = "<group>"; };
|
||||
D74F430B2B23FB9B00425B75 /* StoreObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreObserver.swift; sourceTree = "<group>"; };
|
||||
D753CEA92BE9DE04001C3A5D /* MutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutingTests.swift; sourceTree = "<group>"; };
|
||||
D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP37Draft.swift; sourceTree = "<group>"; };
|
||||
D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleWelcomeView.swift; sourceTree = "<group>"; };
|
||||
D767066E2C8BB3CE00F09726 /* URLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLHandler.swift; sourceTree = "<group>"; };
|
||||
D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZapLinkView.swift; sourceTree = "<group>"; };
|
||||
D773BC5E2C6D538500349F0A /* CommentItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentItem.swift; sourceTree = "<group>"; };
|
||||
D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileActionSheetView.swift; sourceTree = "<group>"; };
|
||||
@@ -2446,9 +2483,11 @@
|
||||
D79C4C162AFEB061003A41B4 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
D79C4C182AFEB061003A41B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D79C4C1C2AFEB061003A41B4 /* DamusNotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DamusNotificationService.entitlements; sourceTree = "<group>"; };
|
||||
D7A0D8742D1FE66A00DCBE59 /* EditPictureControlTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPictureControlTests.swift; sourceTree = "<group>"; };
|
||||
D7ADD3DD2B53854300F104C4 /* DamusPurpleURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleURL.swift; sourceTree = "<group>"; };
|
||||
D7ADD3DF2B538D4200F104C4 /* DamusPurpleURLSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleURLSheetView.swift; sourceTree = "<group>"; };
|
||||
D7ADD3E12B538E3500F104C4 /* DamusPurpleVerifyNpubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleVerifyNpubView.swift; sourceTree = "<group>"; };
|
||||
D7BEE6F82D37B37400CF659F /* DraftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftTests.swift; sourceTree = "<group>"; };
|
||||
D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP98AuthenticatedRequest.swift; sourceTree = "<group>"; };
|
||||
D7CB5D3D2B116DAD00AD4105 /* NotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsManager.swift; sourceTree = "<group>"; };
|
||||
D7CB5D442B116FE800AD4105 /* Contacts+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Contacts+.swift"; sourceTree = "<group>"; };
|
||||
@@ -2461,6 +2500,11 @@
|
||||
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleImpendingExpirationTests.swift; sourceTree = "<group>"; };
|
||||
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationClient.swift; sourceTree = "<group>"; };
|
||||
D7D68FF82C9E01B60015A515 /* KFClickable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KFClickable.swift; sourceTree = "<group>"; };
|
||||
D7DB1FDD2D5A78CE00CF06DA /* NIP44.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP44.swift; sourceTree = "<group>"; };
|
||||
D7DB1FED2D5AC50F00CF06DA /* NIP44v2EncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP44v2EncryptionTests.swift; sourceTree = "<group>"; };
|
||||
D7DB1FF02D5AC5D700CF06DA /* nip44.vectors.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = nip44.vectors.json; sourceTree = "<group>"; };
|
||||
D7DB1FF22D5AC5E400CF06DA /* LICENSES */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSES; sourceTree = "<group>"; };
|
||||
D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Undistractor.swift; sourceTree = "<group>"; };
|
||||
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
|
||||
D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentFullScreenItemNotify.swift; sourceTree = "<group>"; };
|
||||
D7EDED1B2B1178FE0018B19C /* NoteContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContent.swift; sourceTree = "<group>"; };
|
||||
@@ -2482,7 +2526,6 @@
|
||||
E0EE9DD32B8E5FEA00F3002D /* ImageProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessing.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>"; };
|
||||
E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; };
|
||||
F71694E92A662232001F4053 /* OnboardingSuggestionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSuggestionsView.swift; sourceTree = "<group>"; };
|
||||
F71694EB2A662292001F4053 /* SuggestedUsersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestedUsersViewModel.swift; sourceTree = "<group>"; };
|
||||
F71694ED2A6624F9001F4053 /* suggested_users.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = suggested_users.json; sourceTree = "<group>"; };
|
||||
@@ -2506,8 +2549,10 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
|
||||
D7DB1FE42D5A9AC900CF06DA /* CryptoSwift in Frameworks */,
|
||||
3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */,
|
||||
D70D90982CDED61800CD0534 /* CodeScanner in Frameworks */,
|
||||
D7C48C0B2D12DE0C00A3BACF /* SwiftyCrop in Frameworks */,
|
||||
D78DB8592C1CE9CA00F0AB12 /* SwipeActions in Frameworks */,
|
||||
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */,
|
||||
4C27C9322A64766F007DBC75 /* MarkdownUI in Frameworks */,
|
||||
@@ -2535,8 +2580,10 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
82D6FC862CD9A4A600C925F4 /* MarkdownUI in Frameworks */,
|
||||
D7DB1FEC2D5A9F6500CF06DA /* CryptoSwift in Frameworks */,
|
||||
82D6FC8A2CD9A54600C925F4 /* SwipeActions in Frameworks */,
|
||||
D7F360292CEBBE34009D34DA /* CodeScanner in Frameworks */,
|
||||
D7C48C0D2D12E34900A3BACF /* SwiftyCrop in Frameworks */,
|
||||
82D6FC882CD9A4DE00C925F4 /* EmojiPicker in Frameworks */,
|
||||
82D6FC842CD9A48500C925F4 /* Kingfisher in Frameworks */,
|
||||
82D6FC812CD99FC500C925F4 /* secp256k1 in Frameworks */,
|
||||
@@ -2549,8 +2596,10 @@
|
||||
files = (
|
||||
D703D7AF2C670FB700A400EA /* MarkdownUI in Frameworks */,
|
||||
D73E5F9D2C6AA8E3007EB227 /* SwipeActions in Frameworks */,
|
||||
D7DB1FE82D5A9F5300CF06DA /* CryptoSwift in Frameworks */,
|
||||
D73E5F762C6A997E007EB227 /* EmojiPicker in Frameworks */,
|
||||
D703D7192C66E47100A400EA /* UniformTypeIdentifiers.framework in Frameworks */,
|
||||
D7C48C0F2D12E35600A3BACF /* SwiftyCrop in Frameworks */,
|
||||
D703D7492C6709B100A400EA /* secp256k1 in Frameworks */,
|
||||
D70D909C2CDED7B200CD0534 /* CodeScanner in Frameworks */,
|
||||
D73E5F9B2C6AA8B0007EB227 /* Kingfisher in Frameworks */,
|
||||
@@ -2563,6 +2612,7 @@
|
||||
files = (
|
||||
D789D1202AFEFBF20083A7AB /* secp256k1 in Frameworks */,
|
||||
D7EDED312B1290B80018B19C /* MarkdownUI in Frameworks */,
|
||||
D7DB1FEA2D5A9F5A00CF06DA /* CryptoSwift in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -2719,6 +2769,7 @@
|
||||
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */,
|
||||
5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */,
|
||||
D773BC5E2C6D538500349F0A /* CommentItem.swift */,
|
||||
D767066E2C8BB3CE00F09726 /* URLHandler.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -2728,6 +2779,8 @@
|
||||
children = (
|
||||
4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */,
|
||||
4C0C03972A61E27B0098B3B8 /* primal.wasm */,
|
||||
D7DB1FF22D5AC5E400CF06DA /* LICENSES */,
|
||||
D7DB1FF02D5AC5D700CF06DA /* nip44.vectors.json */,
|
||||
);
|
||||
name = Fixtures;
|
||||
sourceTree = "<group>";
|
||||
@@ -3059,6 +3112,7 @@
|
||||
4C75EFA227FA576C0006080F /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D74EA08C2D2E26E6002290DD /* ErrorHandling */,
|
||||
D7D68FF72C9E01A80015A515 /* Utils */,
|
||||
D78DB85D2C20FE9E00F0AB12 /* Chat */,
|
||||
D71AC4CA2BA8E3320076268E /* Extensions */,
|
||||
@@ -3115,7 +3169,6 @@
|
||||
4C363AA128296A7E006E126D /* SearchView.swift */,
|
||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */,
|
||||
4C3AC7A02835A81400E1F516 /* SetupView.swift */,
|
||||
E9E4ED0A295867B900DD7078 /* ThreadView.swift */,
|
||||
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */,
|
||||
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */,
|
||||
647D9A8C2968520300A295DE /* SideMenuView.swift */,
|
||||
@@ -3130,6 +3183,7 @@
|
||||
D783A63E2AD4E53D00658DDA /* SuggestedHashtagsView.swift */,
|
||||
D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */,
|
||||
D71AD8FC2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift */,
|
||||
D74EA0922D2E77B9002290DD /* LoadableNostrEventView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -3184,7 +3238,6 @@
|
||||
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */,
|
||||
4C687C202A5F7ED00092C550 /* DamusBackground.swift */,
|
||||
5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */,
|
||||
5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */,
|
||||
);
|
||||
path = Gradients;
|
||||
sourceTree = "<group>";
|
||||
@@ -3192,6 +3245,7 @@
|
||||
4C7FF7D628233637009601DB /* Util */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */,
|
||||
D73E5F7E2C6AA066007EB227 /* DamusAliases.swift */,
|
||||
E04A37C52B544F090029650D /* URIParsing.swift */,
|
||||
4C1D4FB02A7958E60024F453 /* VersionInfo.swift */,
|
||||
@@ -3253,10 +3307,10 @@
|
||||
4C8D1A6D29F31E4100ACDF75 /* Buttons */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CB017202D2D985800A9ED05 /* CoinosButton.swift */,
|
||||
4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */,
|
||||
F71694F32A6732B7001F4053 /* GradientFollowButton.swift */,
|
||||
4C7D09652A0AE62100943473 /* AlbyButton.swift */,
|
||||
5C7389B62B9E692E00781E0A /* MutinyButton.swift */,
|
||||
);
|
||||
path = Buttons;
|
||||
sourceTree = "<group>";
|
||||
@@ -3307,6 +3361,7 @@
|
||||
4CA3529C2A76AE47003BB08B /* Notify */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D706C5B62D602A050027C627 /* QueueableNotify.swift */,
|
||||
D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */,
|
||||
4C86F7C52A76C51100EC0817 /* AttachedWalletNotify.swift */,
|
||||
4C9D6D152B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift */,
|
||||
@@ -3554,6 +3609,8 @@
|
||||
4CE6DEE527F7A08100C66700 /* damus */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D7DB1FDC2D5A77E500CF06DA /* NIP44 */,
|
||||
D755B28B2D3E7D6500BBEEFA /* NIP37 */,
|
||||
4C45E5002BED4CE10025A428 /* NIP10 */,
|
||||
4C1D4FB32A7967990024F453 /* build-git-hash.txt */,
|
||||
4CA3529C2A76AE47003BB08B /* Notify */,
|
||||
@@ -3590,6 +3647,8 @@
|
||||
4CE6DEF627F7A08200C66700 /* damusTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D7DB1FED2D5AC50F00CF06DA /* NIP44v2EncryptionTests.swift */,
|
||||
D7A0D8742D1FE66A00DCBE59 /* EditPictureControlTests.swift */,
|
||||
E06336A72B7582D600A88E6B /* Assets */,
|
||||
D72A2D032AD9C165002AFF62 /* Mocking */,
|
||||
4C9B0DEC2A65A74000CBDA21 /* Util */,
|
||||
@@ -3599,6 +3658,7 @@
|
||||
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */,
|
||||
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */,
|
||||
E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */,
|
||||
D7BEE6F82D37B37400CF659F /* DraftTests.swift */,
|
||||
4C363A9F2828A8DD006E126D /* LikeTests.swift */,
|
||||
4C363A9D2828A822006E126D /* ReplyTests.swift */,
|
||||
4CE6DEF727F7A08200C66700 /* damusTests.swift */,
|
||||
@@ -3631,6 +3691,7 @@
|
||||
D753CEA92BE9DE04001C3A5D /* MutingTests.swift */,
|
||||
4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */,
|
||||
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */,
|
||||
3A96E3FD2D6BCE3800AE1630 /* RepostedTests.swift */,
|
||||
);
|
||||
path = damusTests;
|
||||
sourceTree = "<group>";
|
||||
@@ -3718,6 +3779,7 @@
|
||||
4CF0ABF42985CD4200D66079 /* Posting */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D706C5AE2D5D31B20027C627 /* AutoSaveIndicatorView.swift */,
|
||||
4CF0ABF52985CD5500D66079 /* UserSearch.swift */,
|
||||
);
|
||||
path = Posting;
|
||||
@@ -3849,6 +3911,14 @@
|
||||
path = Mocking;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D74EA08C2D2E26E6002290DD /* ErrorHandling */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D74EA08D2D2E271E002290DD /* ErrorView.swift */,
|
||||
);
|
||||
path = ErrorHandling;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D74F43082B23F09300425B75 /* Purple */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3862,6 +3932,14 @@
|
||||
path = Purple;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D755B28B2D3E7D6500BBEEFA /* NIP37 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D755B28C2D3E7D7D00BBEEFA /* NIP37Draft.swift */,
|
||||
);
|
||||
path = NIP37;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D78DB85D2C20FE9E00F0AB12 /* Chat */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3902,6 +3980,14 @@
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D7DB1FDC2D5A77E500CF06DA /* NIP44 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D7DB1FDD2D5A78CE00CF06DA /* NIP44.swift */,
|
||||
);
|
||||
path = NIP44;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E06336A72B7582D600A88E6B /* Assets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3967,6 +4053,8 @@
|
||||
3A0A30BA2C21397A00F8C9BC /* EmojiPicker */,
|
||||
D78DB8582C1CE9CA00F0AB12 /* SwipeActions */,
|
||||
D70D90972CDED61800CD0534 /* CodeScanner */,
|
||||
D7C48C0A2D12DE0C00A3BACF /* SwiftyCrop */,
|
||||
D7DB1FE32D5A9AC900CF06DA /* CryptoSwift */,
|
||||
);
|
||||
productName = damus;
|
||||
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
|
||||
@@ -4032,6 +4120,8 @@
|
||||
82D6FC872CD9A4DE00C925F4 /* EmojiPicker */,
|
||||
82D6FC892CD9A54600C925F4 /* SwipeActions */,
|
||||
D7F360282CEBBE34009D34DA /* CodeScanner */,
|
||||
D7C48C0C2D12E34900A3BACF /* SwiftyCrop */,
|
||||
D7DB1FEB2D5A9F6500CF06DA /* CryptoSwift */,
|
||||
);
|
||||
productName = "share extension";
|
||||
productReference = 82D6FA972CD9820500C925F4 /* ShareExtension.appex */;
|
||||
@@ -4059,6 +4149,8 @@
|
||||
D73E5F9A2C6AA8B0007EB227 /* Kingfisher */,
|
||||
D73E5F9C2C6AA8E3007EB227 /* SwipeActions */,
|
||||
D70D909B2CDED7B200CD0534 /* CodeScanner */,
|
||||
D7C48C0E2D12E35600A3BACF /* SwiftyCrop */,
|
||||
D7DB1FE72D5A9F5300CF06DA /* CryptoSwift */,
|
||||
);
|
||||
productName = "highlighter action extension";
|
||||
productReference = D703D7172C66E47100A400EA /* HighlighterActionExtension.appex */;
|
||||
@@ -4081,6 +4173,7 @@
|
||||
packageProductDependencies = (
|
||||
D789D11F2AFEFBF20083A7AB /* secp256k1 */,
|
||||
D7EDED302B1290B80018B19C /* MarkdownUI */,
|
||||
D7DB1FE92D5A9F5A00CF06DA /* CryptoSwift */,
|
||||
);
|
||||
productName = DamusNotificationService;
|
||||
productReference = D79C4C142AFEB061003A41B4 /* DamusNotificationService.appex */;
|
||||
@@ -4167,6 +4260,8 @@
|
||||
3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */,
|
||||
D78DB8572C1CE9CA00F0AB12 /* XCRemoteSwiftPackageReference "SwipeActions" */,
|
||||
D70D90962CDED61800CD0534 /* XCRemoteSwiftPackageReference "CodeScanner" */,
|
||||
D7C48C092D12DE0C00A3BACF /* XCRemoteSwiftPackageReference "SwiftyCrop" */,
|
||||
D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */,
|
||||
);
|
||||
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -4206,7 +4301,9 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E06336AB2B75850100A88E6B /* img_with_location.jpeg in Resources */,
|
||||
D7DB1FF12D5AC5D700CF06DA /* nip44.vectors.json in Resources */,
|
||||
4C0C039A2A61E27B0098B3B8 /* bool_setting.wasm in Resources */,
|
||||
D7DB1FF32D5AC5EA00CF06DA /* LICENSES in Resources */,
|
||||
4C0C03992A61E27B0098B3B8 /* primal.wasm in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -4299,7 +4396,6 @@
|
||||
4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */,
|
||||
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
|
||||
4CDD1AE22A6B3074001CD4DF /* NdbTagsIterator.swift in Sources */,
|
||||
5C7389B72B9E692E00781E0A /* MutinyButton.swift in Sources */,
|
||||
4C216F34286F5ACD00040376 /* DMView.swift in Sources */,
|
||||
D7CB5D512B1174D100AD4105 /* FriendFilter.swift in Sources */,
|
||||
D7CBD1D42B8D21DC00BFD889 /* DamusPurpleNotificationManagement.swift in Sources */,
|
||||
@@ -4429,7 +4525,6 @@
|
||||
4CA3FA1029F593D000FDB3C3 /* ZapTypePicker.swift in Sources */,
|
||||
4C32B95D2A9AD44700DC3548 /* Documentation.docc in Sources */,
|
||||
4C3EA66828FF5F9900C48A62 /* hex.c in Sources */,
|
||||
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */,
|
||||
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */,
|
||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
||||
4CF0ABE7298444FD00D66079 /* EventMutingContainerView.swift in Sources */,
|
||||
@@ -4461,6 +4556,7 @@
|
||||
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
||||
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
|
||||
D7DB93062D66A44100DA1EE5 /* Undistractor.swift in Sources */,
|
||||
D72E12782BEED22500F4F781 /* Array.swift in Sources */,
|
||||
4C198DF529F88D2E004C165C /* ImageMetadata.swift in Sources */,
|
||||
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
||||
@@ -4536,12 +4632,14 @@
|
||||
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */,
|
||||
D734B1452CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */,
|
||||
4C4E137D2A76D63600BDD832 /* UnmuteThreadNotify.swift in Sources */,
|
||||
D706C5B72D602A110027C627 /* QueueableNotify.swift in Sources */,
|
||||
4CE4F0F829DB7399005914DB /* ThiccDivider.swift in Sources */,
|
||||
4CFF8F5929C9FD1E008DB934 /* DamusPurpleView.swift in Sources */,
|
||||
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
|
||||
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
||||
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
|
||||
3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */,
|
||||
D755B28D2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */,
|
||||
F71694F82A6983AF001F4053 /* GrayGradient.swift in Sources */,
|
||||
4C1D4FB12A7958E60024F453 /* VersionInfo.swift in Sources */,
|
||||
D7FF94002AC7AC5300FD969D /* RelayURL.swift in Sources */,
|
||||
@@ -4604,7 +4702,6 @@
|
||||
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
|
||||
F71694EA2A662232001F4053 /* OnboardingSuggestionsView.swift in Sources */,
|
||||
4C12536A2A76D3850004F4B8 /* RelaysChangedNotify.swift in Sources */,
|
||||
5C7389B92B9E69ED00781E0A /* MutinyGradient.swift in Sources */,
|
||||
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
|
||||
D7373BAA2B68A65A00F7783D /* PurpleAccountUpdateNotify.swift in Sources */,
|
||||
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
|
||||
@@ -4620,6 +4717,7 @@
|
||||
4CE1399429F0669900AC6A0B /* BigButton.swift in Sources */,
|
||||
D7EFBA372CC322F300F45588 /* DamusVideoControlsView.swift in Sources */,
|
||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
||||
D706C5AF2D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */,
|
||||
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */,
|
||||
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
||||
4C1253682A76D2470004F4B8 /* MuteNotify.swift in Sources */,
|
||||
@@ -4629,6 +4727,7 @@
|
||||
4CA927612A290E340098A105 /* EventShell.swift in Sources */,
|
||||
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
||||
4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */,
|
||||
D74EA0942D2E77B9002290DD /* LoadableNostrEventView.swift in Sources */,
|
||||
4CEE2AF3280B25C500AB5EEF /* ProfilePicView.swift in Sources */,
|
||||
4CC7AAF6297F1A6A00430951 /* EventBody.swift in Sources */,
|
||||
D76556D62B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift in Sources */,
|
||||
@@ -4667,6 +4766,7 @@
|
||||
50A60D142A28BEEE00186190 /* RelayLog.swift in Sources */,
|
||||
D7EDED212B117DCA0018B19C /* SequenceUtils.swift in Sources */,
|
||||
BA37598A2ABCCDE40018D73B /* ImageResizer.swift in Sources */,
|
||||
D7DB1FDE2D5A78CE00CF06DA /* NIP44.swift in Sources */,
|
||||
B51C1CEB2B55A60A00E312A9 /* MuteDurationMenu.swift in Sources */,
|
||||
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */,
|
||||
4C32B9512A9AD44700DC3548 /* FlatbuffersErrors.swift in Sources */,
|
||||
@@ -4702,6 +4802,7 @@
|
||||
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */,
|
||||
4C1253622A76D00B0004F4B8 /* PostNotify.swift in Sources */,
|
||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
|
||||
5CB017232D2D985E00A9ED05 /* CoinosButton.swift in Sources */,
|
||||
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */,
|
||||
4C9147002A2A891E00DDEA40 /* error.c in Sources */,
|
||||
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */,
|
||||
@@ -4735,6 +4836,7 @@
|
||||
D7CB5D4E2B11728000AD4105 /* NewEventsBits.swift in Sources */,
|
||||
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
|
||||
B57B4C622B312BD700A232C0 /* ReconnectRelaysNotify.swift in Sources */,
|
||||
D767066F2C8BB3CF00F09726 /* URLHandler.swift in Sources */,
|
||||
D7ADD3DE2B53854300F104C4 /* DamusPurpleURL.swift in Sources */,
|
||||
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */,
|
||||
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */,
|
||||
@@ -4747,6 +4849,7 @@
|
||||
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */,
|
||||
D7100C5C2B77016700C59298 /* IAPProductStateView.swift in Sources */,
|
||||
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
|
||||
D74EA0902D2E271E002290DD /* ErrorView.swift in Sources */,
|
||||
4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */,
|
||||
BA37598E2ABCCE500018D73B /* VideoCaptureProcessor.swift in Sources */,
|
||||
4C9B0DF32A65C46800CBDA21 /* ProfileEditButton.swift in Sources */,
|
||||
@@ -4777,10 +4880,12 @@
|
||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
||||
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */,
|
||||
B5A75C2A2B546D94007AFBC0 /* MuteItemTests.swift in Sources */,
|
||||
D7DB1FEE2D5AC51B00CF06DA /* NIP44v2EncryptionTests.swift in Sources */,
|
||||
4C4F14A72A2A61A30045A0B9 /* NostrScriptTests.swift in Sources */,
|
||||
D78525252A7B2EA4002FA637 /* NoteContentViewTests.swift in Sources */,
|
||||
4C3EA67B28FF7B3900C48A62 /* InvoiceTests.swift in Sources */,
|
||||
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */,
|
||||
3A96E3FE2D6BCE3800AE1630 /* RepostedTests.swift in Sources */,
|
||||
4C7D097E2A0C58B900943473 /* WalletConnectTests.swift in Sources */,
|
||||
4CB883AA297612FF00DC99E7 /* ZapTests.swift in Sources */,
|
||||
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */,
|
||||
@@ -4790,11 +4895,13 @@
|
||||
75AD872B2AA23A460085EF2C /* Block+Tests.swift in Sources */,
|
||||
E0E024112B7C19C20075735D /* TranslationTests.swift in Sources */,
|
||||
F944F56E29EA9CCC0067B3BF /* DamusParseContentTests.swift in Sources */,
|
||||
D7BEE6F92D37B37400CF659F /* DraftTests.swift in Sources */,
|
||||
B501062D2B363036003874F5 /* AuthIntegrationTests.swift in Sources */,
|
||||
4CB883AE2976FA9300DC99E7 /* FormatTests.swift in Sources */,
|
||||
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */,
|
||||
E06336AA2B75832100A88E6B /* ImageMetadataTest.swift in Sources */,
|
||||
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
|
||||
D7A0D8752D1FE67900DCBE59 /* EditPictureControlTests.swift in Sources */,
|
||||
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */,
|
||||
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */,
|
||||
4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */,
|
||||
@@ -4885,12 +4992,14 @@
|
||||
82D6FAE62CD99F7900C925F4 /* LocalNotificationNotify.swift in Sources */,
|
||||
82D6FAE72CD99F7900C925F4 /* LoginNotify.swift in Sources */,
|
||||
82D6FAE82CD99F7900C925F4 /* LogoutNotify.swift in Sources */,
|
||||
D706C5B12D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */,
|
||||
82D6FAE92CD99F7900C925F4 /* NewMutesNotify.swift in Sources */,
|
||||
82D6FAEA2CD99F7900C925F4 /* NewUnmutesNotify.swift in Sources */,
|
||||
82D6FAEB2CD99F7900C925F4 /* Notify.swift in Sources */,
|
||||
82D6FAEC2CD99F7900C925F4 /* OnlyZapsNotify.swift in Sources */,
|
||||
82D6FAED2CD99F7900C925F4 /* PostNotify.swift in Sources */,
|
||||
82D6FAEE2CD99F7900C925F4 /* PresentSheetNotify.swift in Sources */,
|
||||
D74EA0932D2E77B9002290DD /* LoadableNostrEventView.swift in Sources */,
|
||||
82D6FAEF2CD99F7900C925F4 /* ProfileUpdatedNotify.swift in Sources */,
|
||||
82D6FAF02CD99F7900C925F4 /* ReportNotify.swift in Sources */,
|
||||
82D6FAF12CD99F7900C925F4 /* ScrollToTopNotify.swift in Sources */,
|
||||
@@ -4919,6 +5028,7 @@
|
||||
82D6FB082CD99F7900C925F4 /* UserStatusSheet.swift in Sources */,
|
||||
82D6FB092CD99F7900C925F4 /* SearchHeaderView.swift in Sources */,
|
||||
82D6FB0A2CD99F7900C925F4 /* DamusGradient.swift in Sources */,
|
||||
D7DB93052D66A44100DA1EE5 /* Undistractor.swift in Sources */,
|
||||
82D6FB0B2CD99F7900C925F4 /* AlbyGradient.swift in Sources */,
|
||||
82D6FB0C2CD99F7900C925F4 /* GoldSupportGradient.swift in Sources */,
|
||||
82D6FB0D2CD99F7900C925F4 /* PinkGradient.swift in Sources */,
|
||||
@@ -4926,7 +5036,6 @@
|
||||
82D6FB0F2CD99F7900C925F4 /* DamusLogoGradient.swift in Sources */,
|
||||
82D6FB102CD99F7900C925F4 /* DamusBackground.swift in Sources */,
|
||||
82D6FB112CD99F7900C925F4 /* DamusLightGradient.swift in Sources */,
|
||||
82D6FB122CD99F7900C925F4 /* MutinyGradient.swift in Sources */,
|
||||
82D6FB132CD99F7900C925F4 /* Shimmer.swift in Sources */,
|
||||
82D6FB142CD99F7900C925F4 /* EndBlock.swift in Sources */,
|
||||
82D6FB152CD99F7900C925F4 /* ImageCarousel.swift in Sources */,
|
||||
@@ -4980,6 +5089,7 @@
|
||||
82D6FB452CD99F7900C925F4 /* InputDismissKeyboard.swift in Sources */,
|
||||
82D6FB462CD99F7900C925F4 /* Constants.swift in Sources */,
|
||||
82D6FB472CD99F7900C925F4 /* LinkView.swift in Sources */,
|
||||
D7DB1FDF2D5A78CE00CF06DA /* NIP44.swift in Sources */,
|
||||
82D6FB482CD99F7900C925F4 /* PreviewCache.swift in Sources */,
|
||||
82D6FB492CD99F7900C925F4 /* Theme.swift in Sources */,
|
||||
82D6FB4A2CD99F7900C925F4 /* NIP05.swift in Sources */,
|
||||
@@ -5057,6 +5167,7 @@
|
||||
82D6FB922CD99F7900C925F4 /* Wallet.swift in Sources */,
|
||||
82D6FB932CD99F7900C925F4 /* Report.swift in Sources */,
|
||||
82D6FB942CD99F7900C925F4 /* LibreTranslateServer.swift in Sources */,
|
||||
D74EA08E2D2E271E002290DD /* ErrorView.swift in Sources */,
|
||||
82D6FB952CD99F7900C925F4 /* TranslationService.swift in Sources */,
|
||||
82D6FB962CD99F7900C925F4 /* DeepLPlan.swift in Sources */,
|
||||
82D6FB972CD99F7900C925F4 /* ZapsModel.swift in Sources */,
|
||||
@@ -5069,6 +5180,7 @@
|
||||
82D6FB9E2CD99F7900C925F4 /* ContentFilters.swift in Sources */,
|
||||
82D6FB9F2CD99F7900C925F4 /* DamusCacheManager.swift in Sources */,
|
||||
82D6FBA02CD99F7900C925F4 /* NotificationsManager.swift in Sources */,
|
||||
D755B28E2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */,
|
||||
82D6FBA12CD99F7900C925F4 /* Contacts+.swift in Sources */,
|
||||
82D6FBA22CD99F7900C925F4 /* ZapType.swift in Sources */,
|
||||
82D6FBA32CD99F7900C925F4 /* NewEventsBits.swift in Sources */,
|
||||
@@ -5124,7 +5236,6 @@
|
||||
82D6FBD82CD99F7900C925F4 /* FriendsButton.swift in Sources */,
|
||||
82D6FBD92CD99F7900C925F4 /* GradientFollowButton.swift in Sources */,
|
||||
82D6FBDA2CD99F7900C925F4 /* AlbyButton.swift in Sources */,
|
||||
82D6FBDB2CD99F7900C925F4 /* MutinyButton.swift in Sources */,
|
||||
82D6FBDC2CD99F7900C925F4 /* DamusVideoPlayerView.swift in Sources */,
|
||||
82D6FBDD2CD99F7900C925F4 /* DamusVideoPlayer.swift in Sources */,
|
||||
82D6FBDE2CD99F7900C925F4 /* DamusVideoCoordinator.swift in Sources */,
|
||||
@@ -5179,6 +5290,7 @@
|
||||
82D6FC0E2CD99F7900C925F4 /* ProfilePicView.swift in Sources */,
|
||||
82D6FC0F2CD99F7900C925F4 /* ProfileView.swift in Sources */,
|
||||
82D6FC102CD99F7900C925F4 /* ProfileNameView.swift in Sources */,
|
||||
5CB017212D2D985E00A9ED05 /* CoinosButton.swift in Sources */,
|
||||
82D6FC112CD99F7900C925F4 /* MaybeAnonPfpView.swift in Sources */,
|
||||
82D6FC122CD99F7900C925F4 /* EventProfileName.swift in Sources */,
|
||||
82D6FC132CD99F7900C925F4 /* FriendIcon.swift in Sources */,
|
||||
@@ -5238,6 +5350,7 @@
|
||||
82D6FC492CD99F7900C925F4 /* BigButton.swift in Sources */,
|
||||
82D6FC4A2CD99F7900C925F4 /* AddRelayView.swift in Sources */,
|
||||
82D6FC4B2CD99F7900C925F4 /* BlocksView.swift in Sources */,
|
||||
D74EA0912D2E3464002290DD /* URLHandler.swift in Sources */,
|
||||
82D6FC4C2CD99F7900C925F4 /* BookmarksView.swift in Sources */,
|
||||
82D6FC4D2CD99F7900C925F4 /* CarouselView.swift in Sources */,
|
||||
82D6FC4E2CD99F7900C925F4 /* ConfigView.swift in Sources */,
|
||||
@@ -5247,6 +5360,7 @@
|
||||
82D6FC522CD99F7900C925F4 /* DMView.swift in Sources */,
|
||||
82D6FC532CD99F7900C925F4 /* EmptyTimelineView.swift in Sources */,
|
||||
82D6FC542CD99F7900C925F4 /* EmptyUserSearchView.swift in Sources */,
|
||||
D706C5B82D602A110027C627 /* QueueableNotify.swift in Sources */,
|
||||
82D6FC552CD99F7900C925F4 /* EventView.swift in Sources */,
|
||||
82D6FC562CD99F7900C925F4 /* EventDetailView.swift in Sources */,
|
||||
82D6FC572CD99F7900C925F4 /* FollowButtonView.swift in Sources */,
|
||||
@@ -5270,7 +5384,6 @@
|
||||
82D6FC682CD99F7900C925F4 /* SearchView.swift in Sources */,
|
||||
82D6FC692CD99F7900C925F4 /* SelectWalletView.swift in Sources */,
|
||||
82D6FC6A2CD99F7900C925F4 /* SetupView.swift in Sources */,
|
||||
82D6FC6B2CD99F7900C925F4 /* ThreadView.swift in Sources */,
|
||||
82D6FC6C2CD99F7900C925F4 /* TimelineView.swift in Sources */,
|
||||
82D6FC6D2CD99F7900C925F4 /* UserRelaysView.swift in Sources */,
|
||||
82D6FC6E2CD99F7900C925F4 /* SideMenuView.swift in Sources */,
|
||||
@@ -5328,16 +5441,17 @@
|
||||
D73E5E3A2C6A97F4007EB227 /* SwipeToDismiss.swift in Sources */,
|
||||
D73E5E3B2C6A97F4007EB227 /* MusicController.swift in Sources */,
|
||||
D73E5E3C2C6A97F4007EB227 /* UserStatusView.swift in Sources */,
|
||||
D74EA08F2D2E271E002290DD /* ErrorView.swift in Sources */,
|
||||
D73E5E3E2C6A97F4007EB227 /* SearchHeaderView.swift in Sources */,
|
||||
D73E5E3F2C6A97F4007EB227 /* DamusGradient.swift in Sources */,
|
||||
D73E5E402C6A97F4007EB227 /* AlbyGradient.swift in Sources */,
|
||||
D73E5E412C6A97F4007EB227 /* GoldSupportGradient.swift in Sources */,
|
||||
D73E5E422C6A97F4007EB227 /* PinkGradient.swift in Sources */,
|
||||
D73E5E432C6A97F4007EB227 /* GrayGradient.swift in Sources */,
|
||||
D7DB93072D66A44100DA1EE5 /* Undistractor.swift in Sources */,
|
||||
D73E5E442C6A97F4007EB227 /* DamusLogoGradient.swift in Sources */,
|
||||
D73E5E452C6A97F4007EB227 /* DamusBackground.swift in Sources */,
|
||||
D73E5E462C6A97F4007EB227 /* DamusLightGradient.swift in Sources */,
|
||||
D73E5E472C6A97F4007EB227 /* MutinyGradient.swift in Sources */,
|
||||
D73E5E482C6A97F4007EB227 /* Shimmer.swift in Sources */,
|
||||
D73E5E492C6A97F4007EB227 /* EndBlock.swift in Sources */,
|
||||
D73E5E4D2C6A97F4007EB227 /* NIP05Badge.swift in Sources */,
|
||||
@@ -5423,6 +5537,7 @@
|
||||
D73E5E9C2C6A97F4007EB227 /* Reply.swift in Sources */,
|
||||
D73E5E9D2C6A97F4007EB227 /* SearchModel.swift in Sources */,
|
||||
D73E5E9E2C6A97F4007EB227 /* NostrFilter+Hashable.swift in Sources */,
|
||||
D74EA0952D2E77B9002290DD /* LoadableNostrEventView.swift in Sources */,
|
||||
D73E5F912C6AA71B007EB227 /* InputDismissKeyboard.swift in Sources */,
|
||||
D73E5E9F2C6A97F4007EB227 /* CreateAccountModel.swift in Sources */,
|
||||
D73E5EA12C6A97F4007EB227 /* SignalModel.swift in Sources */,
|
||||
@@ -5449,6 +5564,7 @@
|
||||
D73E5EB42C6A97F4007EB227 /* NoteContent.swift in Sources */,
|
||||
D73E5EB52C6A97F4007EB227 /* LongformEvent.swift in Sources */,
|
||||
D73E5EB62C6A97F4007EB227 /* PushNotificationClient.swift in Sources */,
|
||||
D706C5B92D602A110027C627 /* QueueableNotify.swift in Sources */,
|
||||
D71AD8FD2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
||||
D73E5EB72C6A97F4007EB227 /* HighlightEvent.swift in Sources */,
|
||||
D73E5EB82C6A97F4007EB227 /* RelayConnection.swift in Sources */,
|
||||
@@ -5457,6 +5573,7 @@
|
||||
D73E5EBB2C6A97F4007EB227 /* Nip98HTTPAuth.swift in Sources */,
|
||||
D73E5EBC2C6A97F4007EB227 /* Relay.swift in Sources */,
|
||||
D73E5EBD2C6A97F4007EB227 /* NostrRequest.swift in Sources */,
|
||||
5CB017222D2D985E00A9ED05 /* CoinosButton.swift in Sources */,
|
||||
D73E5EBE2C6A97F4007EB227 /* NostrLink.swift in Sources */,
|
||||
D73E5EBF2C6A97F4007EB227 /* WebSocket.swift in Sources */,
|
||||
D73E5F812C6AA07A007EB227 /* HighlighterExtensionAliases.swift in Sources */,
|
||||
@@ -5476,7 +5593,6 @@
|
||||
D73E5ED42C6A97F4007EB227 /* FriendsButton.swift in Sources */,
|
||||
D73E5ED52C6A97F4007EB227 /* GradientFollowButton.swift in Sources */,
|
||||
D73E5ED62C6A97F4007EB227 /* AlbyButton.swift in Sources */,
|
||||
D73E5ED72C6A97F4007EB227 /* MutinyButton.swift in Sources */,
|
||||
D73E5ED82C6A97F4007EB227 /* DamusVideoPlayerView.swift in Sources */,
|
||||
D73E5ED92C6A97F4007EB227 /* DamusVideoPlayer.swift in Sources */,
|
||||
D73E5EDA2C6A97F4007EB227 /* DamusVideoCoordinator.swift in Sources */,
|
||||
@@ -5628,7 +5744,6 @@
|
||||
D73E5F602C6A97F5007EB227 /* SearchResultsView.swift in Sources */,
|
||||
D73E5F612C6A97F5007EB227 /* SearchView.swift in Sources */,
|
||||
D73E5F622C6A97F5007EB227 /* SelectWalletView.swift in Sources */,
|
||||
D73E5F642C6A97F5007EB227 /* ThreadView.swift in Sources */,
|
||||
D73E5F652C6A97F5007EB227 /* TimelineView.swift in Sources */,
|
||||
D73E5F662C6A97F5007EB227 /* UserRelaysView.swift in Sources */,
|
||||
D73E5F682C6A97F5007EB227 /* BannerImageView.swift in Sources */,
|
||||
@@ -5709,6 +5824,8 @@
|
||||
D703D7712C670B6D00A400EA /* NdbProfile.swift in Sources */,
|
||||
D703D7A22C670E1A00A400EA /* list.c in Sources */,
|
||||
D703D7A42C670E3C00A400EA /* midl.c in Sources */,
|
||||
D7DB1FE02D5A78CE00CF06DA /* NIP44.swift in Sources */,
|
||||
D706C5B02D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */,
|
||||
D703D7982C670DF200A400EA /* utf8.c in Sources */,
|
||||
D703D78B2C670C9500A400EA /* MakeZapRequest.swift in Sources */,
|
||||
D703D7862C670C6500A400EA /* NewUnmutesNotify.swift in Sources */,
|
||||
@@ -5751,6 +5868,7 @@
|
||||
D703D7622C670ACB00A400EA /* ByteBuffer.swift in Sources */,
|
||||
D703D79A2C670DFD00A400EA /* bech32.c in Sources */,
|
||||
D703D7B62C67118200A400EA /* String+extension.swift in Sources */,
|
||||
D74EA08A2D2BF2A7002290DD /* URLHandler.swift in Sources */,
|
||||
D703D76C2C670B3900A400EA /* Post.swift in Sources */,
|
||||
D703D77A2C670BEB00A400EA /* VeriferOptions.swift in Sources */,
|
||||
D73E5F9E2C6AA9F7007EB227 /* nostrscript.c in Sources */,
|
||||
@@ -5758,6 +5876,7 @@
|
||||
D703D7472C67092700A400EA /* UserSettingsStore.swift in Sources */,
|
||||
D703D7852C670C6100A400EA /* Notify.swift in Sources */,
|
||||
D703D7532C670A2600A400EA /* Wallet.swift in Sources */,
|
||||
D755B28F2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */,
|
||||
D703D75F2C670AA200A400EA /* NostrEvent.swift in Sources */,
|
||||
D703D7442C67086800A400EA /* HeadlessDamusState.swift in Sources */,
|
||||
D703D7922C670D2900A400EA /* RelayURL.swift in Sources */,
|
||||
@@ -6224,7 +6343,7 @@
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Damus needs access to your camera if you want to scan QR codes and upload photos from it";
|
||||
INFOPLIST_KEY_NSFaceIDUsageDescription = "Local authentication to access private key";
|
||||
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Damus needs access to your microphone if you want to upload recorded videos from it";
|
||||
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Damus needs access to your microphone to allow you to create video recordings that you can choose to post publicly on the network";
|
||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images.";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
@@ -6241,7 +6360,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.12;
|
||||
MARKETING_VERSION = 1.13;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
@@ -6251,6 +6370,7 @@
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
@@ -6275,7 +6395,7 @@
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Damus needs access to your camera if you want to scan QR codes and upload photos from it";
|
||||
INFOPLIST_KEY_NSFaceIDUsageDescription = "Local authentication to access private key";
|
||||
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Damus needs access to your microphone if you want to upload recorded videos from it";
|
||||
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Damus needs access to your microphone to allow you to create video recordings that you can choose to post publicly on the network";
|
||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images.";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
@@ -6292,7 +6412,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.12;
|
||||
MARKETING_VERSION = 1.13;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
@@ -6300,6 +6420,7 @@
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h";
|
||||
SWIFT_STRICT_CONCURRENCY = complete;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
@@ -6398,7 +6519,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.13;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jb55.damus2.share-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -6432,7 +6553,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.13;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jb55.damus2.share-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -6466,7 +6587,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.13;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jb55.damus2.highlighter-action-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -6501,7 +6622,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.13;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.jb55.damus2.highlighter-action-extension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -6520,6 +6641,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CODE_SIGN_ENTITLEMENTS = DamusNotificationService/DamusNotificationService.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
@@ -6534,6 +6656,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.13;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2.DamusNotificationService;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -6553,6 +6676,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CODE_SIGN_ENTITLEMENTS = DamusNotificationService/DamusNotificationService.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
@@ -6567,6 +6691,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.13;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2.DamusNotificationService;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -6711,6 +6836,22 @@
|
||||
minimumVersion = 1.14.1;
|
||||
};
|
||||
};
|
||||
D7C48C092D12DE0C00A3BACF /* XCRemoteSwiftPackageReference "SwiftyCrop" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/benedom/SwiftyCrop";
|
||||
requirement = {
|
||||
kind = revision;
|
||||
revision = 454d0a0d4faf6f3a19c8d817ab9d7d27524bd79f;
|
||||
};
|
||||
};
|
||||
D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/krzyzanowskim/CryptoSwift.git";
|
||||
requirement = {
|
||||
kind = revision;
|
||||
revision = e74bbbfbef939224b242ae7c342a90e60b88b5ce;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
@@ -6824,6 +6965,41 @@
|
||||
package = D7A343EC2AD0D77C00CED48B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */;
|
||||
productName = SnapshotTesting;
|
||||
};
|
||||
D7C48C0A2D12DE0C00A3BACF /* SwiftyCrop */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D7C48C092D12DE0C00A3BACF /* XCRemoteSwiftPackageReference "SwiftyCrop" */;
|
||||
productName = SwiftyCrop;
|
||||
};
|
||||
D7C48C0C2D12E34900A3BACF /* SwiftyCrop */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D7C48C092D12DE0C00A3BACF /* XCRemoteSwiftPackageReference "SwiftyCrop" */;
|
||||
productName = SwiftyCrop;
|
||||
};
|
||||
D7C48C0E2D12E35600A3BACF /* SwiftyCrop */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D7C48C092D12DE0C00A3BACF /* XCRemoteSwiftPackageReference "SwiftyCrop" */;
|
||||
productName = SwiftyCrop;
|
||||
};
|
||||
D7DB1FE32D5A9AC900CF06DA /* CryptoSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */;
|
||||
productName = CryptoSwift;
|
||||
};
|
||||
D7DB1FE72D5A9F5300CF06DA /* CryptoSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */;
|
||||
productName = CryptoSwift;
|
||||
};
|
||||
D7DB1FE92D5A9F5A00CF06DA /* CryptoSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */;
|
||||
productName = CryptoSwift;
|
||||
};
|
||||
D7DB1FEB2D5A9F6500CF06DA /* CryptoSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */;
|
||||
productName = CryptoSwift;
|
||||
};
|
||||
D7EDED242B117F7C0018B19C /* MarkdownUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 4C27C9302A64766F007DBC75 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "534c8e58993919d5ead25ceb4788c8e492c86bc2cf5833dc651ae60a0f30169c",
|
||||
"originHash" : "085cf0f645323bf77edb52886489bf77b309a0a2d2b78a54beaf8520b540d596",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "codescanner",
|
||||
@@ -9,6 +9,14 @@
|
||||
"revision" : "9fa582f4b36c69c2a55bff5fb3377eb170ae273c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "cryptoswift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
|
||||
"state" : {
|
||||
"revision" : "e74bbbfbef939224b242ae7c342a90e60b88b5ce"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "emojikit",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -97,6 +105,14 @@
|
||||
"version" : "0.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftycrop",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/benedom/SwiftyCrop",
|
||||
"state" : {
|
||||
"revision" : "454d0a0d4faf6f3a19c8d817ab9d7d27524bd79f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swipeactions",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 511 KiB After Width: | Height: | Size: 511 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 547 KiB After Width: | Height: | Size: 547 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
6
damus/Assets.xcassets/Logos/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
23
damus/Assets.xcassets/Logos/alby.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "alby.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "alby.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "alby.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 300 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "profile-banner.jpeg",
|
||||
"filename" : "coinos.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
BIN
damus/Assets.xcassets/Logos/coinos.imageset/coinos.png
vendored
Normal file
|
After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 290 KiB |
12
damus/Assets.xcassets/alby-go.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "alby-go.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/alby-go.imageset/alby-go.png
vendored
Normal file
|
After Width: | Height: | Size: 40 KiB |
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "alby.svg",
|
||||
"idiom": "universal",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "universal",
|
||||
"scale": "2x",
|
||||
"filename": "alby.svg"
|
||||
},
|
||||
{
|
||||
"idiom": "universal",
|
||||
"scale": "3x",
|
||||
"filename": "alby.svg"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
//
|
||||
// MutinyGradient.swift
|
||||
// damus
|
||||
//
|
||||
// Created by eric on 3/9/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
fileprivate let mutiny_grad_c1 = hex_col(r: 39, g: 95, b: 161)
|
||||
fileprivate let mutiny_grad_c2 = hex_col(r: 13, g: 33, b: 56)
|
||||
fileprivate let mutiny_grad = [mutiny_grad_c2, mutiny_grad_c1]
|
||||
|
||||
let MutinyGradient: LinearGradient =
|
||||
LinearGradient(colors: mutiny_grad, startPoint: .top, endPoint: .bottom)
|
||||
@@ -10,22 +10,68 @@ import SwiftUI
|
||||
struct Reposted: View {
|
||||
let damus: DamusState
|
||||
let pubkey: Pubkey
|
||||
let target: NostrEvent
|
||||
@State var reposts: Int
|
||||
|
||||
init(damus: DamusState, pubkey: Pubkey, target: NostrEvent) {
|
||||
self.damus = damus
|
||||
self.pubkey = pubkey
|
||||
self.target = target
|
||||
self.reposts = damus.boosts.counts[target.id] ?? 1
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center) {
|
||||
Image("repost")
|
||||
.foregroundColor(Color.gray)
|
||||
ProfileName(pubkey: pubkey, damus: damus, show_nip5_domain: false)
|
||||
.foregroundColor(Color.gray)
|
||||
Text("Reposted", comment: "Text indicating that the note was reposted (i.e. re-shared).")
|
||||
.foregroundColor(Color.gray)
|
||||
|
||||
// Show profile picture of the reposter only if the reposter is not the author of the reposted note.
|
||||
if pubkey != target.pubkey {
|
||||
ProfilePicView(pubkey: pubkey, size: eventview_pfp_size(.small), highlight: .none, profiles: damus.profiles, disable_animation: damus.settings.disable_animation)
|
||||
.onTapGesture {
|
||||
show_profile_action_sheet_if_enabled(damus_state: damus, pubkey: pubkey)
|
||||
}
|
||||
.onLongPressGesture(minimumDuration: 0.1) {
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
damus.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: Route.Reposts(reposts: .reposts(state: damus, target: target.id))) {
|
||||
Text(people_reposted_text(profiles: damus.profiles, pubkey: pubkey, reposts: reposts))
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.update_stats), perform: { note_id in
|
||||
guard note_id == target.id else { return }
|
||||
let repost_count = damus.boosts.counts[target.id]
|
||||
if let repost_count, reposts != repost_count {
|
||||
reposts = repost_count
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func people_reposted_text(profiles: Profiles, pubkey: Pubkey, reposts: Int, locale: Locale = Locale.current) -> String {
|
||||
guard reposts > 0 else {
|
||||
return ""
|
||||
}
|
||||
|
||||
let bundle = bundleForLocale(locale: locale)
|
||||
let other_reposts = reposts - 1
|
||||
let display_name = event_author_name(profiles: profiles, pubkey: pubkey)
|
||||
|
||||
if other_reposts == 0 {
|
||||
return String(format: NSLocalizedString("%@ reposted", bundle: bundle, comment: "Text indicating that the note was reposted (i.e. re-shared)."), locale: locale, display_name)
|
||||
} else {
|
||||
return String(format: localizedStringFormat(key: "people_reposted_count", locale: locale), locale: locale, other_reposts, display_name)
|
||||
}
|
||||
}
|
||||
|
||||
struct Reposted_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let test_state = test_damus_state
|
||||
Reposted(damus: test_state, pubkey: test_state.pubkey)
|
||||
Reposted(damus: test_state, pubkey: test_state.pubkey, target: test_note)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ struct SelectableText: View {
|
||||
})) {
|
||||
if let event, case .show_highlight_post_view(let highlighted_text) = self.selectedTextActionState {
|
||||
PostView(
|
||||
action: .highlighting(.init(selected_text: highlighted_text, source: .event(event))),
|
||||
action: .highlighting(.init(selected_text: highlighted_text, source: .event(event.id))),
|
||||
damus_state: damus_state
|
||||
)
|
||||
.presentationDragIndicator(.visible)
|
||||
|
||||
@@ -12,6 +12,14 @@ struct SupporterBadge: View {
|
||||
let purple_account: DamusPurple.Account?
|
||||
let style: Style
|
||||
let text_color: Color
|
||||
var badge_variant: BadgeVariant {
|
||||
if purple_account?.attributes.contains(.memberForMoreThanOneYear) == true {
|
||||
return .oneYearSpecial
|
||||
}
|
||||
else {
|
||||
return .normal
|
||||
}
|
||||
}
|
||||
|
||||
init(percent: Int?, purple_account: DamusPurple.Account? = nil, style: Style, text_color: Color = .secondary) {
|
||||
self.percent = percent
|
||||
@@ -26,13 +34,18 @@ struct SupporterBadge: View {
|
||||
HStack {
|
||||
if let purple_account, purple_account.active == true {
|
||||
HStack(spacing: 1) {
|
||||
Image("star.fill")
|
||||
.resizable()
|
||||
.frame(width:size, height:size)
|
||||
.foregroundStyle(GoldGradient)
|
||||
if self.style == .full {
|
||||
let date = format_date(date: purple_account.created_at, time_style: .none)
|
||||
Text(date)
|
||||
switch self.badge_variant {
|
||||
case .normal:
|
||||
StarShape()
|
||||
.frame(width:size, height:size)
|
||||
.foregroundStyle(GoldGradient)
|
||||
case .oneYearSpecial:
|
||||
DoubleStar(size: size)
|
||||
}
|
||||
|
||||
if self.style == .full,
|
||||
let ordinal = self.purple_account?.ordinal() {
|
||||
Text(ordinal)
|
||||
.foregroundStyle(text_color)
|
||||
.font(.caption)
|
||||
}
|
||||
@@ -56,8 +69,102 @@ struct SupporterBadge: View {
|
||||
case full // Shows the entire badge with a purple subscriber number if present
|
||||
case compact // Does not show purple subscriber number. Only shows the star (if applicable)
|
||||
}
|
||||
|
||||
enum BadgeVariant {
|
||||
/// A normal badge that people are used to
|
||||
case normal
|
||||
/// A special badge for users who have been members for more than a year
|
||||
case oneYearSpecial
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct StarShape: Shape {
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var path = Path()
|
||||
let center = CGPoint(x: rect.midX, y: rect.midY)
|
||||
let radius: CGFloat = min(rect.width, rect.height) / 2
|
||||
let points = 5
|
||||
let adjustment: CGFloat = .pi / 2
|
||||
|
||||
for i in 0..<points * 2 {
|
||||
let angle = (CGFloat(i) * .pi / CGFloat(points)) - adjustment
|
||||
let pointRadius = i % 2 == 0 ? radius : radius * 0.4
|
||||
let point = CGPoint(x: center.x + pointRadius * cos(angle), y: center.y + pointRadius * sin(angle))
|
||||
if i == 0 {
|
||||
path.move(to: point)
|
||||
} else {
|
||||
path.addLine(to: point)
|
||||
}
|
||||
}
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
struct DoubleStar: View {
|
||||
let size: CGFloat
|
||||
var starOffset: CGFloat = 5
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 17.0, *) {
|
||||
DoubleStarShape(starOffset: starOffset)
|
||||
.frame(width: size, height: size)
|
||||
.foregroundStyle(GoldGradient)
|
||||
.padding(.trailing, starOffset)
|
||||
} else {
|
||||
Fallback(size: size, starOffset: starOffset)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
struct DoubleStarShape: Shape {
|
||||
var strokeSize: CGFloat = 3
|
||||
var starOffset: CGFloat
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
let normalSizedStarPath = StarShape().path(in: rect)
|
||||
let largerStarPath = StarShape().path(in: rect.insetBy(dx: -strokeSize, dy: -strokeSize))
|
||||
|
||||
let finalPath = normalSizedStarPath
|
||||
.subtracting(
|
||||
largerStarPath.offsetBy(dx: starOffset, dy: 0)
|
||||
)
|
||||
.union(
|
||||
normalSizedStarPath.offsetBy(dx: starOffset, dy: 0)
|
||||
)
|
||||
|
||||
return finalPath
|
||||
}
|
||||
}
|
||||
|
||||
/// A fallback view for those who cannot run iOS 17
|
||||
struct Fallback: View {
|
||||
var size: CGFloat
|
||||
var starOffset: CGFloat
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
StarShape()
|
||||
.frame(width: size, height: size)
|
||||
.foregroundStyle(GoldGradient)
|
||||
|
||||
StarShape()
|
||||
.fill(GoldGradient)
|
||||
.overlay(
|
||||
StarShape()
|
||||
.stroke(Color.damusAdaptableWhite, lineWidth: 1)
|
||||
)
|
||||
.frame(width: size + 1, height: size + 1)
|
||||
.padding(.leading, -size - starOffset)
|
||||
}
|
||||
.padding(.trailing, -3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func support_level_color(_ percent: Int) -> Color {
|
||||
if percent == 0 {
|
||||
return .gray
|
||||
@@ -86,7 +193,7 @@ struct SupporterBadge_Previews: PreviewProvider {
|
||||
HStack(alignment: .center) {
|
||||
SupporterBadge(
|
||||
percent: nil,
|
||||
purple_account: DamusPurple.Account(pubkey: test_pubkey, created_at: .now, expiry: .now.addingTimeInterval(10000), subscriber_number: subscriber_number, active: true),
|
||||
purple_account: DamusPurple.Account(pubkey: test_pubkey, created_at: .now, expiry: .now.addingTimeInterval(10000), subscriber_number: subscriber_number, active: true, attributes: []),
|
||||
style: .full
|
||||
)
|
||||
.frame(width: 100)
|
||||
@@ -118,4 +225,52 @@ struct SupporterBadge_Previews: PreviewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("1 yr badge") {
|
||||
VStack {
|
||||
HStack(alignment: .center) {
|
||||
SupporterBadge(
|
||||
percent: nil,
|
||||
purple_account: DamusPurple.Account(pubkey: test_pubkey, created_at: .now, expiry: .now.addingTimeInterval(10000), subscriber_number: 3, active: true, attributes: []),
|
||||
style: .full
|
||||
)
|
||||
.frame(width: 100)
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
SupporterBadge(
|
||||
percent: nil,
|
||||
purple_account: DamusPurple.Account(pubkey: test_pubkey, created_at: .now, expiry: .now.addingTimeInterval(10000), subscriber_number: 3, active: true, attributes: [.memberForMoreThanOneYear]),
|
||||
style: .full
|
||||
)
|
||||
.frame(width: 100)
|
||||
}
|
||||
|
||||
Text(verbatim: "Double star (just shape itself, with alt background color, to show it adapts to background well)")
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
if #available(iOS 17.0, *) {
|
||||
HStack(alignment: .center) {
|
||||
DoubleStar.DoubleStarShape(starOffset: 5)
|
||||
.frame(width: 17, height: 17)
|
||||
.padding(.trailing, -8)
|
||||
}
|
||||
.background(Color.blue)
|
||||
}
|
||||
|
||||
Text(verbatim: "Double star (fallback for iOS 16 and below)")
|
||||
|
||||
HStack(alignment: .center) {
|
||||
DoubleStar.Fallback(size: 17, starOffset: 5)
|
||||
}
|
||||
|
||||
Text(verbatim: "Double star (fallback for iOS 16 and below, with alt color limitation shown)")
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
HStack(alignment: .center) {
|
||||
DoubleStar.Fallback(size: 17, starOffset: 5)
|
||||
}
|
||||
.background(Color.blue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ enum Sheets: Identifiable {
|
||||
case onboardingSuggestions
|
||||
case purple(DamusPurpleURL)
|
||||
case purple_onboarding
|
||||
|
||||
case error(ErrorView.UserPresentableError)
|
||||
|
||||
static func zap(target: ZapTarget, lnurl: String) -> Sheets {
|
||||
return .zap(ZapSheet(target: target, lnurl: lnurl))
|
||||
}
|
||||
@@ -53,6 +54,7 @@ enum Sheets: Identifiable {
|
||||
case .onboardingSuggestions: return "onboarding-suggestions"
|
||||
case .purple(let purple_url): return "purple" + purple_url.url_string()
|
||||
case .purple_onboarding: return "purple_onboarding"
|
||||
case .error(_): return "error"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,12 +222,6 @@ struct ContentView: View {
|
||||
navigationCoordinator.push(route: Route.Script(script: model))
|
||||
}
|
||||
|
||||
func open_profile(pubkey: Pubkey) {
|
||||
let profile_model = ProfileModel(pubkey: pubkey, damus: damus_state!)
|
||||
let followers = FollowersModel(damus_state: damus_state!, target: pubkey)
|
||||
navigationCoordinator.push(route: Route.Profile(profile: profile_model, followers: followers))
|
||||
}
|
||||
|
||||
func open_search(filt: NostrFilter) {
|
||||
let search = SearchModel(state: damus_state!, search: filt)
|
||||
navigationCoordinator.push(route: Route.Search(search: search))
|
||||
@@ -310,6 +306,9 @@ struct ContentView: View {
|
||||
hasSeenOnboardingSuggestions = true
|
||||
}
|
||||
self.appDelegate?.state = damus_state
|
||||
Task { // We probably don't need this to be a detached task. According to https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Defining-and-Calling-Asynchronous-Functions, awaits are only suspension points that do not block the thread.
|
||||
await self.listenAndHandleLocalNotifications()
|
||||
}
|
||||
}
|
||||
.sheet(item: $active_sheet) { item in
|
||||
switch item {
|
||||
@@ -339,36 +338,14 @@ struct ContentView: View {
|
||||
DamusPurpleURLSheetView(damus_state: damus_state!, purple_url: purple_url)
|
||||
case .purple_onboarding:
|
||||
DamusPurpleNewUserOnboardingView(damus_state: damus_state)
|
||||
case .error(let error):
|
||||
ErrorView(damus_state: damus_state!, error: error)
|
||||
}
|
||||
}
|
||||
.onOpenURL { url in
|
||||
on_open_url(state: damus_state!, url: url) { res in
|
||||
guard let res else {
|
||||
return
|
||||
}
|
||||
|
||||
switch res {
|
||||
case .filter(let filt): self.open_search(filt: filt)
|
||||
case .profile(let pk): self.open_profile(pubkey: pk)
|
||||
case .event(let ev): self.open_event(ev: ev)
|
||||
case .wallet_connect(let nwc): self.open_wallet(nwc: nwc)
|
||||
case .script(let data): self.open_script(data)
|
||||
case .purple(let purple_url):
|
||||
if case let .welcome(checkout_id) = purple_url.variant {
|
||||
// If this is a welcome link, do the following before showing the onboarding screen:
|
||||
// 1. Check if this is legitimate and good to go.
|
||||
// 2. Mark as complete if this is good to go.
|
||||
Task {
|
||||
let is_good_to_go = try? await damus_state.purple.check_and_mark_ln_checkout_is_good_to_go(checkout_id: checkout_id)
|
||||
if is_good_to_go == true {
|
||||
self.active_sheet = .purple(purple_url)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
self.active_sheet = .purple(purple_url)
|
||||
}
|
||||
}
|
||||
Task {
|
||||
let open_action = await DamusURLHandler.handle_opening_url_and_compute_view_action(damus_state: self.damus_state, url: url)
|
||||
self.execute_open_action(open_action)
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.compose)) { action in
|
||||
@@ -390,6 +367,8 @@ struct ContentView: View {
|
||||
self.confirm_mute = true
|
||||
}
|
||||
.onReceive(handle_notify(.attached_wallet)) { nwc in
|
||||
try? damus_state.pool.add_relay(.nwc(url: nwc.relay))
|
||||
|
||||
// update the lightning address on our profile when we attach a
|
||||
// wallet with an associated
|
||||
guard let ds = self.damus_state,
|
||||
@@ -531,27 +510,6 @@ struct ContentView: View {
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.local_notification)) { local in
|
||||
guard let damus_state else { return }
|
||||
|
||||
switch local.mention {
|
||||
case .pubkey(let pubkey):
|
||||
open_profile(pubkey: pubkey)
|
||||
|
||||
case .note(let noteId):
|
||||
openEvent(noteId: noteId, notificationType: local.type)
|
||||
case .nevent(let nevent):
|
||||
openEvent(noteId: nevent.noteid, notificationType: local.type)
|
||||
case .nprofile(let nprofile):
|
||||
open_profile(pubkey: nprofile.author)
|
||||
case .nrelay(_):
|
||||
break
|
||||
case .naddr(let naddr):
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
.onReceive(handle_notify(.onlyzaps_mode)) { hide in
|
||||
home.filter_events()
|
||||
@@ -608,7 +566,7 @@ struct ContentView: View {
|
||||
}, message: {
|
||||
Text("No mute list found, create a new one? This will overwrite any previous mute lists.", comment: "Alert message prompt that asks if the user wants to create a new mute list, overwriting previous mute lists.")
|
||||
})
|
||||
.alert(NSLocalizedString("Mute User", comment: "Title of alert for muting a user."), isPresented: $confirm_mute, actions: {
|
||||
.alert(NSLocalizedString("Mute/Block User", comment: "Title of alert for muting/blocking a user."), isPresented: $confirm_mute, actions: {
|
||||
Button(NSLocalizedString("Cancel", comment: "Alert button to cancel out of alert for muting a user."), role: .cancel) {
|
||||
confirm_mute = false
|
||||
}
|
||||
@@ -661,6 +619,28 @@ struct ContentView: View {
|
||||
self.selected_timeline = timeline
|
||||
}
|
||||
|
||||
/// Listens to requests to open a push/local user notification
|
||||
///
|
||||
/// This function never returns, it just keeps streaming
|
||||
func listenAndHandleLocalNotifications() async {
|
||||
for await notification in await QueueableNotify<LossyLocalNotification>.shared.stream {
|
||||
self.handleNotification(notification: notification)
|
||||
}
|
||||
}
|
||||
|
||||
func handleNotification(notification: LossyLocalNotification) {
|
||||
Log.info("ContentView is handling a notification", for: .push_notifications)
|
||||
guard let damus_state else {
|
||||
// This should never happen because `listenAndHandleLocalNotifications` is called after damus state is initialized in `onAppear`
|
||||
assertionFailure("DamusState not loaded when ContentView (new handler) was handling a notification")
|
||||
Log.error("DamusState not loaded when ContentView (new handler) was handling a notification", for: .push_notifications)
|
||||
return
|
||||
}
|
||||
let local = notification
|
||||
let openAction = local.toViewOpenAction()
|
||||
self.execute_open_action(openAction)
|
||||
}
|
||||
|
||||
func connect() {
|
||||
// nostrdb
|
||||
var mndb = Ndb()
|
||||
@@ -766,22 +746,38 @@ struct ContentView: View {
|
||||
damus_state.postbox.send(ev)
|
||||
}
|
||||
}
|
||||
|
||||
private func openEvent(noteId: NoteId, notificationType: LocalNotificationType) {
|
||||
guard let target = damus_state.events.lookup(noteId) else {
|
||||
|
||||
/// An open action within the app
|
||||
/// This is used to model, store, and communicate a desired view action to be taken as a result of opening an object,
|
||||
/// for example a URL
|
||||
///
|
||||
/// ## Implementation notes
|
||||
///
|
||||
/// - The reason this was created was to separate URL parsing logic, the underlying actions that mutate the state of the app, and the action to be taken on the view layer as a result. This makes it easier to test, to read the URL handling code, and to add new functionality in between the two (e.g. a confirmation screen before proceeding with a given open action)
|
||||
enum ViewOpenAction {
|
||||
/// Open a page route
|
||||
case route(Route)
|
||||
/// Open a sheet
|
||||
case sheet(Sheets)
|
||||
/// Do nothing.
|
||||
///
|
||||
/// ## Implementation notes
|
||||
/// - This is used here instead of Optional values to make semantics explicit and force better programming intent, instead of accidentally doing nothing because of Swift's syntax sugar.
|
||||
case no_action
|
||||
}
|
||||
|
||||
/// Executes an action to open something in the app view
|
||||
///
|
||||
/// - Parameter open_action: The action to perform
|
||||
func execute_open_action(_ open_action: ViewOpenAction) {
|
||||
switch open_action {
|
||||
case .route(let route):
|
||||
navigationCoordinator.push(route: route)
|
||||
case .sheet(let sheet):
|
||||
self.active_sheet = sheet
|
||||
case .no_action:
|
||||
return
|
||||
}
|
||||
|
||||
switch notificationType {
|
||||
case .dm:
|
||||
selected_timeline = .dms
|
||||
damus_state.dms.set_active_dm(target.pubkey)
|
||||
navigationCoordinator.push(route: Route.DMChat(dms: damus_state.dms.active_model))
|
||||
case .like, .zap, .mention, .repost, .reply, .tagged:
|
||||
open_event(ev: target)
|
||||
case .profile_zap:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -931,14 +927,41 @@ enum FindEventType {
|
||||
|
||||
enum FoundEvent {
|
||||
case profile(Pubkey)
|
||||
case invalid_profile(NostrEvent)
|
||||
case event(NostrEvent)
|
||||
}
|
||||
|
||||
/// Finds an event from NostrDB if it exists, or from the network
|
||||
///
|
||||
/// This is the callback version. There is also an asyc/await version of this function.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - state: Damus state
|
||||
/// - query_: The query, including the event being looked for, and the relays to use when looking
|
||||
/// - callback: The function to call with results
|
||||
func find_event(state: DamusState, query query_: FindEvent, callback: @escaping (FoundEvent?) -> ()) {
|
||||
return find_event_with_subid(state: state, query: query_, subid: UUID().description, callback: callback)
|
||||
}
|
||||
|
||||
/// Finds an event from NostrDB if it exists, or from the network
|
||||
///
|
||||
/// This is a the async/await version of `find_event`. Use this when using callbacks is impossible or cumbersome.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - state: Damus state
|
||||
/// - query_: The query, including the event being looked for, and the relays to use when looking
|
||||
/// - callback: The function to call with results
|
||||
func find_event(state: DamusState, query query_: FindEvent) async -> FoundEvent? {
|
||||
await withCheckedContinuation { continuation in
|
||||
find_event(state: state, query: query_) { event in
|
||||
var already_resumed = false
|
||||
if !already_resumed { // Ensure we do not resume twice, as it causes a crash
|
||||
continuation.resume(returning: event)
|
||||
already_resumed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: String, callback: @escaping (FoundEvent?) -> ()) {
|
||||
|
||||
var filter: NostrFilter? = nil
|
||||
@@ -988,10 +1011,6 @@ func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: St
|
||||
switch query {
|
||||
case .profile:
|
||||
if ev.known_kind == .metadata {
|
||||
guard state.ndb.lookup_profile_key(ev.pubkey) != nil else {
|
||||
callback(.invalid_profile(ev))
|
||||
return
|
||||
}
|
||||
callback(.profile(ev.pubkey))
|
||||
}
|
||||
case .event:
|
||||
@@ -1000,20 +1019,28 @@ func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: St
|
||||
case .eose:
|
||||
if !has_event {
|
||||
attempts += 1
|
||||
if attempts == state.pool.our_descriptors.count / 2 {
|
||||
callback(nil)
|
||||
if attempts >= state.pool.our_descriptors.count {
|
||||
callback(nil) // If we could not find any events in any of the relays we are connected to, send back nil
|
||||
}
|
||||
state.pool.unsubscribe(sub_id: subid, to: [relay_id])
|
||||
}
|
||||
state.pool.unsubscribe(sub_id: subid, to: [relay_id]) // We are only finding an event once, so close subscription on eose
|
||||
case .notice:
|
||||
break
|
||||
case .auth:
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Finds a replaceable event based on an `naddr` address.
|
||||
///
|
||||
/// This is the callback version of the function. There is another function that makes use of async/await
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - damus_state: The Damus state
|
||||
/// - naddr: the `naddr` address
|
||||
/// - callback: A function to handle the found event
|
||||
func naddrLookup(damus_state: DamusState, naddr: NAddr, callback: @escaping (NostrEvent?) -> ()) {
|
||||
var nostrKinds: [NostrKind]? = NostrKind(rawValue: naddr.kind).map { [$0] }
|
||||
|
||||
@@ -1042,6 +1069,26 @@ func naddrLookup(damus_state: DamusState, naddr: NAddr, callback: @escaping (Nos
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds a replaceable event based on an `naddr` address.
|
||||
///
|
||||
/// This is the async/await version of the function. Another version of this function which makes use of callback functions also exists .
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - damus_state: The Damus state
|
||||
/// - naddr: the `naddr` address
|
||||
/// - callback: A function to handle the found event
|
||||
func naddrLookup(damus_state: DamusState, naddr: NAddr) async -> NostrEvent? {
|
||||
await withCheckedContinuation { continuation in
|
||||
var already_resumed = false
|
||||
naddrLookup(damus_state: damus_state, naddr: naddr) { event in
|
||||
if !already_resumed { // Ensure we do not resume twice, as it causes a crash
|
||||
continuation.resume(returning: event)
|
||||
already_resumed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func timeline_name(_ timeline: Timeline?) -> String {
|
||||
guard let timeline else {
|
||||
return ""
|
||||
@@ -1152,60 +1199,32 @@ func handle_post_notification(keypair: FullKeypair, postbox: PostBox, events: Ev
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum OpenResult {
|
||||
case profile(Pubkey)
|
||||
case filter(NostrFilter)
|
||||
case event(NostrEvent)
|
||||
case wallet_connect(WalletConnectURL)
|
||||
case script([UInt8])
|
||||
case purple(DamusPurpleURL)
|
||||
}
|
||||
|
||||
func on_open_url(state: DamusState, url: URL, result: @escaping (OpenResult?) -> Void) {
|
||||
if let purple_url = DamusPurpleURL(url: url) {
|
||||
result(.purple(purple_url))
|
||||
return
|
||||
}
|
||||
|
||||
if let nwc = WalletConnectURL(str: url.absoluteString) {
|
||||
result(.wallet_connect(nwc))
|
||||
return
|
||||
}
|
||||
|
||||
guard let link = decode_nostr_uri(url.absoluteString) else {
|
||||
result(nil)
|
||||
return
|
||||
}
|
||||
|
||||
switch link {
|
||||
case .ref(let ref):
|
||||
switch ref {
|
||||
case .pubkey(let pk):
|
||||
result(.profile(pk))
|
||||
case .event(let noteid):
|
||||
find_event(state: state, query: .event(evid: noteid)) { res in
|
||||
guard let res, case .event(let ev) = res else { return }
|
||||
result(.event(ev))
|
||||
}
|
||||
case .hashtag(let ht):
|
||||
result(.filter(.filter_hashtag([ht.hashtag])))
|
||||
case .param, .quote, .reference:
|
||||
// doesn't really make sense here
|
||||
break
|
||||
case .naddr(let naddr):
|
||||
naddrLookup(damus_state: state, naddr: naddr) { res in
|
||||
guard let res = res else { return }
|
||||
result(.event(res))
|
||||
}
|
||||
extension LossyLocalNotification {
|
||||
/// Computes a view open action from a mention reference.
|
||||
/// Use this when opening a user-presentable interface to a specific mention reference.
|
||||
func toViewOpenAction() -> ContentView.ViewOpenAction {
|
||||
switch self.mention {
|
||||
case .pubkey(let pubkey):
|
||||
return .route(.ProfileByKey(pubkey: pubkey))
|
||||
case .note(let noteId):
|
||||
return .route(.LoadableNostrEvent(note_reference: .note_id(noteId)))
|
||||
case .nevent(let nEvent):
|
||||
// TODO: Improve this by implementing a route that handles nevents with their relay hints.
|
||||
return .route(.LoadableNostrEvent(note_reference: .note_id(nEvent.noteid)))
|
||||
case .nprofile(let nProfile):
|
||||
// TODO: Improve this by implementing a profile route that handles nprofiles with their relay hints.
|
||||
return .route(.ProfileByKey(pubkey: nProfile.author))
|
||||
case .nrelay(let string):
|
||||
// We do not need to implement `nrelay` support, it has been deprecated.
|
||||
// See https://github.com/nostr-protocol/nips/blob/6e7a618e7f873bb91e743caacc3b09edab7796a0/BREAKING.md?plain=1#L21
|
||||
return .sheet(.error(ErrorView.UserPresentableError(
|
||||
user_visible_description: NSLocalizedString("You opened an invalid link. The link you tried to open refers to \"nrelay\", which has been deprecated and is not supported.", comment: "User-visible error description for a user who tries to open a deprecated \"nrelay\" link."),
|
||||
tip: NSLocalizedString("Please contact the person who provided the link, and ask for another link.", comment: "User-visible tip on what to do if a link contains a deprecated \"nrelay\" reference."),
|
||||
technical_info: "`MentionRef.toViewOpenAction` detected deprecated `nrelay` contents"
|
||||
)))
|
||||
case .naddr(let nAddr):
|
||||
return .route(.LoadableNostrEvent(note_reference: .naddr(nAddr)))
|
||||
}
|
||||
case .filter(let filt):
|
||||
result(.filter(filt))
|
||||
break
|
||||
// TODO: handle filter searches?
|
||||
case .script(let script):
|
||||
result(.script(script))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>river</string>
|
||||
<string>alby</string>
|
||||
<string>albygo</string>
|
||||
<string>bitcoinbeach</string>
|
||||
<string>breez</string>
|
||||
<string>muun</string>
|
||||
@@ -71,6 +73,6 @@
|
||||
<key>NSAppleMusicUsageDescription</key>
|
||||
<string>Damus needs access to your media library for playback statuses</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Damus needs access to your microphone for creating video recording posts</string>
|
||||
<string>Damus needs access to your microphone to allow you to create video recordings that you can choose to post publicly on the network</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -175,6 +175,9 @@ class DamusState: HeadlessDamusState {
|
||||
|
||||
func close() {
|
||||
print("txn: damus close")
|
||||
Task {
|
||||
try await self.push_notification_client.revoke_token()
|
||||
}
|
||||
wallet.disconnect()
|
||||
pool.close()
|
||||
ndb.close()
|
||||
|
||||
@@ -6,14 +6,45 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUICore
|
||||
import UIKit
|
||||
|
||||
/// Represents artifacts in a post draft, which is rendered by `PostView`
|
||||
///
|
||||
/// ## Implementation notes
|
||||
///
|
||||
/// - This is NOT `Codable` because we store these persistently as NIP-37 drafts in NostrDB, instead of directly encoding the object.
|
||||
/// - `NSMutableAttributedString` is the bottleneck for making this `Codable`, and replacing that with another type requires a very large refactor.
|
||||
/// - Encoding/decoding logic is lossy, and is not fully round-trippable. This class does a best effort attempt at encoding and recovering as much information as possible, but the information is dispersed into many different places, types, and functions around the code, making round-trip guarantees very difficult without severely refactoring `PostView`, `TextViewWrapper`, and other associated classes, unfortunately. These are the known limitations at the moment:
|
||||
/// - Image metadata is lost on decoding
|
||||
/// - The `filtered_pubkeys` filter effectively gets applied upon encoding, causing them to change upon decoding
|
||||
///
|
||||
class DraftArtifacts: Equatable {
|
||||
/// The text content of the note draft
|
||||
///
|
||||
/// ## Implementation notes
|
||||
///
|
||||
/// - This serves as the backing model for `PostView` and `TextViewWrapper`. It might be cleaner to use a specialized data model for this in the future and render to attributed string in real time, but that will require a big refactor. See https://github.com/damus-io/damus/issues/1862#issuecomment-2585756932
|
||||
var content: NSMutableAttributedString
|
||||
/// A list of media items that have been attached to the note draft.
|
||||
var media: [UploadedMedia]
|
||||
/// The references for this note, which will be translated into tags once the event is published.
|
||||
var references: [RefId]
|
||||
/// Pubkeys that should be filtered out from the references
|
||||
///
|
||||
/// For example, when replying to an event, the user can select which pubkey mentions they want to keep, and which ones to remove.
|
||||
var filtered_pubkeys: Set<Pubkey> = []
|
||||
|
||||
init(content: NSMutableAttributedString = NSMutableAttributedString(string: ""), media: [UploadedMedia] = []) {
|
||||
/// A unique ID for this draft that allows us to address these if we need to.
|
||||
///
|
||||
/// This will be the unique identifier in the NIP-37 note
|
||||
let id: String
|
||||
|
||||
init(content: NSMutableAttributedString = NSMutableAttributedString(string: ""), media: [UploadedMedia] = [], references: [RefId], id: String) {
|
||||
self.content = content
|
||||
self.media = media
|
||||
self.references = references
|
||||
self.id = id
|
||||
}
|
||||
|
||||
static func == (lhs: DraftArtifacts, rhs: DraftArtifacts) -> Bool {
|
||||
@@ -22,11 +53,217 @@ class DraftArtifacts: Equatable {
|
||||
lhs.content.string == rhs.content.string // Comparing the text content is not perfect but acceptable in this case because attributes for our post editor are determined purely from text content
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Encoding and decoding functions to and from NIP-37 nostr events
|
||||
|
||||
/// Converts the draft artifacts into a NIP-37 draft event that can be saved into NostrDB or any Nostr relay
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - action: The post action for this draft, which provides necessary context for the draft (e.g. Is it meant to highlight something? Reply to something?)
|
||||
/// - damus_state: The damus state, needed for encrypting, fetching Nostr data depedencies, and forming the NIP-37 draft
|
||||
/// - references: references in the post?
|
||||
/// - Returns: The NIP-37 draft packaged in a way that can be easily wrapped/unwrapped.
|
||||
func to_nip37_draft(action: PostAction, damus_state: DamusState) throws -> NIP37Draft? {
|
||||
guard let keypair = damus_state.keypair.to_full() else { return nil }
|
||||
let post = build_post(state: damus_state, action: action, draft: self)
|
||||
guard let note = post.to_event(keypair: keypair) else { return nil }
|
||||
return try NIP37Draft(unwrapped_note: note, draft_id: self.id, keypair: keypair)
|
||||
}
|
||||
|
||||
/// Instantiates a draft object from a NIP-37 draft
|
||||
/// - Parameters:
|
||||
/// - nip37_draft: The NIP-37 draft object
|
||||
/// - damus_state: Damus state of the user who wants to load this draft object. Needed for pulling profiles from Ndb, and decrypting contents.
|
||||
/// - Returns: A draft artifacts object, or `nil` if such cannot be loaded.
|
||||
static func from(nip37_draft: NIP37Draft, damus_state: DamusState) -> DraftArtifacts? {
|
||||
return Self.from(
|
||||
event: nip37_draft.unwrapped_note,
|
||||
draft_id: nip37_draft.id ?? UUID().uuidString, // Generate random UUID as the draft ID if none is specified. It is always better to have an ID that we can use for addressing later.
|
||||
damus_state: damus_state
|
||||
)
|
||||
}
|
||||
|
||||
/// Load a draft artifacts object from a plain, unwrapped NostrEvent
|
||||
///
|
||||
/// This function will parse the contents of a Nostr Event and turn it into an editable draft that we can use.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - event: The Nostr event to use as a template
|
||||
/// - draft_id: The unique ID of this draft, used for keeping draft identities stable. UUIDs are recommended but not required.
|
||||
/// - damus_state: The user's Damus state, used for fetching profiles in NostrDB
|
||||
/// - Returns: The draft that can be loaded into `PostView`.
|
||||
static func from(event: NostrEvent, draft_id: String, damus_state: DamusState) -> DraftArtifacts {
|
||||
let parsed_blocks = parse_note_content(content: .init(note: event, keypair: damus_state.keypair))
|
||||
return Self.from(parsed_blocks: parsed_blocks, references: Array(event.references), draft_id: draft_id, damus_state: damus_state)
|
||||
}
|
||||
|
||||
/// Load a draft artifacts object from parsed Nostr event blocks
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - parsed_blocks: The blocks parsed from a Nostr event
|
||||
/// - references: The references in the Nostr event
|
||||
/// - draft_id: The unique ID of the draft as per NIP-37
|
||||
/// - damus_state: Damus state, used for fetching profile info in NostrDB
|
||||
/// - Returns: The draft that can be loaded into `PostView`.
|
||||
static func from(parsed_blocks: Blocks, references: [RefId], draft_id: String, damus_state: DamusState) -> DraftArtifacts {
|
||||
let rich_text_content: NSMutableAttributedString = .init(string: "")
|
||||
var media: [UploadedMedia] = []
|
||||
for block in parsed_blocks.blocks {
|
||||
switch block {
|
||||
case .mention(let mention):
|
||||
if case .pubkey(let pubkey) = mention.ref {
|
||||
// A profile reference, format things properly.
|
||||
let profile = damus_state.ndb.lookup_profile(pubkey)?.unsafeUnownedValue?.profile
|
||||
let profile_name = parse_display_name(profile: profile, pubkey: pubkey).username
|
||||
guard let url_address = URL(string: block.asString) else {
|
||||
rich_text_content.append(.init(string: block.asString))
|
||||
continue
|
||||
}
|
||||
let attributed_string = NSMutableAttributedString(
|
||||
string: "@\(profile_name)",
|
||||
attributes: [
|
||||
.link: url_address,
|
||||
.foregroundColor: UIColor(Color.accentColor)
|
||||
]
|
||||
)
|
||||
rich_text_content.append(attributed_string)
|
||||
}
|
||||
else if case .note(_) = mention.ref {
|
||||
// These note references occur when we quote a note, and since that is tracked via `PostAction` in `PostView`, ignore it here to avoid attaching the same event twice in a note
|
||||
continue
|
||||
}
|
||||
else {
|
||||
// Other references
|
||||
rich_text_content.append(.init(string: block.asString))
|
||||
}
|
||||
case .url(let url):
|
||||
if isSupportedImage(url: url) {
|
||||
// Image, add that to our media attachments
|
||||
// TODO: Add metadata decoding support
|
||||
media.append(UploadedMedia(localURL: url, uploadedURL: url, metadata: .none))
|
||||
continue
|
||||
}
|
||||
else {
|
||||
// Normal URL, plain text
|
||||
rich_text_content.append(.init(string: block.asString))
|
||||
}
|
||||
case .invoice(_), .relay(_), .hashtag(_), .text(_):
|
||||
// Everything else is currently plain text.
|
||||
rich_text_content.append(.init(string: block.asString))
|
||||
}
|
||||
}
|
||||
return DraftArtifacts(content: rich_text_content, media: media, references: references, id: draft_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Holds and keeps track of the note post drafts throughout the app.
|
||||
class Drafts: ObservableObject {
|
||||
@Published var post: DraftArtifacts? = nil
|
||||
@Published var replies: [NostrEvent: DraftArtifacts] = [:]
|
||||
@Published var quotes: [NostrEvent: DraftArtifacts] = [:]
|
||||
@Published var highlights: [HighlightSource: DraftArtifacts] = [:]
|
||||
@Published var replies: [NoteId: DraftArtifacts] = [:]
|
||||
@Published var quotes: [NoteId: DraftArtifacts] = [:]
|
||||
/// The drafts we have for highlights
|
||||
///
|
||||
/// ## Implementation notes
|
||||
/// - Although in practice we also load drafts based on the highlight source for better UX (making it easier to find a draft), we need the keys to be of type `HighlightContentDraft` because we need the selected text information to be able to construct the NIP-37 draft, as well as to load that into post view.
|
||||
@Published var highlights: [HighlightContentDraft: DraftArtifacts] = [:]
|
||||
|
||||
/// Loads drafts from storage (NostrDB + UserDefaults)
|
||||
func load(from damus_state: DamusState) {
|
||||
guard let note_ids = damus_state.settings.draft_event_ids?.compactMap({ NoteId(hex: $0) }) else { return }
|
||||
for note_id in note_ids {
|
||||
let txn = damus_state.ndb.lookup_note(note_id)
|
||||
guard let note = txn?.unsafeUnownedValue else { continue }
|
||||
// Implementation note: This currently fails silently, because:
|
||||
// 1. Errors are unlikely and not expected
|
||||
// 2. It is not mission critical to recover from this error
|
||||
// 3. The changes that add a error view sheet with useful info is not yet merged in as of writing.
|
||||
try? self.load(wrapped_draft_note: note, with: damus_state)
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a specific NIP-37 note into this class
|
||||
func load(wrapped_draft_note: NdbNote, with damus_state: DamusState) throws {
|
||||
// Extract draft info from the NIP-37 note
|
||||
guard let full_keypair = damus_state.keypair.to_full() else { return }
|
||||
guard let nip37_draft = try NIP37Draft(wrapped_note: wrapped_draft_note, keypair: full_keypair) else { return }
|
||||
guard let known_kind = nip37_draft.unwrapped_note.known_kind else { return }
|
||||
guard let draft_artifacts = DraftArtifacts.from(
|
||||
nip37_draft: nip37_draft,
|
||||
damus_state: damus_state
|
||||
) else { return }
|
||||
|
||||
// Find out where to place these drafts
|
||||
let blocks = parse_note_content(content: .note(nip37_draft.unwrapped_note))
|
||||
switch known_kind {
|
||||
case .text:
|
||||
if let replied_to_note_id = nip37_draft.unwrapped_note.direct_replies() {
|
||||
self.replies[replied_to_note_id] = draft_artifacts
|
||||
}
|
||||
else {
|
||||
for block in blocks.blocks {
|
||||
if case .mention(let mention) = block {
|
||||
if case .note(let note_id) = mention.ref {
|
||||
self.quotes[note_id] = draft_artifacts
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
self.post = draft_artifacts
|
||||
}
|
||||
case .highlight:
|
||||
guard let highlight = HighlightContentDraft(from: nip37_draft.unwrapped_note) else { return }
|
||||
self.highlights[highlight] = draft_artifacts
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/// Saves the drafts tracked by this class persistently using NostrDB + UserDefaults
|
||||
func save(damus_state: DamusState) {
|
||||
var draft_events: [NdbNote] = []
|
||||
post_artifact_block: if let post_artifacts = self.post {
|
||||
let nip37_draft = try? post_artifacts.to_nip37_draft(action: .posting(.user(damus_state.pubkey)), damus_state: damus_state)
|
||||
guard let wrapped_note = nip37_draft?.wrapped_note else { break post_artifact_block }
|
||||
draft_events.append(wrapped_note)
|
||||
}
|
||||
for (replied_to_note_id, reply_artifacts) in self.replies {
|
||||
guard let replied_to_note = damus_state.ndb.lookup_note(replied_to_note_id)?.unsafeUnownedValue?.to_owned() else { continue }
|
||||
let nip37_draft = try? reply_artifacts.to_nip37_draft(action: .replying_to(replied_to_note), damus_state: damus_state)
|
||||
guard let wrapped_note = nip37_draft?.wrapped_note else { continue }
|
||||
draft_events.append(wrapped_note)
|
||||
}
|
||||
for (quoted_note_id, quote_note_artifacts) in self.quotes {
|
||||
guard let quoted_note = damus_state.ndb.lookup_note(quoted_note_id)?.unsafeUnownedValue?.to_owned() else { continue }
|
||||
let nip37_draft = try? quote_note_artifacts.to_nip37_draft(action: .quoting(quoted_note), damus_state: damus_state)
|
||||
guard let wrapped_note = nip37_draft?.wrapped_note else { continue }
|
||||
draft_events.append(wrapped_note)
|
||||
}
|
||||
for (highlight, highlight_note_artifacts) in self.highlights {
|
||||
let nip37_draft = try? highlight_note_artifacts.to_nip37_draft(action: .highlighting(highlight), damus_state: damus_state)
|
||||
guard let wrapped_note = nip37_draft?.wrapped_note else { continue }
|
||||
draft_events.append(wrapped_note)
|
||||
}
|
||||
|
||||
for draft_event in draft_events {
|
||||
// Implementation note: We do not support draft synchronization with relays yet.
|
||||
// TODO: Once it is time to implement draft syncing with relays, please consider the following:
|
||||
// - Privacy: Sending drafts to the network leaks metadata about app activity, and may break user expectations
|
||||
// - Down-sync conflict resolution: Consider how to solve conflicts for different draft versions holding the same ID (e.g. edited in Damus, then another client, then Damus again)
|
||||
damus_state.pool.send_raw_to_local_ndb(.typical(.event(draft_event)))
|
||||
}
|
||||
|
||||
damus_state.settings.draft_event_ids = draft_events.map({ $0.id.hex() })
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience extensions
|
||||
|
||||
fileprivate extension Array {
|
||||
mutating func appendIfNotNil(_ element: Element?) {
|
||||
if let element = element {
|
||||
self.append(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,17 +188,29 @@ extension HighlightEvent {
|
||||
struct HighlightContentDraft: Hashable {
|
||||
let selected_text: String
|
||||
let source: HighlightSource
|
||||
|
||||
|
||||
init(selected_text: String, source: HighlightSource) {
|
||||
self.selected_text = selected_text
|
||||
self.source = source
|
||||
}
|
||||
|
||||
init?(from note: NdbNote) {
|
||||
guard let source = HighlightSource.from(tags: note.tags.strings()) else { return nil }
|
||||
self.source = source
|
||||
self.selected_text = note.content
|
||||
}
|
||||
}
|
||||
|
||||
enum HighlightSource: Hashable {
|
||||
static let TAG_SOURCE_ELEMENT = "source"
|
||||
case event(NostrEvent)
|
||||
case event(NoteId)
|
||||
case external_url(URL)
|
||||
|
||||
func tags() -> [[String]] {
|
||||
switch self {
|
||||
case .event(let event):
|
||||
return [ ["e", "\(event.id)", HighlightSource.TAG_SOURCE_ELEMENT] ]
|
||||
case .event(let event_id):
|
||||
return [ ["e", "\(event_id)", HighlightSource.TAG_SOURCE_ELEMENT] ]
|
||||
case .external_url(let url):
|
||||
return [ ["r", "\(url)", HighlightSource.TAG_SOURCE_ELEMENT] ]
|
||||
}
|
||||
@@ -206,12 +218,26 @@ enum HighlightSource: Hashable {
|
||||
|
||||
func ref() -> RefId {
|
||||
switch self {
|
||||
case .event(let event):
|
||||
return .event(event.id)
|
||||
case .event(let event_id):
|
||||
return .event(event_id)
|
||||
case .external_url(let url):
|
||||
return .reference(url.absoluteString)
|
||||
}
|
||||
}
|
||||
|
||||
static func from(tags: [[String]]) -> HighlightSource? {
|
||||
for tag in tags {
|
||||
if tag.count == 3 && tag[0] == "e" && tag[2] == HighlightSource.TAG_SOURCE_ELEMENT {
|
||||
guard let event_id = NoteId(hex: tag[1]) else { continue }
|
||||
return .event(event_id)
|
||||
}
|
||||
if tag.count == 3 && tag[0] == "r" && tag[2] == HighlightSource.TAG_SOURCE_ELEMENT {
|
||||
guard let url = URL(string: tag[1]) else { continue }
|
||||
return .external_url(url)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
struct ShareContent {
|
||||
|
||||
@@ -79,6 +79,7 @@ class HomeModel: ContactsDelegate {
|
||||
var notifications = NotificationsModel()
|
||||
var notification_status = NotificationStatusModel()
|
||||
var events: EventHolder = EventHolder()
|
||||
var already_reposted: Set<NoteId> = Set()
|
||||
var zap_button: ZapButtonModel = ZapButtonModel()
|
||||
|
||||
init() {
|
||||
@@ -122,6 +123,7 @@ class HomeModel: ContactsDelegate {
|
||||
/// This is called whenever DamusState gets set. This function is used to load or setup anything we need from the new DamusState
|
||||
func load_our_stuff_from_damus_state() {
|
||||
self.load_latest_contact_event_from_damus_state()
|
||||
self.load_drafts_from_damus_state()
|
||||
}
|
||||
|
||||
/// This loads the latest contact event we have on file from NostrDB. This should be called as soon as we get the new DamusState
|
||||
@@ -134,6 +136,10 @@ class HomeModel: ContactsDelegate {
|
||||
process_contact_event(state: damus_state, ev: latest_contact_event)
|
||||
}
|
||||
|
||||
func load_drafts_from_damus_state() {
|
||||
damus_state.drafts.load(from: damus_state)
|
||||
}
|
||||
|
||||
// MARK: - ContactsDelegate functions
|
||||
|
||||
func latest_contact_event_changed(new_event: NostrEvent) {
|
||||
@@ -215,6 +221,10 @@ class HomeModel: ContactsDelegate {
|
||||
break
|
||||
case .status:
|
||||
handle_status_event(ev)
|
||||
case .draft:
|
||||
// TODO: Implement draft syncing with relays. We intentionally do not support that as of writing. See `DraftsModel.swift` for other details
|
||||
// try? damus_state.drafts.load(wrapped_draft_note: ev, with: damus_state)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +376,8 @@ class HomeModel: ContactsDelegate {
|
||||
boost_ev_id = inner_ev.id
|
||||
|
||||
Task {
|
||||
// NOTE (jb55): remove this after nostrdb update, since nostrdb
|
||||
// processess reposts when note is ingested
|
||||
guard validate_event(ev: inner_ev) == .ok else {
|
||||
return
|
||||
}
|
||||
@@ -385,7 +397,7 @@ class HomeModel: ContactsDelegate {
|
||||
switch self.damus_state.boosts.add_event(ev, target: e) {
|
||||
case .already_counted:
|
||||
break
|
||||
case .success(let n):
|
||||
case .success(_):
|
||||
notify(.update_stats(note_id: e))
|
||||
}
|
||||
}
|
||||
@@ -394,7 +406,7 @@ class HomeModel: ContactsDelegate {
|
||||
switch damus_state.quote_reposts.add_event(ev, target: target) {
|
||||
case .already_counted:
|
||||
break
|
||||
case .success(let n):
|
||||
case .success(_):
|
||||
notify(.update_stats(note_id: target))
|
||||
}
|
||||
}
|
||||
@@ -723,6 +735,16 @@ class HomeModel: ContactsDelegate {
|
||||
handle_quote_repost_event(ev, target: quoted_event.note_id)
|
||||
}
|
||||
|
||||
// don't add duplicate reposts to home
|
||||
if ev.known_kind == .boost, let target = ev.get_inner_event()?.id {
|
||||
if already_reposted.contains(target) {
|
||||
Log.info("Skipping duplicate repost for event %s", for: .timeline, target.hex())
|
||||
return
|
||||
} else {
|
||||
already_reposted.insert(target)
|
||||
}
|
||||
}
|
||||
|
||||
if sub_id == home_subid {
|
||||
insert_home_event(ev)
|
||||
} else if sub_id == notifications_subid {
|
||||
|
||||
@@ -77,11 +77,19 @@ enum MediaUpload {
|
||||
}
|
||||
}
|
||||
|
||||
class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
|
||||
protocol ImageUploadModelProtocol {
|
||||
init()
|
||||
|
||||
func start(media: MediaUpload, uploader: any MediaUploaderProtocol, mediaType: ImageUploadMediaType, keypair: Keypair?) async -> ImageUploadResult
|
||||
}
|
||||
|
||||
class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject, ImageUploadModelProtocol {
|
||||
@Published var progress: Double? = nil
|
||||
|
||||
func start(media: MediaUpload, uploader: MediaUploader, keypair: Keypair? = nil) async -> ImageUploadResult {
|
||||
let res = await create_upload_request(mediaToUpload: media, mediaUploader: uploader, progress: self, keypair: keypair)
|
||||
override required init() { }
|
||||
|
||||
func start(media: MediaUpload, uploader: any MediaUploaderProtocol, mediaType: ImageUploadMediaType, keypair: Keypair? = nil) async -> ImageUploadResult {
|
||||
let res = await AttachMediaUtility.create_upload_request(mediaToUpload: media, mediaUploader: uploader, mediaType: mediaType, progress: self, keypair: keypair)
|
||||
|
||||
switch res {
|
||||
case .success(_):
|
||||
@@ -89,10 +97,17 @@ class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
|
||||
self.progress = nil
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
}
|
||||
case .failed(_):
|
||||
case .failed(let error):
|
||||
DispatchQueue.main.async {
|
||||
self.progress = nil
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
if let nsError = error as NSError?,
|
||||
nsError.domain == NSURLErrorDomain,
|
||||
nsError.code == NSURLErrorCancelled {
|
||||
print("Upload forced cancelled by user after Cancelling the Post, no feedback triggered.")
|
||||
} else {
|
||||
// Trigger feedback for all other errors
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// LongformEvent.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Daniel Nogueira on 2023-11-24.
|
||||
// Created by Daniel D'Aquino on 2023-11-24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -7,7 +7,18 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum MediaUploader: String, CaseIterable, Identifiable, StringCodable {
|
||||
protocol MediaUploaderProtocol: Identifiable {
|
||||
var nameParam: String { get }
|
||||
var mediaTypeParam: String { get }
|
||||
var supportsVideo: Bool { get }
|
||||
var requiresNip98: Bool { get }
|
||||
var postAPI: String { get }
|
||||
|
||||
func getMediaURL(from data: Data) -> String?
|
||||
func mediaTypeValue(for mediaType: ImageUploadMediaType) -> String?
|
||||
}
|
||||
|
||||
enum MediaUploader: String, CaseIterable, MediaUploaderProtocol, StringCodable {
|
||||
var id: String { self.rawValue }
|
||||
case nostrBuild
|
||||
case nostrcheck
|
||||
@@ -33,6 +44,19 @@ enum MediaUploader: String, CaseIterable, Identifiable, StringCodable {
|
||||
}
|
||||
}
|
||||
|
||||
var mediaTypeParam: String {
|
||||
return "media_type"
|
||||
}
|
||||
|
||||
func mediaTypeValue(for mediaType: ImageUploadMediaType) -> String? {
|
||||
switch mediaType {
|
||||
case .normal:
|
||||
return nil
|
||||
case .profile_picture:
|
||||
return "avatar"
|
||||
}
|
||||
}
|
||||
|
||||
var supportsVideo: Bool {
|
||||
switch self {
|
||||
case .nostrBuild:
|
||||
@@ -42,6 +66,15 @@ enum MediaUploader: String, CaseIterable, Identifiable, StringCodable {
|
||||
}
|
||||
}
|
||||
|
||||
var requiresNip98: Bool {
|
||||
switch self {
|
||||
case .nostrBuild:
|
||||
return true
|
||||
case .nostrcheck:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
struct Model: Identifiable, Hashable {
|
||||
var id: String { self.tag }
|
||||
var index: Int
|
||||
|
||||
@@ -122,6 +122,10 @@ enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
protocol URLEncodable {
|
||||
func url() -> URL?
|
||||
}
|
||||
|
||||
struct Mention<T: Equatable>: Equatable {
|
||||
let index: Int?
|
||||
let ref: T
|
||||
|
||||
@@ -363,6 +363,42 @@ class DamusPurple: StoreObserverDelegate {
|
||||
return freshly_completed_checkouts
|
||||
}
|
||||
|
||||
/// Handles a Purple URL
|
||||
/// - Parameter purple_url: The Purple URL being opened
|
||||
/// - Returns: A view to be shown in the UI
|
||||
@MainActor
|
||||
func handle(purple_url: DamusPurpleURL) async -> ContentView.ViewOpenAction {
|
||||
if case let .welcome(checkout_id) = purple_url.variant {
|
||||
// If this is a welcome link, do the following before showing the onboarding screen:
|
||||
// 1. Check if this is legitimate and good to go.
|
||||
// 2. Mark as complete if this is good to go.
|
||||
let is_good_to_go = try? await check_and_mark_ln_checkout_is_good_to_go(checkout_id: checkout_id)
|
||||
switch is_good_to_go {
|
||||
case .some(let is_good_to_go):
|
||||
if is_good_to_go {
|
||||
return .sheet(.purple(purple_url)) // ALL GOOD, SHOW WELCOME SHEET
|
||||
}
|
||||
else {
|
||||
return .sheet(.error(.init(
|
||||
user_visible_description: NSLocalizedString("You clicked on a Purple welcome link, but it does not look like the checkout is completed yet. This is likely a bug.", comment: "Error label upon continuing in the app from a Damus Purple purchase"),
|
||||
tip: NSLocalizedString("Please double-check the checkout web page, or go to the Side Menu → \"Purple\" to check your account status. If you have already paid, but still don't see your account active, please save the URL of the checkout page where you came from, contact our support, and give us the URL to help you with this issue.", comment: "User-facing tips on what to do if a Purple welcome link doesn't work"),
|
||||
technical_info: "Handling Purple URL \"\(purple_url)\" failed, the `is_good_to_go` result was `\(is_good_to_go)`"
|
||||
)))
|
||||
}
|
||||
case .none:
|
||||
return .sheet(.error(.init(
|
||||
user_visible_description: NSLocalizedString("You clicked on a Purple welcome link, but we could not find your checkout. This is likely a bug.", comment: "Error label upon continuing in the app from a Damus Purple purchase"),
|
||||
tip: NSLocalizedString("Please double-check the checkout web page, or go to the Side Menu → \"Purple\" to check your account status. If you have already paid, but still don't see your account active, please save the URL of the checkout page where you came from, contact our support, and give us the URL to help you with this issue.", comment: "User-facing tips on what to do if a Purple welcome link doesn't work"),
|
||||
technical_info: "Handling Purple URL \"\(purple_url)\" failed, the `is_good_to_go` result was `\(String(describing: is_good_to_go))`"
|
||||
)))
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Show the purple url contents
|
||||
return .sheet(.purple(purple_url))
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// This function checks the status of a specific checkout id with the server
|
||||
/// You should use this result immediately, since it will internally be marked as handled
|
||||
@@ -382,6 +418,13 @@ class DamusPurple: StoreObserverDelegate {
|
||||
let expiry: Date
|
||||
let subscriber_number: Int
|
||||
let active: Bool
|
||||
let attributes: PurpleAccountAttributes
|
||||
|
||||
struct PurpleAccountAttributes: OptionSet {
|
||||
let rawValue: Int
|
||||
|
||||
static let memberForMoreThanOneYear = PurpleAccountAttributes(rawValue: 1 << 0)
|
||||
}
|
||||
|
||||
func ordinal() -> String? {
|
||||
let number = Int(self.subscriber_number)
|
||||
@@ -402,7 +445,8 @@ class DamusPurple: StoreObserverDelegate {
|
||||
created_at: Date.init(timeIntervalSince1970: TimeInterval(payload.created_at)),
|
||||
expiry: Date.init(timeIntervalSince1970: TimeInterval(payload.expiry)),
|
||||
subscriber_number: Int(payload.subscriber_number),
|
||||
active: payload.active
|
||||
active: payload.active,
|
||||
attributes: (payload.attributes?.member_for_more_than_one_year ?? false) ? [.memberForMoreThanOneYear] : []
|
||||
)
|
||||
}
|
||||
|
||||
@@ -412,6 +456,11 @@ class DamusPurple: StoreObserverDelegate {
|
||||
let expiry: UInt64 // Unix timestamp
|
||||
let subscriber_number: UInt
|
||||
let active: Bool
|
||||
let attributes: Attributes?
|
||||
|
||||
struct Attributes: Codable {
|
||||
let member_for_more_than_one_year: Bool
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// DamusPurpleURL.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Daniel Nogueira on 2024-01-13.
|
||||
// Created by Daniel D'Aquino on 2024-01-13.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -7,42 +7,75 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// manages the lifetime of a thread
|
||||
/// manages the lifetime of a thread in a thread view such as `ChatroomThreadView`
|
||||
/// Makes a subscription to the relay pool to get events related to the thread
|
||||
/// It also keeps track of a selected event in the thread, and can pinpoint all of its parents and reply chain
|
||||
@MainActor
|
||||
class ThreadModel: ObservableObject {
|
||||
@Published var event: NostrEvent
|
||||
/// The original event where this thread was loaded from
|
||||
/// We use this to know the starting point from which we try to load the rest of the thread
|
||||
/// This is immutable because this is our starting point of the thread, and we don't expect this to ever change during the lifetime of a thread view
|
||||
let original_event: NostrEvent
|
||||
let highlight: String?
|
||||
var event_map: Set<NostrEvent>
|
||||
/// A map of events, the reply chain, etc
|
||||
/// This can be read by the view, but it can only be updated internally, because it is this classes' responsibility to ensure we load the proper events
|
||||
@Published private(set) var event_map: ThreadEventMap
|
||||
/// The currently selected event
|
||||
/// Can only be directly changed internally. Views should set this via the `select` methods
|
||||
@Published private(set) var selected_event: NostrEvent
|
||||
|
||||
init(event: NostrEvent, damus_state: DamusState, highlight: String? = nil) {
|
||||
self.damus_state = damus_state
|
||||
self.event_map = Set()
|
||||
self.event = event
|
||||
self.original_event = event
|
||||
self.highlight = highlight
|
||||
add_event(event, keypair: damus_state.keypair)
|
||||
/// All of the parent events of `selected_event` in the thread, sorted from the highest level in the thread (The root of the thread), down to the direct parent
|
||||
///
|
||||
/// ## Implementation notes
|
||||
///
|
||||
/// This is a computed property because we then don't need to worry about keeping things in sync
|
||||
var parent_events: [NostrEvent] {
|
||||
return event_map.parent_events(of: selected_event)
|
||||
}
|
||||
|
||||
func events() -> [NostrEvent] {
|
||||
return Array(event_map).sorted(by: { a, b in
|
||||
return a.created_at < b.created_at
|
||||
/// All of the direct and indirect replies of `selected_event` in the thread. sorted chronologically
|
||||
///
|
||||
/// ## Implementation notes
|
||||
///
|
||||
/// This is a computed property because we then don't need to worry about keeping things in sync
|
||||
var sorted_child_events: [NostrEvent] {
|
||||
event_map.sorted_recursive_child_events(of: selected_event).filter({
|
||||
should_show_event(event: $0, damus_state: damus_state) // Hide muted events from chatroom conversation
|
||||
})
|
||||
}
|
||||
|
||||
var is_original: Bool {
|
||||
return original_event.id == event.id
|
||||
}
|
||||
|
||||
/// The damus state, needed to access the relay pool and load the thread events
|
||||
let damus_state: DamusState
|
||||
|
||||
let profiles_subid = UUID().description
|
||||
let base_subid = UUID().description
|
||||
let meta_subid = UUID().description
|
||||
|
||||
var subids: [String] {
|
||||
private let profiles_subid = UUID().description
|
||||
private let base_subid = UUID().description
|
||||
private let meta_subid = UUID().description
|
||||
private var subids: [String] {
|
||||
return [profiles_subid, base_subid, meta_subid]
|
||||
}
|
||||
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
/// Initialize this model
|
||||
///
|
||||
/// You should also call `subscribe()` to start loading thread events from the relay pool.
|
||||
/// This is done manually to ensure we only load stuff when needed (e.g. when a view appears)
|
||||
init(event: NostrEvent, damus_state: DamusState) {
|
||||
self.damus_state = damus_state
|
||||
self.event_map = ThreadEventMap()
|
||||
self.original_event = event
|
||||
self.selected_event = event
|
||||
add_event(event, keypair: damus_state.keypair)
|
||||
}
|
||||
|
||||
/// All events in the thread, sorted in chronological order
|
||||
var events: [NostrEvent] {
|
||||
return event_map.sorted_events
|
||||
}
|
||||
|
||||
|
||||
// MARK: Relay pool subscription management
|
||||
|
||||
/// Unsubscribe from events in the relay pool. Call this when unloading the view
|
||||
func unsubscribe() {
|
||||
self.damus_state.pool.remove_handler(sub_id: base_subid)
|
||||
self.damus_state.pool.remove_handler(sub_id: meta_subid)
|
||||
@@ -50,33 +83,25 @@ class ThreadModel: ObservableObject {
|
||||
self.damus_state.pool.unsubscribe(sub_id: base_subid)
|
||||
self.damus_state.pool.unsubscribe(sub_id: meta_subid)
|
||||
self.damus_state.pool.unsubscribe(sub_id: profiles_subid)
|
||||
print("unsubscribing from thread \(event.id) with sub_id \(base_subid)")
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func set_active_event(_ ev: NostrEvent, keypair: Keypair) -> Bool {
|
||||
self.event = ev
|
||||
add_event(ev, keypair: keypair)
|
||||
|
||||
//self.objectWillChange.send()
|
||||
return false
|
||||
Log.info("unsubscribing to thread %s with sub_id %s", for: .render, original_event.id.hex(), base_subid)
|
||||
}
|
||||
|
||||
/// Subscribe to events in this thread. Call this when loading the view.
|
||||
func subscribe() {
|
||||
var meta_events = NostrFilter()
|
||||
var quote_events = NostrFilter()
|
||||
var event_filter = NostrFilter()
|
||||
var ref_events = NostrFilter()
|
||||
|
||||
let thread_id = event.thread_id()
|
||||
let thread_id = original_event.thread_id()
|
||||
|
||||
ref_events.referenced_ids = [thread_id, event.id]
|
||||
ref_events.referenced_ids = [thread_id, original_event.id]
|
||||
ref_events.kinds = [.text]
|
||||
ref_events.limit = 1000
|
||||
|
||||
event_filter.ids = [thread_id, event.id]
|
||||
event_filter.ids = [thread_id, original_event.id]
|
||||
|
||||
meta_events.referenced_ids = [event.id]
|
||||
meta_events.referenced_ids = [original_event.id]
|
||||
|
||||
var kinds: [NostrKind] = [.zap, .text, .boost]
|
||||
if !damus_state.settings.onlyzaps_mode {
|
||||
@@ -86,33 +111,40 @@ class ThreadModel: ObservableObject {
|
||||
meta_events.limit = 1000
|
||||
|
||||
quote_events.kinds = [.text]
|
||||
quote_events.quotes = [event.id]
|
||||
quote_events.quotes = [original_event.id]
|
||||
quote_events.limit = 1000
|
||||
|
||||
let base_filters = [event_filter, ref_events]
|
||||
let meta_filters = [meta_events, quote_events]
|
||||
|
||||
print("subscribing to thread \(event.id) with sub_id \(base_subid)")
|
||||
Log.info("subscribing to thread %s with sub_id %s", for: .render, original_event.id.hex(), base_subid)
|
||||
damus_state.pool.subscribe(sub_id: base_subid, filters: base_filters, handler: handle_event)
|
||||
damus_state.pool.subscribe(sub_id: meta_subid, filters: meta_filters, handler: handle_event)
|
||||
}
|
||||
|
||||
/// Adds an event to this thread.
|
||||
/// Normally this does not need to be called externally because it is the responsibility of this class to load the events, not the view's.
|
||||
/// However, this can be called externally for testing purposes (e.g. injecting events for testing)
|
||||
func add_event(_ ev: NostrEvent, keypair: Keypair) {
|
||||
if event_map.contains(ev) {
|
||||
if event_map.contains(id: ev.id) {
|
||||
return
|
||||
}
|
||||
|
||||
damus_state.events.upsert(ev)
|
||||
_ = damus_state.events.upsert(ev)
|
||||
damus_state.replies.count_replies(ev, keypair: keypair)
|
||||
damus_state.events.add_replies(ev: ev, keypair: keypair)
|
||||
|
||||
event_map.insert(ev)
|
||||
event_map.add(event: ev)
|
||||
|
||||
// Publish changes
|
||||
objectWillChange.send()
|
||||
}
|
||||
|
||||
|
||||
/// Handles an incoming event from a relay pool
|
||||
///
|
||||
/// Marked as private because it is this class' responsibility to load events, not the view's. Simplify the interface
|
||||
@MainActor
|
||||
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
|
||||
private func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||
let (sub_id, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: ev) { sid, ev in
|
||||
guard subids.contains(sid) else {
|
||||
return
|
||||
@@ -125,7 +157,7 @@ class ThreadModel: ObservableObject {
|
||||
} else if ev.is_textlike {
|
||||
// handle thread quote reposts, we just count them instead of
|
||||
// adding them to the thread
|
||||
if let target = ev.is_quote_repost, target == self.event.id {
|
||||
if let target = ev.is_quote_repost, target == self.selected_event.id {
|
||||
//let _ = self.damus_state.quote_reposts.add_event(ev, target: target)
|
||||
} else {
|
||||
self.add_event(ev, keypair: damus_state.keypair)
|
||||
@@ -139,10 +171,167 @@ class ThreadModel: ObservableObject {
|
||||
|
||||
if sub_id == self.base_subid {
|
||||
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
||||
load_profiles(context: "thread", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(Array(event_map)), damus_state: damus_state, txn: txn)
|
||||
load_profiles(context: "thread", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(Array(event_map.events)), damus_state: damus_state, txn: txn)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: External control interface
|
||||
// Control methods created for the thread view
|
||||
|
||||
/// Change the currently selected event
|
||||
///
|
||||
/// - Parameter event: Event to select
|
||||
func select(event: NostrEvent) {
|
||||
self.selected_event = event
|
||||
add_event(event, keypair: damus_state.keypair)
|
||||
}
|
||||
}
|
||||
|
||||
/// A thread event map, a model that holds events, indexes them, and can efficiently answer questions about a thread.
|
||||
///
|
||||
/// Add events that are part of a thread to this model, and use one of its many convenience functions to get answers about the hierarchy of the thread.
|
||||
///
|
||||
/// This does NOT perform any event loading, networking, or storage operations. This is simply a convenient/efficient way to keep and query about a thread
|
||||
struct ThreadEventMap {
|
||||
/// A map for keeping nostr events, and efficiently querying them by note id
|
||||
///
|
||||
/// Marked as `private` because:
|
||||
/// - We want to hide this complexity from the user of this struct
|
||||
/// - It is this struct's responsibility to keep this in sync with `event_reply_index`
|
||||
private var event_map: [NoteId: NostrEvent] = [:]
|
||||
/// An index of the reply hierarchy, which allows replies to be found in O(1) efficiency
|
||||
///
|
||||
/// ## Implementation notes
|
||||
///
|
||||
/// Marked as `private` because:
|
||||
/// - We want to hide this complexity from the user of this struct
|
||||
/// - It is this struct's responsibility to keep this in sync with `event_map`
|
||||
///
|
||||
/// We only store note ids to save space, as we can easily get them from `event_map`
|
||||
private var event_reply_index: [NoteId: Set<NoteId>] = [:]
|
||||
|
||||
|
||||
// MARK: External interface
|
||||
|
||||
/// Events in the thread, in no particular order
|
||||
/// Use this when the order does not matter
|
||||
var events: Set<NostrEvent> {
|
||||
return Set(event_map.values)
|
||||
}
|
||||
|
||||
/// Events in the thread, sorted chronologically. Use this when the order matters.
|
||||
/// Use `.events` when the order doesn't matter, as it is more computationally efficient.
|
||||
var sorted_events: [NostrEvent] {
|
||||
return events.sorted(by: { a, b in
|
||||
return a.created_at < b.created_at
|
||||
})
|
||||
}
|
||||
|
||||
/// Add an event to this map
|
||||
///
|
||||
/// Efficiency: O(1)
|
||||
///
|
||||
/// - Parameter event: The event to be added
|
||||
mutating func add(event: NostrEvent) {
|
||||
self.event_map[event.id] = event
|
||||
|
||||
// Update our efficient reply index
|
||||
if let note_id_replied_to = event.direct_replies() {
|
||||
if event_reply_index[note_id_replied_to] == nil {
|
||||
event_reply_index[note_id_replied_to] = [event.id]
|
||||
}
|
||||
else {
|
||||
event_reply_index[note_id_replied_to]?.insert(event.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the thread map contains a given note, referenced by ID
|
||||
///
|
||||
/// Efficiency: O(1)
|
||||
///
|
||||
/// - Parameter id: The ID to look for
|
||||
/// - Returns: True if it does, false otherwise
|
||||
func contains(id: NoteId) -> Bool {
|
||||
return self.event_map[id] != nil
|
||||
}
|
||||
|
||||
/// Gets a note from the thread by its id
|
||||
///
|
||||
/// Efficiency: O(1)
|
||||
///
|
||||
/// - Parameter id: The note id
|
||||
/// - Returns: The note, if it exists in the thread map.
|
||||
func get(id: NoteId) -> NostrEvent? {
|
||||
return self.event_map[id]
|
||||
}
|
||||
|
||||
|
||||
/// Returns all the parent events in a thread, relative to a given event
|
||||
///
|
||||
/// Efficiency: O(N) in the worst case
|
||||
///
|
||||
/// - Parameter query_event: The event for which to find the parents for
|
||||
/// - Returns: An array of parent events, sorted from the highest level in the thread (The root of the thread), down to the direct parent of the query event. If query event is not found, this will return an empty array
|
||||
func parent_events(of query_event: NostrEvent) -> [NostrEvent] {
|
||||
var parents: [NostrEvent] = []
|
||||
var event = query_event
|
||||
while true {
|
||||
guard let direct_reply = event.direct_replies(),
|
||||
let parent_event = self.get(id: direct_reply), parent_event != event
|
||||
else {
|
||||
break
|
||||
}
|
||||
|
||||
parents.append(parent_event)
|
||||
event = parent_event
|
||||
}
|
||||
|
||||
return parents.reversed()
|
||||
}
|
||||
|
||||
|
||||
/// All of the replies in a thread for a given event, including indirect replies (reply of a reply), sorted in chronological order
|
||||
///
|
||||
/// Efficiency: O(Nlog(N)) in the worst case scenario, coming from Swift's built-in sorting algorithm "Timsort"
|
||||
///
|
||||
/// - Parameter query_event: The event for which to find the children for
|
||||
/// - Returns: All of the direct and indirect replies for an event, sorted in chronological order. If query event is not present, this will be an empty array.
|
||||
func sorted_recursive_child_events(of query_event: NostrEvent) -> [NostrEvent] {
|
||||
let all_recursive_child_events = self.recursive_child_events(of: query_event)
|
||||
return all_recursive_child_events.sorted(by: { a, b in
|
||||
return a.created_at < b.created_at
|
||||
})
|
||||
}
|
||||
|
||||
/// All of the replies in a thread for a given event, including indirect replies (reply of a reply), in any order
|
||||
///
|
||||
/// Use this when the order does not matter, as it is more efficient
|
||||
///
|
||||
/// Efficiency: O(N) in the worst case scenario.
|
||||
///
|
||||
/// - Parameter query_event: The event for which to find the children for
|
||||
/// - Returns: All of the direct and indirect replies for an event, sorted in chronological order. If query event is not present, this will be an empty array.
|
||||
func recursive_child_events(of query_event: NostrEvent) -> Set<NostrEvent> {
|
||||
let immediate_children_ids = self.event_reply_index[query_event.id] ?? []
|
||||
var immediate_children: Set<NostrEvent> = []
|
||||
for immediate_child_id in immediate_children_ids {
|
||||
guard let immediate_child = self.event_map[immediate_child_id] else {
|
||||
// This is an internal inconsistency.
|
||||
// Crash the app in debug mode to increase awareness, but let it go in production mode (not mission critical)
|
||||
assertionFailure("Desync between `event_map` and `event_reply_index` should never happen in `ThreadEventMap`!")
|
||||
continue
|
||||
}
|
||||
immediate_children.insert(immediate_child)
|
||||
}
|
||||
|
||||
var indirect_children: Set<NdbNote> = []
|
||||
for immediate_child in immediate_children {
|
||||
let recursive_children = self.recursive_child_events(of: immediate_child)
|
||||
indirect_children = indirect_children.union(recursive_children)
|
||||
}
|
||||
return immediate_children.union(indirect_children)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
107
damus/Models/URLHandler.swift
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// URLHandler.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Daniel D’Aquino on 2024-09-06.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Parses URLs into actions within the app.
|
||||
///
|
||||
/// ## Implementation notes
|
||||
///
|
||||
/// - This exists so that we can separate the logic of parsing the URL and the actual action within the app. That makes the code more readable, testable, and extensible
|
||||
struct DamusURLHandler {
|
||||
/// Parses a URL, handles any needed actions within damus state, and returns the view to be opened in the app
|
||||
///
|
||||
/// Side effects: May mutate `damus_state` in some circumstances
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - damus_state: The Damus state. May be mutated as part of this function
|
||||
/// - url: The URL to be opened
|
||||
/// - Returns: A view to be shown to the user
|
||||
static func handle_opening_url_and_compute_view_action(damus_state: DamusState, url: URL) async -> ContentView.ViewOpenAction {
|
||||
let parsed_url_info = parse_url(url: url)
|
||||
|
||||
switch parsed_url_info {
|
||||
case .profile(let pubkey):
|
||||
return .route(.ProfileByKey(pubkey: pubkey))
|
||||
case .filter(let nostrFilter):
|
||||
let search = SearchModel(state: damus_state, search: nostrFilter)
|
||||
return .route(.Search(search: search))
|
||||
case .event(let nostrEvent):
|
||||
let thread = await ThreadModel(event: nostrEvent, damus_state: damus_state)
|
||||
return .route(.Thread(thread: thread))
|
||||
case .event_reference(let event_reference):
|
||||
return .route(.LoadableNostrEvent(note_reference: event_reference))
|
||||
case .wallet_connect(let walletConnectURL):
|
||||
damus_state.wallet.new(walletConnectURL)
|
||||
return .route(.Wallet(wallet: damus_state.wallet))
|
||||
case .script(let data):
|
||||
let model = ScriptModel(data: data, state: .not_loaded)
|
||||
return .route(.Script(script: model))
|
||||
case .purple(let purple_url):
|
||||
return await damus_state.purple.handle(purple_url: purple_url)
|
||||
case nil:
|
||||
break
|
||||
}
|
||||
return .sheet(.error(ErrorView.UserPresentableError(
|
||||
user_visible_description: NSLocalizedString("Could not parse the URL you are trying to open.", comment: "User visible error description"),
|
||||
tip: NSLocalizedString("Please try again, check the URL for typos, or contact support for further help.", comment: "User visible error tips"),
|
||||
technical_info: "Could not find a suitable open action. User tried to open this URL: \(url.absoluteString)"
|
||||
)))
|
||||
}
|
||||
|
||||
/// Parses a URL into a structured information object.
|
||||
///
|
||||
/// This function does not cause any mutations on the app, or any side-effects.
|
||||
///
|
||||
/// - Parameter url: The URL to be parsed
|
||||
/// - Returns: Structured information about the contents inside the URL. Returns `nil` if URL is not compatible, invalid, or could not be parsed for some reason.
|
||||
static func parse_url(url: URL) -> ParsedURLInfo? {
|
||||
if let purple_url = DamusPurpleURL(url: url) {
|
||||
return .purple(purple_url)
|
||||
}
|
||||
|
||||
if let nwc = WalletConnectURL(str: url.absoluteString) {
|
||||
return .wallet_connect(nwc)
|
||||
}
|
||||
|
||||
guard let link = decode_nostr_uri(url.absoluteString) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch link {
|
||||
case .ref(let ref):
|
||||
switch ref {
|
||||
case .pubkey(let pk):
|
||||
return .profile(pk)
|
||||
case .event(let noteid):
|
||||
return .event_reference(.note_id(noteid))
|
||||
case .hashtag(let ht):
|
||||
return .filter(.filter_hashtag([ht.hashtag]))
|
||||
case .param, .quote, .reference:
|
||||
// doesn't really make sense here
|
||||
break
|
||||
case .naddr(let naddr):
|
||||
return .event_reference(.naddr(naddr))
|
||||
}
|
||||
case .filter(let filt):
|
||||
return .filter(filt)
|
||||
case .script(let script):
|
||||
return .script(script)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
enum ParsedURLInfo {
|
||||
case profile(Pubkey)
|
||||
case filter(NostrFilter)
|
||||
case event(NostrEvent)
|
||||
case event_reference(LoadableNostrEventViewModel.NoteReference)
|
||||
case wallet_connect(WalletConnectURL)
|
||||
case script([UInt8])
|
||||
case purple(DamusPurpleURL)
|
||||
}
|
||||
}
|
||||
@@ -185,9 +185,6 @@ class UserSettingsStore: ObservableObject {
|
||||
|
||||
@Setting(key: "show_music_statuses", default_value: true)
|
||||
var show_music_statuses: Bool
|
||||
|
||||
@Setting(key: "show_only_preferred_languages", default_value: false)
|
||||
var show_only_preferred_languages: Bool
|
||||
|
||||
@Setting(key: "multiple_events_per_pubkey", default_value: false)
|
||||
var multiple_events_per_pubkey: Bool
|
||||
@@ -204,6 +201,10 @@ class UserSettingsStore: ObservableObject {
|
||||
@Setting(key: "developer_mode", default_value: false)
|
||||
var developer_mode: Bool
|
||||
|
||||
/// Makes all post content gibberish and blurhashes images, to avoid distractions when developers are working.
|
||||
@Setting(key: "undistract_mode", default_value: false)
|
||||
var undistractMode: Bool
|
||||
|
||||
@Setting(key: "always_show_onboarding_suggestions", default_value: false)
|
||||
var always_show_onboarding_suggestions: Bool
|
||||
|
||||
@@ -327,9 +328,13 @@ class UserSettingsStore: ObservableObject {
|
||||
|
||||
// MARK: Internal, hidden settings
|
||||
|
||||
// TODO: Get rid of this once we have NostrDB query capabilities integrated
|
||||
@Setting(key: "latest_contact_event_id", default_value: nil)
|
||||
var latest_contact_event_id_hex: String?
|
||||
|
||||
// TODO: Get rid of this once we have NostrDB query capabilities integrated
|
||||
@Setting(key: "draft_event_ids", default_value: nil)
|
||||
var draft_event_ids: [String]?
|
||||
|
||||
// MARK: Helper types
|
||||
|
||||
|
||||