Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
a8b6b5f10e
|
|||
|
1bedb6b2bd
|
|||
| 8d9f728cf0 | |||
| 2c62741e25 | |||
| 1f612f7fde | |||
| 0e9e102d0f | |||
| b94e8765a1 | |||
| 53964f5c1a | |||
| bd574d93c3 | |||
| 47514ace79 | |||
| 298b43733f | |||
| 02116c0af5 | |||
| 92121e3b2d | |||
| c92094823e | |||
| f4b1a504a5 | |||
| 99ae7de5eb | |||
| b3d9ee3fc0 | |||
| e65219ee3e | |||
| 414c67a919 | |||
| f436291209 | |||
| a9196a39df | |||
| 6a8ee9c360 | |||
|
947e24864e
|
|||
|
b9198d6bd7
|
|||
| 14bf187a6e | |||
| c996e5f8b3 | |||
|
b6dad349c9
|
|||
|
56dde30cf6
|
|||
|
95bfbae131
|
|||
|
3da0ff7ecc
|
|||
|
b8f846ded8
|
|||
|
e74c45ad39
|
|||
|
e6a03522c6
|
|||
|
dbc7d79ecd
|
|||
|
d2b5a65eca
|
|||
|
16b19d3a96
|
|||
|
70edb8d7c5
|
|||
|
ea04ebe95c
|
|||
|
44cf47faa4
|
|||
|
612abfd862
|
|||
|
20af086273
|
|||
| e9c1671d06 | |||
| d02847d466 | |||
| 580fa954b2 | |||
| aef516ae9f | |||
| eb4e3b692b | |||
| fe52381d63 | |||
| ab8d52e685 | |||
| 1d32200ae3 | |||
| 309b00380d | |||
| 7fa2118480 | |||
| 1a6c17e308 | |||
| 82a6046620 | |||
| 241755c8c4 | |||
| b26f66f15c | |||
| 28bd0c81e8 | |||
| 0bd1814877 | |||
| ee94f67b94 | |||
| 3a25075473 | |||
| d16ff8f78f | |||
| 38dc90cb33 | |||
| 52bbc698b2 | |||
| 496a11f597 | |||
| 4a8a0ea1bd | |||
| c424d4da99 | |||
| 69d5fc1553 | |||
| bcb59896db | |||
| e1e6d9eb3d | |||
| f1fdae5957 | |||
| f96647fa40 | |||
| 5ea522d306 |
@@ -1,3 +1,51 @@
|
|||||||
|
## [1.14] - 2025-05-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added safety reminder to wallets with higher balance (Daniel D’Aquino)
|
||||||
|
- Added one-click Coinos wallet setup (Daniel D’Aquino)
|
||||||
|
- Add notification setting to hide hellthreads (Terry Yiu)
|
||||||
|
- Added separated first aid option for relay lists that does not need a contact list reset (Daniel D’Aquino)
|
||||||
|
- Added NIP-65 relay list support (Daniel D’Aquino)
|
||||||
|
- Added Unicode 16 emoji reactions for iOS 18.4+ by upgrading EmojiPicker (Terry Yiu)
|
||||||
|
- Added a search interface to the settings screen (SanjaySiddharth)
|
||||||
|
- Added view introducing users to Zaps (ericholguin)
|
||||||
|
- Added new wallet view with balance and transactions list (ericholguin)
|
||||||
|
- Added copy technical info button to user visible errors, so that users can more easily share errors with developers (Daniel D’Aquino)
|
||||||
|
- Add dismiss button to wallet high balance reminders (Daniel D’Aquino)
|
||||||
|
- Zap receiver information now included for outgoing zaps (Daniel D’Aquino)
|
||||||
|
- Added inline note rendering of invoices to pull up wallet selector sheet (Terry Yiu)
|
||||||
|
- Added route to profile page from wallet tx list (ericholguin)
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Added additional information on top of blurred images (SanjaySiddharth)
|
||||||
|
- Improved robustness of relay list handling (Daniel D’Aquino)
|
||||||
|
- Updated image cache for better stability (Daniel D’Aquino)
|
||||||
|
- Improved integration with Nostr Wallet Connect wallets (ericholguin)
|
||||||
|
- Added relay connectivity information to NWC settings (Daniel D’Aquino)
|
||||||
|
- Improved handling around NWC responses (Daniel D’Aquino)
|
||||||
|
- Added more human visible errors on NWC wallets to aid with troubleshooting (Daniel D’Aquino)
|
||||||
|
- Re-enabled note zaps as permitted by the new App Store guidelines (Daniel D’Aquino)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Hide future notes from timeline (Terry Yiu)
|
||||||
|
- Fixed issue where profiles with a NIP-65 relay list would not display on Damus (Daniel D’Aquino)
|
||||||
|
- Fix quote notes to include missing q tag (Terry Yiu)
|
||||||
|
- Fixed issue where the side menu would close when copying the npub (SanjaySiddharth)
|
||||||
|
- Fixed issue where cached images would be backed up to iCloud (Daniel D’Aquino)
|
||||||
|
- Optimized classify_url function (Terry Yiu)
|
||||||
|
- Fixed note rendering for those that contain previewable items or leading and trailing whitespaces (Terry Yiu)
|
||||||
|
- Fixed issue where some videos would become unplayable after some time using the app (Daniel D’Aquino)
|
||||||
|
|
||||||
|
|
||||||
|
[1.14]: https://github.com/damus-io/damus/releases/tag/v1.14
|
||||||
|
|
||||||
|
|
||||||
## [1.13.1] - 2025-03-21
|
## [1.13.1] - 2025-03-21
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+203
-10
@@ -14,6 +14,12 @@
|
|||||||
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
|
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
|
||||||
3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 3A0A30BA2C21397A00F8C9BC /* EmojiPicker */; };
|
3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 3A0A30BA2C21397A00F8C9BC /* EmojiPicker */; };
|
||||||
3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */; };
|
3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */; };
|
||||||
|
3A2BAC5A2DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BAC592DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift */; };
|
||||||
|
3A2BAC5B2DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BAC592DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift */; };
|
||||||
|
3A2BAC5C2DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BAC592DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift */; };
|
||||||
|
3A2BAC5E2DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BAC5D2DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift */; };
|
||||||
|
3A2BAC5F2DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BAC5D2DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift */; };
|
||||||
|
3A2BAC602DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BAC5D2DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift */; };
|
||||||
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */; };
|
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */; };
|
||||||
3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */; };
|
3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */; };
|
||||||
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F229A91366008A0F29 /* ProfileViewTests.swift */; };
|
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F229A91366008A0F29 /* ProfileViewTests.swift */; };
|
||||||
@@ -21,10 +27,23 @@
|
|||||||
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
|
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
|
||||||
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */; };
|
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */; };
|
||||||
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; };
|
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; };
|
||||||
|
3A515C502DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C4F2DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift */; };
|
||||||
|
3A515C512DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C4F2DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift */; };
|
||||||
|
3A515C522DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C4F2DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift */; };
|
||||||
|
3A515C542DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */; };
|
||||||
|
3A515C552DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */; };
|
||||||
|
3A515C562DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */; };
|
||||||
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
|
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
|
||||||
|
3A92C0FE2DE16E9800CEEBAC /* FaviconCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */; };
|
||||||
|
3A92C0FF2DE16E9800CEEBAC /* FaviconCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */; };
|
||||||
|
3A92C1002DE16E9800CEEBAC /* FaviconCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */; };
|
||||||
|
3A92C1022DE17ACA00CEEBAC /* NIP05DomainTimelineHeaderViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92C1012DE17ACA00CEEBAC /* NIP05DomainTimelineHeaderViewTests.swift */; };
|
||||||
3A96E3FE2D6BCE3800AE1630 /* RepostedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A96E3FD2D6BCE3800AE1630 /* RepostedTests.swift */; };
|
3A96E3FE2D6BCE3800AE1630 /* RepostedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A96E3FD2D6BCE3800AE1630 /* RepostedTests.swift */; };
|
||||||
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
|
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
|
||||||
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
|
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
|
||||||
|
3AA2F4E82DF1467A00B18606 /* TrustedNetworkButtonTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2F4E72DF1467A00B18606 /* TrustedNetworkButtonTip.swift */; };
|
||||||
|
3AA2F4E92DF1467A00B18606 /* TrustedNetworkButtonTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2F4E72DF1467A00B18606 /* TrustedNetworkButtonTip.swift */; };
|
||||||
|
3AA2F4EA2DF1467A00B18606 /* TrustedNetworkButtonTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2F4E72DF1467A00B18606 /* TrustedNetworkButtonTip.swift */; };
|
||||||
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA59D1C2999B0400061C48E /* DraftsModel.swift */; };
|
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA59D1C2999B0400061C48E /* DraftsModel.swift */; };
|
||||||
3AAA95CA298DF87B00F3D526 /* TranslationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95C9298DF87B00F3D526 /* TranslationService.swift */; };
|
3AAA95CA298DF87B00F3D526 /* TranslationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95C9298DF87B00F3D526 /* TranslationService.swift */; };
|
||||||
3AAA95CC298E07E900F3D526 /* DeepLPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */; };
|
3AAA95CC298E07E900F3D526 /* DeepLPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */; };
|
||||||
@@ -33,6 +52,15 @@
|
|||||||
3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685A297633BC00C46468 /* InfoPlist.strings */; };
|
3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685A297633BC00C46468 /* InfoPlist.strings */; };
|
||||||
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685D297633BC00C46468 /* Localizable.strings */; };
|
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685D297633BC00C46468 /* Localizable.strings */; };
|
||||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
|
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
|
||||||
|
3ACF94382DA9A52F00971A4E /* FaviconFinder in Frameworks */ = {isa = PBXBuildFile; productRef = 3ACF94372DA9A52F00971A4E /* FaviconFinder */; };
|
||||||
|
3ACF943E2DA9B10800971A4E /* FaviconFinder in Frameworks */ = {isa = PBXBuildFile; productRef = 3ACF943D2DA9B10800971A4E /* FaviconFinder */; };
|
||||||
|
3ACF94402DA9B11200971A4E /* FaviconFinder in Frameworks */ = {isa = PBXBuildFile; productRef = 3ACF943F2DA9B11200971A4E /* FaviconFinder */; };
|
||||||
|
3ACF94422DA9FCAB00971A4E /* NIP05DomainTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACF94412DA9FCAB00971A4E /* NIP05DomainTimelineView.swift */; };
|
||||||
|
3ACF94432DA9FCAB00971A4E /* NIP05DomainTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACF94412DA9FCAB00971A4E /* NIP05DomainTimelineView.swift */; };
|
||||||
|
3ACF94442DA9FCAB00971A4E /* NIP05DomainTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACF94412DA9FCAB00971A4E /* NIP05DomainTimelineView.swift */; };
|
||||||
|
3ACF94462DAA006500971A4E /* NIP05DomainEventsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACF94452DAA006500971A4E /* NIP05DomainEventsModel.swift */; };
|
||||||
|
3ACF94472DAA006500971A4E /* NIP05DomainEventsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACF94452DAA006500971A4E /* NIP05DomainEventsModel.swift */; };
|
||||||
|
3ACF94482DAA006500971A4E /* NIP05DomainEventsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACF94452DAA006500971A4E /* NIP05DomainEventsModel.swift */; };
|
||||||
3AE45AF6297BB2E700C1D842 /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
3AE45AF6297BB2E700C1D842 /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
||||||
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */; };
|
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */; };
|
||||||
4C011B5E2BD0A56A002F2F9B /* ChatEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5C2BD0A56A002F2F9B /* ChatEventView.swift */; };
|
4C011B5E2BD0A56A002F2F9B /* ChatEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5C2BD0A56A002F2F9B /* ChatEventView.swift */; };
|
||||||
@@ -47,6 +75,7 @@
|
|||||||
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; };
|
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; };
|
||||||
4C0C03992A61E27B0098B3B8 /* primal.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C0C03972A61E27B0098B3B8 /* primal.wasm */; };
|
4C0C03992A61E27B0098B3B8 /* primal.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C0C03972A61E27B0098B3B8 /* primal.wasm */; };
|
||||||
4C0C039A2A61E27B0098B3B8 /* bool_setting.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */; };
|
4C0C039A2A61E27B0098B3B8 /* bool_setting.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */; };
|
||||||
|
4C0ED07F2D7A1E260020D8A2 /* Benchmarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0ED07E2D7A1E260020D8A2 /* Benchmarking.swift */; };
|
||||||
4C1253502A76C5B20004F4B8 /* UnfollowedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C12534F2A76C5B20004F4B8 /* UnfollowedNotify.swift */; };
|
4C1253502A76C5B20004F4B8 /* UnfollowedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C12534F2A76C5B20004F4B8 /* UnfollowedNotify.swift */; };
|
||||||
4C1253522A76C6130004F4B8 /* ComposeNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253512A76C6130004F4B8 /* ComposeNotify.swift */; };
|
4C1253522A76C6130004F4B8 /* ComposeNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253512A76C6130004F4B8 /* ComposeNotify.swift */; };
|
||||||
4C1253542A76C7D60004F4B8 /* LogoutNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253532A76C7D60004F4B8 /* LogoutNotify.swift */; };
|
4C1253542A76C7D60004F4B8 /* LogoutNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253532A76C7D60004F4B8 /* LogoutNotify.swift */; };
|
||||||
@@ -244,7 +273,7 @@
|
|||||||
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CE29E38B950036AF10 /* nostr_bech32.c */; };
|
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CE29E38B950036AF10 /* nostr_bech32.c */; };
|
||||||
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */; };
|
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */; };
|
||||||
4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */; };
|
4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */; };
|
||||||
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; };
|
4C8D1A6F29F31E5000ACDF75 /* TrustedNetworkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* TrustedNetworkButton.swift */; };
|
||||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
|
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
|
||||||
4C8FA7242BED58A900798A6A /* ThreadReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C45E5012BED4D000025A428 /* ThreadReply.swift */; };
|
4C8FA7242BED58A900798A6A /* ThreadReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C45E5012BED4D000025A428 /* ThreadReply.swift */; };
|
||||||
4C9054852A6AEAA000811EEC /* NdbTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9054842A6AEAA000811EEC /* NdbTests.swift */; };
|
4C9054852A6AEAA000811EEC /* NdbTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9054842A6AEAA000811EEC /* NdbTests.swift */; };
|
||||||
@@ -398,11 +427,26 @@
|
|||||||
5C0567592C8FBDE30073F23A /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
|
5C0567592C8FBDE30073F23A /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
|
||||||
5C05675A2C8FBDE70073F23A /* NDBSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0567572C8FBC560073F23A /* NDBSearchView.swift */; };
|
5C05675A2C8FBDE70073F23A /* NDBSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0567572C8FBC560073F23A /* NDBSearchView.swift */; };
|
||||||
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
|
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
|
||||||
|
5C09FD122DF283D700823661 /* FollowPackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C09FD112DF283D200823661 /* FollowPackModel.swift */; };
|
||||||
|
5C09FD132DF283D700823661 /* FollowPackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C09FD112DF283D200823661 /* FollowPackModel.swift */; };
|
||||||
|
5C09FD142DF283D700823661 /* FollowPackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C09FD112DF283D200823661 /* FollowPackModel.swift */; };
|
||||||
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */; };
|
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */; };
|
||||||
5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; };
|
5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; };
|
||||||
5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */; };
|
5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */; };
|
||||||
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
|
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
|
||||||
5C4D9EA72C042FA5005EA0F7 /* HighlightDraftContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */; };
|
5C4D9EA72C042FA5005EA0F7 /* HighlightDraftContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */; };
|
||||||
|
5C4FA7EC2DC29AE900CE658C /* FollowPackEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7EB2DC29AE900CE658C /* FollowPackEvent.swift */; };
|
||||||
|
5C4FA7ED2DC29AE900CE658C /* FollowPackEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7EB2DC29AE900CE658C /* FollowPackEvent.swift */; };
|
||||||
|
5C4FA7EE2DC29AE900CE658C /* FollowPackEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7EB2DC29AE900CE658C /* FollowPackEvent.swift */; };
|
||||||
|
5C4FA7FB2DC29C3800CE658C /* FollowPackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7F92DC29C3800CE658C /* FollowPackView.swift */; };
|
||||||
|
5C4FA7FC2DC29C3800CE658C /* FollowPackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7F92DC29C3800CE658C /* FollowPackView.swift */; };
|
||||||
|
5C4FA7FD2DC29C3800CE658C /* FollowPackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7F92DC29C3800CE658C /* FollowPackView.swift */; };
|
||||||
|
5C4FA7FF2DC5119300CE658C /* FollowPackPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7FE2DC5119300CE658C /* FollowPackPreview.swift */; };
|
||||||
|
5C4FA8002DC5119300CE658C /* FollowPackPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7FE2DC5119300CE658C /* FollowPackPreview.swift */; };
|
||||||
|
5C4FA8012DC5119300CE658C /* FollowPackPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7FE2DC5119300CE658C /* FollowPackPreview.swift */; };
|
||||||
|
5C4FA8032DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA8022DCAF80400CE658C /* FollowPackTimeline.swift */; };
|
||||||
|
5C4FA8042DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA8022DCAF80400CE658C /* FollowPackTimeline.swift */; };
|
||||||
|
5C4FA8052DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA8022DCAF80400CE658C /* FollowPackTimeline.swift */; };
|
||||||
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
|
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
|
||||||
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
|
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
|
||||||
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; };
|
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; };
|
||||||
@@ -743,7 +787,7 @@
|
|||||||
82D6FBD52CD99F7900C925F4 /* ConnectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D095C2A098C5D00943473 /* ConnectWalletView.swift */; };
|
82D6FBD52CD99F7900C925F4 /* ConnectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D095C2A098C5D00943473 /* ConnectWalletView.swift */; };
|
||||||
82D6FBD62CD99F7900C925F4 /* WalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D095D2A098C5D00943473 /* WalletView.swift */; };
|
82D6FBD62CD99F7900C925F4 /* WalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D095D2A098C5D00943473 /* WalletView.swift */; };
|
||||||
82D6FBD72CD99F7900C925F4 /* NWCScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09672A0AE9B200943473 /* NWCScannerView.swift */; };
|
82D6FBD72CD99F7900C925F4 /* NWCScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09672A0AE9B200943473 /* NWCScannerView.swift */; };
|
||||||
82D6FBD82CD99F7900C925F4 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; };
|
82D6FBD82CD99F7900C925F4 /* TrustedNetworkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* TrustedNetworkButton.swift */; };
|
||||||
82D6FBD92CD99F7900C925F4 /* GradientFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694F32A6732B7001F4053 /* GradientFollowButton.swift */; };
|
82D6FBD92CD99F7900C925F4 /* GradientFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694F32A6732B7001F4053 /* GradientFollowButton.swift */; };
|
||||||
82D6FBDA2CD99F7900C925F4 /* AlbyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09652A0AE62100943473 /* AlbyButton.swift */; };
|
82D6FBDA2CD99F7900C925F4 /* AlbyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09652A0AE62100943473 /* AlbyButton.swift */; };
|
||||||
82D6FBDC2CD99F7900C925F4 /* DamusVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2929DDF54400516EAC /* DamusVideoPlayerView.swift */; };
|
82D6FBDC2CD99F7900C925F4 /* DamusVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2929DDF54400516EAC /* DamusVideoPlayerView.swift */; };
|
||||||
@@ -1285,7 +1329,7 @@
|
|||||||
D73E5ECC2C6A97F4007EB227 /* SuggestedUsersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694EB2A662292001F4053 /* SuggestedUsersViewModel.swift */; };
|
D73E5ECC2C6A97F4007EB227 /* SuggestedUsersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694EB2A662292001F4053 /* SuggestedUsersViewModel.swift */; };
|
||||||
D73E5ED22C6A97F4007EB227 /* WalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D095D2A098C5D00943473 /* WalletView.swift */; };
|
D73E5ED22C6A97F4007EB227 /* WalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D095D2A098C5D00943473 /* WalletView.swift */; };
|
||||||
D73E5ED32C6A97F4007EB227 /* NWCScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09672A0AE9B200943473 /* NWCScannerView.swift */; };
|
D73E5ED32C6A97F4007EB227 /* NWCScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09672A0AE9B200943473 /* NWCScannerView.swift */; };
|
||||||
D73E5ED42C6A97F4007EB227 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; };
|
D73E5ED42C6A97F4007EB227 /* TrustedNetworkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* TrustedNetworkButton.swift */; };
|
||||||
D73E5ED52C6A97F4007EB227 /* GradientFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694F32A6732B7001F4053 /* GradientFollowButton.swift */; };
|
D73E5ED52C6A97F4007EB227 /* GradientFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71694F32A6732B7001F4053 /* GradientFollowButton.swift */; };
|
||||||
D73E5ED62C6A97F4007EB227 /* AlbyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09652A0AE62100943473 /* AlbyButton.swift */; };
|
D73E5ED62C6A97F4007EB227 /* AlbyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09652A0AE62100943473 /* AlbyButton.swift */; };
|
||||||
D73E5ED82C6A97F4007EB227 /* DamusVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2929DDF54400516EAC /* DamusVideoPlayerView.swift */; };
|
D73E5ED82C6A97F4007EB227 /* DamusVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1A9A2929DDF54400516EAC /* DamusVideoPlayerView.swift */; };
|
||||||
@@ -1479,6 +1523,9 @@
|
|||||||
D74AAFD22B155E78006CF0F4 /* WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09612A098D0E00943473 /* WalletConnect.swift */; };
|
D74AAFD22B155E78006CF0F4 /* WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7D09612A098D0E00943473 /* WalletConnect.swift */; };
|
||||||
D74AAFD42B155ECB006CF0F4 /* Zaps+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD32B155ECB006CF0F4 /* Zaps+.swift */; };
|
D74AAFD42B155ECB006CF0F4 /* Zaps+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD32B155ECB006CF0F4 /* Zaps+.swift */; };
|
||||||
D74AAFD62B155F0C006CF0F4 /* WalletConnect+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */; };
|
D74AAFD62B155F0C006CF0F4 /* WalletConnect+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */; };
|
||||||
|
D74E64132DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74E64112DC95CBE004C7892 /* HumanReadableErrors.swift */; };
|
||||||
|
D74E64142DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74E64112DC95CBE004C7892 /* HumanReadableErrors.swift */; };
|
||||||
|
D74E64152DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74E64112DC95CBE004C7892 /* HumanReadableErrors.swift */; };
|
||||||
D74EA08A2D2BF2A7002290DD /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D767066E2C8BB3CE00F09726 /* URLHandler.swift */; };
|
D74EA08A2D2BF2A7002290DD /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D767066E2C8BB3CE00F09726 /* URLHandler.swift */; };
|
||||||
D74EA08E2D2E271E002290DD /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA08D2D2E271E002290DD /* ErrorView.swift */; };
|
D74EA08E2D2E271E002290DD /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA08D2D2E271E002290DD /* ErrorView.swift */; };
|
||||||
D74EA08F2D2E271E002290DD /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA08D2D2E271E002290DD /* ErrorView.swift */; };
|
D74EA08F2D2E271E002290DD /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74EA08D2D2E271E002290DD /* ErrorView.swift */; };
|
||||||
@@ -1543,6 +1590,9 @@
|
|||||||
D7A0D8752D1FE67900DCBE59 /* EditPictureControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A0D8742D1FE66A00DCBE59 /* EditPictureControlTests.swift */; };
|
D7A0D8752D1FE67900DCBE59 /* EditPictureControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A0D8742D1FE66A00DCBE59 /* EditPictureControlTests.swift */; };
|
||||||
D7A343EE2AD0D77C00CED48B /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343ED2AD0D77C00CED48B /* InlineSnapshotTesting */; };
|
D7A343EE2AD0D77C00CED48B /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343ED2AD0D77C00CED48B /* InlineSnapshotTesting */; };
|
||||||
D7A343F02AD0D77C00CED48B /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343EF2AD0D77C00CED48B /* SnapshotTesting */; };
|
D7A343F02AD0D77C00CED48B /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D7A343EF2AD0D77C00CED48B /* SnapshotTesting */; };
|
||||||
|
D7AACFFF2E0387B800FB7699 /* LnurlAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AACFFE2E0387B800FB7699 /* LnurlAmountView.swift */; };
|
||||||
|
D7AAD0002E0387B800FB7699 /* LnurlAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AACFFE2E0387B800FB7699 /* LnurlAmountView.swift */; };
|
||||||
|
D7AAD0012E0387B800FB7699 /* LnurlAmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AACFFE2E0387B800FB7699 /* LnurlAmountView.swift */; };
|
||||||
D7ADD3DE2B53854300F104C4 /* DamusPurpleURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3DD2B53854300F104C4 /* DamusPurpleURL.swift */; };
|
D7ADD3DE2B53854300F104C4 /* DamusPurpleURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3DD2B53854300F104C4 /* DamusPurpleURL.swift */; };
|
||||||
D7ADD3E02B538D4200F104C4 /* DamusPurpleURLSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3DF2B538D4200F104C4 /* DamusPurpleURLSheetView.swift */; };
|
D7ADD3E02B538D4200F104C4 /* DamusPurpleURLSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3DF2B538D4200F104C4 /* DamusPurpleURLSheetView.swift */; };
|
||||||
D7ADD3E22B538E3500F104C4 /* DamusPurpleVerifyNpubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3E12B538E3500F104C4 /* DamusPurpleVerifyNpubView.swift */; };
|
D7ADD3E22B538E3500F104C4 /* DamusPurpleVerifyNpubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADD3E12B538E3500F104C4 /* DamusPurpleVerifyNpubView.swift */; };
|
||||||
@@ -1673,6 +1723,9 @@
|
|||||||
D7DB930C2D69486700DA1EE5 /* NIP65.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DB93092D69485A00DA1EE5 /* NIP65.swift */; };
|
D7DB930C2D69486700DA1EE5 /* NIP65.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DB93092D69485A00DA1EE5 /* NIP65.swift */; };
|
||||||
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
||||||
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
||||||
|
D7DF58322DFCF18D00E9AD28 /* SendPaymentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF58312DFCF18800E9AD28 /* SendPaymentView.swift */; };
|
||||||
|
D7DF58332DFCF18D00E9AD28 /* SendPaymentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF58312DFCF18800E9AD28 /* SendPaymentView.swift */; };
|
||||||
|
D7DF58342DFCF18D00E9AD28 /* SendPaymentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DF58312DFCF18800E9AD28 /* SendPaymentView.swift */; };
|
||||||
D7EB00B02CD59C8D00660C07 /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; };
|
D7EB00B02CD59C8D00660C07 /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; };
|
||||||
D7EB00B12CD59C8D00660C07 /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; };
|
D7EB00B12CD59C8D00660C07 /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; };
|
||||||
D7EDED152B11776B0018B19C /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
D7EDED152B11776B0018B19C /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
||||||
@@ -1703,6 +1756,9 @@
|
|||||||
D7F360262CEBBD8B009D34DA /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; };
|
D7F360262CEBBD8B009D34DA /* PresentFullScreenItemNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */; };
|
||||||
D7F360272CEBBDC0009D34DA /* DamusVideoControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EFBA362CC322F300F45588 /* DamusVideoControlsView.swift */; };
|
D7F360272CEBBDC0009D34DA /* DamusVideoControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EFBA362CC322F300F45588 /* DamusVideoControlsView.swift */; };
|
||||||
D7F360292CEBBE34009D34DA /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = D7F360282CEBBE34009D34DA /* CodeScanner */; };
|
D7F360292CEBBE34009D34DA /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = D7F360282CEBBE34009D34DA /* CodeScanner */; };
|
||||||
|
D7FA46E52DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FA46E42DBDAA75002C9BB0 /* ImageCacheMigrations.swift */; };
|
||||||
|
D7FA46E62DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FA46E42DBDAA75002C9BB0 /* ImageCacheMigrations.swift */; };
|
||||||
|
D7FA46E72DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FA46E42DBDAA75002C9BB0 /* ImageCacheMigrations.swift */; };
|
||||||
D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
|
D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
|
||||||
D7FB14222BE5970000398331 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D7FB14212BE5970000398331 /* PrivacyInfo.xcprivacy */; };
|
D7FB14222BE5970000398331 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D7FB14212BE5970000398331 /* PrivacyInfo.xcprivacy */; };
|
||||||
D7FB14252BE5A9A800398331 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D7FB14242BE5A9A800398331 /* PrivacyInfo.xcprivacy */; };
|
D7FB14252BE5A9A800398331 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D7FB14242BE5A9A800398331 /* PrivacyInfo.xcprivacy */; };
|
||||||
@@ -1802,6 +1858,8 @@
|
|||||||
3A25EF142992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3A25EF142992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3A25EF152992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "el-GR"; path = "el-GR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3A25EF152992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "el-GR"; path = "el-GR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
3A2B8B0A296A8982009CC16D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-US"; path = "en-US.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3A2B8B0A296A8982009CC16D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-US"; path = "en-US.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
|
3A2BAC592DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05DomainTimelineHeaderView.swift; sourceTree = "<group>"; };
|
||||||
|
3A2BAC5D2DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05DomainPubkeysView.swift; sourceTree = "<group>"; };
|
||||||
3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyDescriptionTests.swift; sourceTree = "<group>"; };
|
3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyDescriptionTests.swift; sourceTree = "<group>"; };
|
||||||
3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationUtil.swift; sourceTree = "<group>"; };
|
3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationUtil.swift; sourceTree = "<group>"; };
|
||||||
3A3040F229A91366008A0F29 /* ProfileViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewTests.swift; sourceTree = "<group>"; };
|
3A3040F229A91366008A0F29 /* ProfileViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -1827,6 +1885,8 @@
|
|||||||
3A47CB782BDA05A200728A7C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3A47CB782BDA05A200728A7C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3A47CB792BDA05A200728A7C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fi; path = fi.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3A47CB792BDA05A200728A7C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fi; path = fi.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; };
|
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; };
|
||||||
|
3A515C4F2DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedNetworkRepliesTip.swift; sourceTree = "<group>"; };
|
||||||
|
3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedNetworkButtonTipViewStyle.swift; sourceTree = "<group>"; };
|
||||||
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
3A5CAE1D298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3A5CAE1D298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3A5CAE1E298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3A5CAE1E298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
@@ -1847,6 +1907,8 @@
|
|||||||
3A929C20297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3A929C20297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3A929C21297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3A929C21297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3A929C22297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "it-IT"; path = "it-IT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3A929C22297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "it-IT"; path = "it-IT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
|
3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconCache.swift; sourceTree = "<group>"; };
|
||||||
|
3A92C1012DE17ACA00CEEBAC /* NIP05DomainTimelineHeaderViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05DomainTimelineHeaderViewTests.swift; sourceTree = "<group>"; };
|
||||||
3A93342929884CA600D6A8F3 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3A93342929884CA600D6A8F3 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3A93342A29884CA600D6A8F3 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3A93342A29884CA600D6A8F3 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3A93342B29884CA600D6A8F3 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pl-PL"; path = "pl-PL.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3A93342B29884CA600D6A8F3 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pl-PL"; path = "pl-PL.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
@@ -1859,6 +1921,7 @@
|
|||||||
3A994C4E2BE5B9370019F632 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3A994C4E2BE5B9370019F632 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3AA247FE297E3D900090C62D /* RepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostsView.swift; sourceTree = "<group>"; };
|
3AA247FE297E3D900090C62D /* RepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostsView.swift; sourceTree = "<group>"; };
|
||||||
3AA24801297E3DC20090C62D /* RepostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostView.swift; sourceTree = "<group>"; };
|
3AA24801297E3DC20090C62D /* RepostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostView.swift; sourceTree = "<group>"; };
|
||||||
|
3AA2F4E72DF1467A00B18606 /* TrustedNetworkButtonTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedNetworkButtonTip.swift; sourceTree = "<group>"; };
|
||||||
3AA59D1C2999B0400061C48E /* DraftsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsModel.swift; sourceTree = "<group>"; };
|
3AA59D1C2999B0400061C48E /* DraftsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsModel.swift; sourceTree = "<group>"; };
|
||||||
3AA5E70229B682A5002701ED /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3AA5E70229B682A5002701ED /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3AA5E70329B682AD002701ED /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
3AA5E70329B682AD002701ED /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
@@ -1885,6 +1948,8 @@
|
|||||||
3ACB685B297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3ACB685B297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3ACB685E297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3ACB685E297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; sourceTree = "<group>"; };
|
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; sourceTree = "<group>"; };
|
||||||
|
3ACF94412DA9FCAB00971A4E /* NIP05DomainTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05DomainTimelineView.swift; sourceTree = "<group>"; };
|
||||||
|
3ACF94452DAA006500971A4E /* NIP05DomainEventsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05DomainEventsModel.swift; sourceTree = "<group>"; };
|
||||||
3AD14EB529C40F38009D2D9C /* hu-HU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "hu-HU"; path = "hu-HU.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3AD14EB529C40F38009D2D9C /* hu-HU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "hu-HU"; path = "hu-HU.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
3AD14EB629C40F38009D2D9C /* hu-HU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "hu-HU"; path = "hu-HU.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3AD14EB629C40F38009D2D9C /* hu-HU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "hu-HU"; path = "hu-HU.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3AD14EB729C40F38009D2D9C /* hu-HU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "hu-HU"; path = "hu-HU.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3AD14EB729C40F38009D2D9C /* hu-HU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "hu-HU"; path = "hu-HU.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
@@ -1919,6 +1984,7 @@
|
|||||||
4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = "<group>"; };
|
4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = "<group>"; };
|
||||||
4C0C03972A61E27B0098B3B8 /* primal.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = primal.wasm; path = nostrscript/primal.wasm; sourceTree = SOURCE_ROOT; };
|
4C0C03972A61E27B0098B3B8 /* primal.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = primal.wasm; path = nostrscript/primal.wasm; sourceTree = SOURCE_ROOT; };
|
||||||
4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = bool_setting.wasm; path = nostrscript/bool_setting.wasm; sourceTree = SOURCE_ROOT; };
|
4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = bool_setting.wasm; path = nostrscript/bool_setting.wasm; sourceTree = SOURCE_ROOT; };
|
||||||
|
4C0ED07E2D7A1E260020D8A2 /* Benchmarking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Benchmarking.swift; sourceTree = "<group>"; };
|
||||||
4C12534F2A76C5B20004F4B8 /* UnfollowedNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfollowedNotify.swift; sourceTree = "<group>"; };
|
4C12534F2A76C5B20004F4B8 /* UnfollowedNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfollowedNotify.swift; sourceTree = "<group>"; };
|
||||||
4C1253512A76C6130004F4B8 /* ComposeNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeNotify.swift; sourceTree = "<group>"; };
|
4C1253512A76C6130004F4B8 /* ComposeNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeNotify.swift; sourceTree = "<group>"; };
|
||||||
4C1253532A76C7D60004F4B8 /* LogoutNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutNotify.swift; sourceTree = "<group>"; };
|
4C1253532A76C7D60004F4B8 /* LogoutNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutNotify.swift; sourceTree = "<group>"; };
|
||||||
@@ -2243,7 +2309,7 @@
|
|||||||
4C8D00D229E3C19F0036AF10 /* str_block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = str_block.h; sourceTree = "<group>"; };
|
4C8D00D229E3C19F0036AF10 /* str_block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = str_block.h; sourceTree = "<group>"; };
|
||||||
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP19Tests.swift; sourceTree = "<group>"; };
|
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP19Tests.swift; sourceTree = "<group>"; };
|
||||||
4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendIcon.swift; sourceTree = "<group>"; };
|
4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendIcon.swift; sourceTree = "<group>"; };
|
||||||
4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsButton.swift; sourceTree = "<group>"; };
|
4C8D1A6E29F31E5000ACDF75 /* TrustedNetworkButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedNetworkButton.swift; sourceTree = "<group>"; };
|
||||||
4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusColors.swift; sourceTree = "<group>"; };
|
4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusColors.swift; sourceTree = "<group>"; };
|
||||||
4C9054842A6AEAA000811EEC /* NdbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NdbTests.swift; sourceTree = "<group>"; };
|
4C9054842A6AEAA000811EEC /* NdbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NdbTests.swift; sourceTree = "<group>"; };
|
||||||
4C9054882A6AED4700811EEC /* NdbTagIterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NdbTagIterator.swift; sourceTree = "<group>"; };
|
4C9054882A6AED4700811EEC /* NdbTagIterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NdbTagIterator.swift; sourceTree = "<group>"; };
|
||||||
@@ -2404,11 +2470,16 @@
|
|||||||
5C0567542C8B60C20073F23A /* OffsetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetExtension.swift; sourceTree = "<group>"; };
|
5C0567542C8B60C20073F23A /* OffsetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetExtension.swift; sourceTree = "<group>"; };
|
||||||
5C0567572C8FBC560073F23A /* NDBSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NDBSearchView.swift; sourceTree = "<group>"; };
|
5C0567572C8FBC560073F23A /* NDBSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NDBSearchView.swift; sourceTree = "<group>"; };
|
||||||
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLogoGradient.swift; sourceTree = "<group>"; };
|
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLogoGradient.swift; sourceTree = "<group>"; };
|
||||||
|
5C09FD112DF283D200823661 /* FollowPackModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowPackModel.swift; sourceTree = "<group>"; };
|
||||||
5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySoftwareDetail.swift; sourceTree = "<group>"; };
|
5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySoftwareDetail.swift; sourceTree = "<group>"; };
|
||||||
5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayAdminDetail.swift; sourceTree = "<group>"; };
|
5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayAdminDetail.swift; sourceTree = "<group>"; };
|
||||||
5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayNipList.swift; sourceTree = "<group>"; };
|
5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayNipList.swift; sourceTree = "<group>"; };
|
||||||
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
|
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
|
||||||
5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDraftContentView.swift; sourceTree = "<group>"; };
|
5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDraftContentView.swift; sourceTree = "<group>"; };
|
||||||
|
5C4FA7EB2DC29AE900CE658C /* FollowPackEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowPackEvent.swift; sourceTree = "<group>"; };
|
||||||
|
5C4FA7F92DC29C3800CE658C /* FollowPackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowPackView.swift; sourceTree = "<group>"; };
|
||||||
|
5C4FA7FE2DC5119300CE658C /* FollowPackPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowPackPreview.swift; sourceTree = "<group>"; };
|
||||||
|
5C4FA8022DCAF80400CE658C /* FollowPackTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowPackTimeline.swift; sourceTree = "<group>"; };
|
||||||
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
|
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
|
||||||
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
|
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
|
||||||
5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButtonStyle.swift; sourceTree = "<group>"; };
|
5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
@@ -2514,6 +2585,7 @@
|
|||||||
D74AAFCE2B155D8C006CF0F4 /* ZapDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapDataModel.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WalletConnect+.swift"; sourceTree = "<group>"; };
|
||||||
|
D74E64112DC95CBE004C7892 /* HumanReadableErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HumanReadableErrors.swift; sourceTree = "<group>"; };
|
||||||
D74EA08D2D2E271E002290DD /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.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>"; };
|
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>"; };
|
D74F43092B23F0BE00425B75 /* DamusPurple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurple.swift; sourceTree = "<group>"; };
|
||||||
@@ -2543,6 +2615,7 @@
|
|||||||
D79C4C182AFEB061003A41B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 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>"; };
|
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>"; };
|
D7A0D8742D1FE66A00DCBE59 /* EditPictureControlTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPictureControlTests.swift; sourceTree = "<group>"; };
|
||||||
|
D7AACFFE2E0387B800FB7699 /* LnurlAmountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LnurlAmountView.swift; sourceTree = "<group>"; };
|
||||||
D7ADD3DD2B53854300F104C4 /* DamusPurpleURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleURL.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>"; };
|
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>"; };
|
D7ADD3E12B538E3500F104C4 /* DamusPurpleVerifyNpubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleVerifyNpubView.swift; sourceTree = "<group>"; };
|
||||||
@@ -2567,6 +2640,7 @@
|
|||||||
D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Undistractor.swift; sourceTree = "<group>"; };
|
D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Undistractor.swift; sourceTree = "<group>"; };
|
||||||
D7DB93092D69485A00DA1EE5 /* NIP65.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP65.swift; sourceTree = "<group>"; };
|
D7DB93092D69485A00DA1EE5 /* NIP65.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP65.swift; sourceTree = "<group>"; };
|
||||||
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
|
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
|
||||||
|
D7DF58312DFCF18800E9AD28 /* SendPaymentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendPaymentView.swift; sourceTree = "<group>"; };
|
||||||
D7EB00AF2CD59C8300660C07 /* PresentFullScreenItemNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentFullScreenItemNotify.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>"; };
|
D7EDED1B2B1178FE0018B19C /* NoteContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContent.swift; sourceTree = "<group>"; };
|
||||||
D7EDED1D2B11797D0018B19C /* LongformEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformEvent.swift; sourceTree = "<group>"; };
|
D7EDED1D2B11797D0018B19C /* LongformEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformEvent.swift; sourceTree = "<group>"; };
|
||||||
@@ -2574,6 +2648,7 @@
|
|||||||
D7EDED2D2B128E8A0018B19C /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = "<group>"; };
|
D7EDED2D2B128E8A0018B19C /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = "<group>"; };
|
||||||
D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusUserDefaults.swift; sourceTree = "<group>"; };
|
D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusUserDefaults.swift; sourceTree = "<group>"; };
|
||||||
D7EFBA362CC322F300F45588 /* DamusVideoControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusVideoControlsView.swift; sourceTree = "<group>"; };
|
D7EFBA362CC322F300F45588 /* DamusVideoControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusVideoControlsView.swift; sourceTree = "<group>"; };
|
||||||
|
D7FA46E42DBDAA75002C9BB0 /* ImageCacheMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCacheMigrations.swift; sourceTree = "<group>"; };
|
||||||
D7FB14212BE5970000398331 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
D7FB14212BE5970000398331 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||||
D7FB14242BE5A9A800398331 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
D7FB14242BE5A9A800398331 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||||
D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstAidSettingsView.swift; sourceTree = "<group>"; };
|
D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstAidSettingsView.swift; sourceTree = "<group>"; };
|
||||||
@@ -2609,6 +2684,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
3ACF94382DA9A52F00971A4E /* FaviconFinder in Frameworks */,
|
||||||
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
|
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
|
||||||
D7DB1FE42D5A9AC900CF06DA /* CryptoSwift in Frameworks */,
|
D7DB1FE42D5A9AC900CF06DA /* CryptoSwift in Frameworks */,
|
||||||
3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */,
|
3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */,
|
||||||
@@ -2640,6 +2716,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
3ACF94402DA9B11200971A4E /* FaviconFinder in Frameworks */,
|
||||||
82D6FC862CD9A4A600C925F4 /* MarkdownUI in Frameworks */,
|
82D6FC862CD9A4A600C925F4 /* MarkdownUI in Frameworks */,
|
||||||
D7DB1FEC2D5A9F6500CF06DA /* CryptoSwift in Frameworks */,
|
D7DB1FEC2D5A9F6500CF06DA /* CryptoSwift in Frameworks */,
|
||||||
82D6FC8A2CD9A54600C925F4 /* SwipeActions in Frameworks */,
|
82D6FC8A2CD9A54600C925F4 /* SwipeActions in Frameworks */,
|
||||||
@@ -2656,6 +2733,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D703D7AF2C670FB700A400EA /* MarkdownUI in Frameworks */,
|
D703D7AF2C670FB700A400EA /* MarkdownUI in Frameworks */,
|
||||||
|
3ACF943E2DA9B10800971A4E /* FaviconFinder in Frameworks */,
|
||||||
D73E5F9D2C6AA8E3007EB227 /* SwipeActions in Frameworks */,
|
D73E5F9D2C6AA8E3007EB227 /* SwipeActions in Frameworks */,
|
||||||
D7DB1FE82D5A9F5300CF06DA /* CryptoSwift in Frameworks */,
|
D7DB1FE82D5A9F5300CF06DA /* CryptoSwift in Frameworks */,
|
||||||
D73E5F762C6A997E007EB227 /* EmojiPicker in Frameworks */,
|
D73E5F762C6A997E007EB227 /* EmojiPicker in Frameworks */,
|
||||||
@@ -2690,6 +2768,16 @@
|
|||||||
path = "Empty Views";
|
path = "Empty Views";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
3A515C4E2DF4E0E6002D3B34 /* Tips */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3AA2F4E72DF1467A00B18606 /* TrustedNetworkButtonTip.swift */,
|
||||||
|
3A515C532DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift */,
|
||||||
|
3A515C4F2DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift */,
|
||||||
|
);
|
||||||
|
path = Tips;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
3AA24800297E3DAE0090C62D /* Reposts */ = {
|
3AA24800297E3DAE0090C62D /* Reposts */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -2770,6 +2858,8 @@
|
|||||||
4C0A3F8D280F63FF000448DE /* Models */ = {
|
4C0A3F8D280F63FF000448DE /* Models */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5C09FD112DF283D200823661 /* FollowPackModel.swift */,
|
||||||
|
5C4FA7EB2DC29AE900CE658C /* FollowPackEvent.swift */,
|
||||||
D73BDB122D71212600D69970 /* NostrNetworkManager */,
|
D73BDB122D71212600D69970 /* NostrNetworkManager */,
|
||||||
D74F43082B23F09300425B75 /* Purple */,
|
D74F43082B23F09300425B75 /* Purple */,
|
||||||
BA3759882ABCCDE30018D73B /* Camera */,
|
BA3759882ABCCDE30018D73B /* Camera */,
|
||||||
@@ -2833,6 +2923,7 @@
|
|||||||
5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */,
|
5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */,
|
||||||
D773BC5E2C6D538500349F0A /* CommentItem.swift */,
|
D773BC5E2C6D538500349F0A /* CommentItem.swift */,
|
||||||
D767066E2C8BB3CE00F09726 /* URLHandler.swift */,
|
D767066E2C8BB3CE00F09726 /* URLHandler.swift */,
|
||||||
|
3ACF94452DAA006500971A4E /* NIP05DomainEventsModel.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3184,6 +3275,7 @@
|
|||||||
4C190F232A547D1700027FD5 /* NostrScript */,
|
4C190F232A547D1700027FD5 /* NostrScript */,
|
||||||
4C7D095A2A098C5C00943473 /* Wallet */,
|
4C7D095A2A098C5C00943473 /* Wallet */,
|
||||||
4C8D1A6D29F31E4100ACDF75 /* Buttons */,
|
4C8D1A6D29F31E4100ACDF75 /* Buttons */,
|
||||||
|
3A515C4E2DF4E0E6002D3B34 /* Tips */,
|
||||||
4C1A9A2829DDF53B00516EAC /* Video */,
|
4C1A9A2829DDF53B00516EAC /* Video */,
|
||||||
4C1A9A1B29DDCF8B00516EAC /* Settings */,
|
4C1A9A1B29DDCF8B00516EAC /* Settings */,
|
||||||
4CFF8F6129CC9A80008DB934 /* Images */,
|
4CFF8F6129CC9A80008DB934 /* Images */,
|
||||||
@@ -3247,6 +3339,9 @@
|
|||||||
D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */,
|
D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */,
|
||||||
D71AD8FC2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift */,
|
D71AD8FC2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift */,
|
||||||
D74EA0922D2E77B9002290DD /* LoadableNostrEventView.swift */,
|
D74EA0922D2E77B9002290DD /* LoadableNostrEventView.swift */,
|
||||||
|
3ACF94412DA9FCAB00971A4E /* NIP05DomainTimelineView.swift */,
|
||||||
|
3A2BAC592DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift */,
|
||||||
|
3A2BAC5D2DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3283,6 +3378,8 @@
|
|||||||
4C7D095A2A098C5C00943473 /* Wallet */ = {
|
4C7D095A2A098C5C00943473 /* Wallet */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D7AACFFE2E0387B800FB7699 /* LnurlAmountView.swift */,
|
||||||
|
D7DF58312DFCF18800E9AD28 /* SendPaymentView.swift */,
|
||||||
5C8498012D5D14FA00F74FEB /* ZapExplainer.swift */,
|
5C8498012D5D14FA00F74FEB /* ZapExplainer.swift */,
|
||||||
5CB017302D4422D600A9ED05 /* NWCSettings.swift */,
|
5CB017302D4422D600A9ED05 /* NWCSettings.swift */,
|
||||||
5CB0172C2D42C76600A9ED05 /* BalanceView.swift */,
|
5CB0172C2D42C76600A9ED05 /* BalanceView.swift */,
|
||||||
@@ -3312,6 +3409,7 @@
|
|||||||
4C7FF7D628233637009601DB /* Util */ = {
|
4C7FF7D628233637009601DB /* Util */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D7FA46E42DBDAA75002C9BB0 /* ImageCacheMigrations.swift */,
|
||||||
D73B74E02D8365B40067BDBC /* ExtraFonts.swift */,
|
D73B74E02D8365B40067BDBC /* ExtraFonts.swift */,
|
||||||
D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */,
|
D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */,
|
||||||
D73E5F7E2C6AA066007EB227 /* DamusAliases.swift */,
|
D73E5F7E2C6AA066007EB227 /* DamusAliases.swift */,
|
||||||
@@ -3368,6 +3466,7 @@
|
|||||||
D74AAFCE2B155D8C006CF0F4 /* ZapDataModel.swift */,
|
D74AAFCE2B155D8C006CF0F4 /* ZapDataModel.swift */,
|
||||||
D74AAFD32B155ECB006CF0F4 /* Zaps+.swift */,
|
D74AAFD32B155ECB006CF0F4 /* Zaps+.swift */,
|
||||||
D7D09AB42DADCA5600AB170D /* CoinosDeterministicAccountClient.swift */,
|
D7D09AB42DADCA5600AB170D /* CoinosDeterministicAccountClient.swift */,
|
||||||
|
3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3376,7 +3475,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5CB017202D2D985800A9ED05 /* CoinosButton.swift */,
|
5CB017202D2D985800A9ED05 /* CoinosButton.swift */,
|
||||||
4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */,
|
4C8D1A6E29F31E5000ACDF75 /* TrustedNetworkButton.swift */,
|
||||||
F71694F32A6732B7001F4053 /* GradientFollowButton.swift */,
|
F71694F32A6732B7001F4053 /* GradientFollowButton.swift */,
|
||||||
4C7D09652A0AE62100943473 /* AlbyButton.swift */,
|
4C7D09652A0AE62100943473 /* AlbyButton.swift */,
|
||||||
);
|
);
|
||||||
@@ -3570,6 +3669,7 @@
|
|||||||
4CC7AAEE297F11B300430951 /* Events */ = {
|
4CC7AAEE297F11B300430951 /* Events */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5C4FA7FA2DC29C3800CE658C /* FollowPack */,
|
||||||
5CC852A02BDED9970039FFC5 /* Highlight */,
|
5CC852A02BDED9970039FFC5 /* Highlight */,
|
||||||
4CA927682A290F8F0098A105 /* Components */,
|
4CA927682A290F8F0098A105 /* Components */,
|
||||||
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
||||||
@@ -3763,6 +3863,8 @@
|
|||||||
4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */,
|
4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */,
|
||||||
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */,
|
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */,
|
||||||
3A96E3FD2D6BCE3800AE1630 /* RepostedTests.swift */,
|
3A96E3FD2D6BCE3800AE1630 /* RepostedTests.swift */,
|
||||||
|
4C0ED07E2D7A1E260020D8A2 /* Benchmarking.swift */,
|
||||||
|
3A92C1012DE17ACA00CEEBAC /* NIP05DomainTimelineHeaderViewTests.swift */,
|
||||||
);
|
);
|
||||||
path = damusTests;
|
path = damusTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3883,6 +3985,16 @@
|
|||||||
path = Images;
|
path = Images;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
5C4FA7FA2DC29C3800CE658C /* FollowPack */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5C4FA8022DCAF80400CE658C /* FollowPackTimeline.swift */,
|
||||||
|
5C4FA7FE2DC5119300CE658C /* FollowPackPreview.swift */,
|
||||||
|
5C4FA7F92DC29C3800CE658C /* FollowPackView.swift */,
|
||||||
|
);
|
||||||
|
path = FollowPack;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
5CC852A02BDED9970039FFC5 /* Highlight */ = {
|
5CC852A02BDED9970039FFC5 /* Highlight */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -4035,6 +4147,7 @@
|
|||||||
D78F080A2D7F78B000FC6C75 /* WalletConnect */ = {
|
D78F080A2D7F78B000FC6C75 /* WalletConnect */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D74E64112DC95CBE004C7892 /* HumanReadableErrors.swift */,
|
||||||
D78F08102D7F78F600FC6C75 /* Response.swift */,
|
D78F08102D7F78F600FC6C75 /* Response.swift */,
|
||||||
D78F080B2D7F78EB00FC6C75 /* Request.swift */,
|
D78F080B2D7F78EB00FC6C75 /* Request.swift */,
|
||||||
4C7D09612A098D0E00943473 /* WalletConnect.swift */,
|
4C7D09612A098D0E00943473 /* WalletConnect.swift */,
|
||||||
@@ -4163,6 +4276,7 @@
|
|||||||
D70D90972CDED61800CD0534 /* CodeScanner */,
|
D70D90972CDED61800CD0534 /* CodeScanner */,
|
||||||
D7C48C0A2D12DE0C00A3BACF /* SwiftyCrop */,
|
D7C48C0A2D12DE0C00A3BACF /* SwiftyCrop */,
|
||||||
D7DB1FE32D5A9AC900CF06DA /* CryptoSwift */,
|
D7DB1FE32D5A9AC900CF06DA /* CryptoSwift */,
|
||||||
|
3ACF94372DA9A52F00971A4E /* FaviconFinder */,
|
||||||
);
|
);
|
||||||
productName = damus;
|
productName = damus;
|
||||||
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
|
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
|
||||||
@@ -4230,6 +4344,7 @@
|
|||||||
D7F360282CEBBE34009D34DA /* CodeScanner */,
|
D7F360282CEBBE34009D34DA /* CodeScanner */,
|
||||||
D7C48C0C2D12E34900A3BACF /* SwiftyCrop */,
|
D7C48C0C2D12E34900A3BACF /* SwiftyCrop */,
|
||||||
D7DB1FEB2D5A9F6500CF06DA /* CryptoSwift */,
|
D7DB1FEB2D5A9F6500CF06DA /* CryptoSwift */,
|
||||||
|
3ACF943F2DA9B11200971A4E /* FaviconFinder */,
|
||||||
);
|
);
|
||||||
productName = "share extension";
|
productName = "share extension";
|
||||||
productReference = 82D6FA972CD9820500C925F4 /* ShareExtension.appex */;
|
productReference = 82D6FA972CD9820500C925F4 /* ShareExtension.appex */;
|
||||||
@@ -4259,6 +4374,7 @@
|
|||||||
D70D909B2CDED7B200CD0534 /* CodeScanner */,
|
D70D909B2CDED7B200CD0534 /* CodeScanner */,
|
||||||
D7C48C0E2D12E35600A3BACF /* SwiftyCrop */,
|
D7C48C0E2D12E35600A3BACF /* SwiftyCrop */,
|
||||||
D7DB1FE72D5A9F5300CF06DA /* CryptoSwift */,
|
D7DB1FE72D5A9F5300CF06DA /* CryptoSwift */,
|
||||||
|
3ACF943D2DA9B10800971A4E /* FaviconFinder */,
|
||||||
);
|
);
|
||||||
productName = "highlighter action extension";
|
productName = "highlighter action extension";
|
||||||
productReference = D703D7172C66E47100A400EA /* HighlighterActionExtension.appex */;
|
productReference = D703D7172C66E47100A400EA /* HighlighterActionExtension.appex */;
|
||||||
@@ -4371,6 +4487,7 @@
|
|||||||
D70D90962CDED61800CD0534 /* XCRemoteSwiftPackageReference "CodeScanner" */,
|
D70D90962CDED61800CD0534 /* XCRemoteSwiftPackageReference "CodeScanner" */,
|
||||||
D7C48C092D12DE0C00A3BACF /* XCRemoteSwiftPackageReference "SwiftyCrop" */,
|
D7C48C092D12DE0C00A3BACF /* XCRemoteSwiftPackageReference "SwiftyCrop" */,
|
||||||
D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */,
|
D7DB1FE22D5A9AC900CF06DA /* XCRemoteSwiftPackageReference "CryptoSwift" */,
|
||||||
|
3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
|
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@@ -4513,6 +4630,7 @@
|
|||||||
4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
|
4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
|
||||||
5C8498032D5D150000F74FEB /* ZapExplainer.swift in Sources */,
|
5C8498032D5D150000F74FEB /* ZapExplainer.swift in Sources */,
|
||||||
504323A72A34915F006AE6DC /* RelayModel.swift in Sources */,
|
504323A72A34915F006AE6DC /* RelayModel.swift in Sources */,
|
||||||
|
3A2BAC5C2DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift in Sources */,
|
||||||
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */,
|
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */,
|
||||||
4C32B9542A9AD44700DC3548 /* FlatBuffersUtils.swift in Sources */,
|
4C32B9542A9AD44700DC3548 /* FlatBuffersUtils.swift in Sources */,
|
||||||
D7EDED1C2B1178FE0018B19C /* NoteContent.swift in Sources */,
|
D7EDED1C2B1178FE0018B19C /* NoteContent.swift in Sources */,
|
||||||
@@ -4551,6 +4669,7 @@
|
|||||||
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
|
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
|
||||||
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
||||||
D798D2282B085CDA00234419 /* NdbNote+.swift in Sources */,
|
D798D2282B085CDA00234419 /* NdbNote+.swift in Sources */,
|
||||||
|
3ACF94422DA9FCAB00971A4E /* NIP05DomainTimelineView.swift in Sources */,
|
||||||
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
|
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
|
||||||
4C1253662A76D0FF0004F4B8 /* OnlyZapsNotify.swift in Sources */,
|
4C1253662A76D0FF0004F4B8 /* OnlyZapsNotify.swift in Sources */,
|
||||||
4CA927652A290F1A0098A105 /* TimeDot.swift in Sources */,
|
4CA927652A290F1A0098A105 /* TimeDot.swift in Sources */,
|
||||||
@@ -4583,6 +4702,7 @@
|
|||||||
4C363A9028247A1D006E126D /* NostrLink.swift in Sources */,
|
4C363A9028247A1D006E126D /* NostrLink.swift in Sources */,
|
||||||
4C3D52B6298DB4E6001C5831 /* ZapEvent.swift in Sources */,
|
4C3D52B6298DB4E6001C5831 /* ZapEvent.swift in Sources */,
|
||||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
|
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
|
||||||
|
D7AAD0012E0387B800FB7699 /* LnurlAmountView.swift in Sources */,
|
||||||
F7F0BA272978E54D009531F3 /* ParticipantsView.swift in Sources */,
|
F7F0BA272978E54D009531F3 /* ParticipantsView.swift in Sources */,
|
||||||
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
||||||
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */,
|
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */,
|
||||||
@@ -4593,7 +4713,7 @@
|
|||||||
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
||||||
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
||||||
D74AAFCF2B155D8C006CF0F4 /* ZapDataModel.swift in Sources */,
|
D74AAFCF2B155D8C006CF0F4 /* ZapDataModel.swift in Sources */,
|
||||||
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */,
|
4C8D1A6F29F31E5000ACDF75 /* TrustedNetworkButton.swift in Sources */,
|
||||||
D7100C562B76F8E600C59298 /* PurpleViewPrimitives.swift in Sources */,
|
D7100C562B76F8E600C59298 /* PurpleViewPrimitives.swift in Sources */,
|
||||||
B57B4C642B312BFA00A232C0 /* RelayAuthenticationDetail.swift in Sources */,
|
B57B4C642B312BFA00A232C0 /* RelayAuthenticationDetail.swift in Sources */,
|
||||||
D7EDED2E2B128E8A0018B19C /* CollectionExtension.swift in Sources */,
|
D7EDED2E2B128E8A0018B19C /* CollectionExtension.swift in Sources */,
|
||||||
@@ -4602,6 +4722,7 @@
|
|||||||
BA3759972ABCCF360018D73B /* CameraPreview.swift in Sources */,
|
BA3759972ABCCF360018D73B /* CameraPreview.swift in Sources */,
|
||||||
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
||||||
4CA927672A290F8B0098A105 /* RelativeTime.swift in Sources */,
|
4CA927672A290F8B0098A105 /* RelativeTime.swift in Sources */,
|
||||||
|
5C4FA8032DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */,
|
||||||
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
|
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
|
||||||
D7CB5D4B2B11721600AD4105 /* ZapType.swift in Sources */,
|
D7CB5D4B2B11721600AD4105 /* ZapType.swift in Sources */,
|
||||||
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */,
|
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */,
|
||||||
@@ -4675,9 +4796,11 @@
|
|||||||
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
||||||
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
||||||
BA3759932ABCCEBA0018D73B /* CameraModel.swift in Sources */,
|
BA3759932ABCCEBA0018D73B /* CameraModel.swift in Sources */,
|
||||||
|
D7FA46E72DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */,
|
||||||
D7100C5A2B76FD5100C59298 /* LogoView.swift in Sources */,
|
D7100C5A2B76FD5100C59298 /* LogoView.swift in Sources */,
|
||||||
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
||||||
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
||||||
|
3A515C502DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */,
|
||||||
D7CB5D3B2B112FBB00AD4105 /* NotificationFormatter.swift in Sources */,
|
D7CB5D3B2B112FBB00AD4105 /* NotificationFormatter.swift in Sources */,
|
||||||
4C4E137B2A76D5FB00BDD832 /* MuteThreadNotify.swift in Sources */,
|
4C4E137B2A76D5FB00BDD832 /* MuteThreadNotify.swift in Sources */,
|
||||||
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
||||||
@@ -4688,6 +4811,7 @@
|
|||||||
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */,
|
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */,
|
||||||
D7373BA82B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift in Sources */,
|
D7373BA82B68974500F7783D /* DamusPurpleNewUserOnboardingView.swift in Sources */,
|
||||||
4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */,
|
4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */,
|
||||||
|
D74E64152DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */,
|
||||||
D7CB5D452B116FE800AD4105 /* Contacts+.swift in Sources */,
|
D7CB5D452B116FE800AD4105 /* Contacts+.swift in Sources */,
|
||||||
4CA352A42A76AFF3003BB08B /* UpdateStatsNotify.swift in Sources */,
|
4CA352A42A76AFF3003BB08B /* UpdateStatsNotify.swift in Sources */,
|
||||||
D798D21E2B0858BB00234419 /* MigratedTypes.swift in Sources */,
|
D798D21E2B0858BB00234419 /* MigratedTypes.swift in Sources */,
|
||||||
@@ -4744,7 +4868,9 @@
|
|||||||
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
||||||
4C12535E2A76CA870004F4B8 /* SwitchedTimelineNotify.swift in Sources */,
|
4C12535E2A76CA870004F4B8 /* SwitchedTimelineNotify.swift in Sources */,
|
||||||
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */,
|
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */,
|
||||||
|
5C4FA7ED2DC29AE900CE658C /* FollowPackEvent.swift in Sources */,
|
||||||
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */,
|
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */,
|
||||||
|
3ACF94462DAA006500971A4E /* NIP05DomainEventsModel.swift in Sources */,
|
||||||
D734B1452CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */,
|
D734B1452CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */,
|
||||||
4C4E137D2A76D63600BDD832 /* UnmuteThreadNotify.swift in Sources */,
|
4C4E137D2A76D63600BDD832 /* UnmuteThreadNotify.swift in Sources */,
|
||||||
D706C5B72D602A110027C627 /* QueueableNotify.swift in Sources */,
|
D706C5B72D602A110027C627 /* QueueableNotify.swift in Sources */,
|
||||||
@@ -4770,6 +4896,7 @@
|
|||||||
4CA352A22A76AEC5003BB08B /* LikedNotify.swift in Sources */,
|
4CA352A22A76AEC5003BB08B /* LikedNotify.swift in Sources */,
|
||||||
5CC8529F2BD744F60039FFC5 /* HighlightView.swift in Sources */,
|
5CC8529F2BD744F60039FFC5 /* HighlightView.swift in Sources */,
|
||||||
BA37598D2ABCCE500018D73B /* PhotoCaptureProcessor.swift in Sources */,
|
BA37598D2ABCCE500018D73B /* PhotoCaptureProcessor.swift in Sources */,
|
||||||
|
3A515C562DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */,
|
||||||
5CC8529D2BD741CD0039FFC5 /* HighlightEvent.swift in Sources */,
|
5CC8529D2BD741CD0039FFC5 /* HighlightEvent.swift in Sources */,
|
||||||
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */,
|
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */,
|
||||||
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
||||||
@@ -4816,6 +4943,7 @@
|
|||||||
4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */,
|
4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */,
|
||||||
BA3759942ABCCEBA0018D73B /* CameraService.swift in Sources */,
|
BA3759942ABCCEBA0018D73B /* CameraService.swift in Sources */,
|
||||||
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
|
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
|
||||||
|
3AA2F4E82DF1467A00B18606 /* TrustedNetworkButtonTip.swift in Sources */,
|
||||||
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
|
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
|
||||||
F71694EA2A662232001F4053 /* OnboardingSuggestionsView.swift in Sources */,
|
F71694EA2A662232001F4053 /* OnboardingSuggestionsView.swift in Sources */,
|
||||||
4C12536A2A76D3850004F4B8 /* RelaysChangedNotify.swift in Sources */,
|
4C12536A2A76D3850004F4B8 /* RelaysChangedNotify.swift in Sources */,
|
||||||
@@ -4823,6 +4951,7 @@
|
|||||||
D7373BAA2B68A65A00F7783D /* PurpleAccountUpdateNotify.swift in Sources */,
|
D7373BAA2B68A65A00F7783D /* PurpleAccountUpdateNotify.swift in Sources */,
|
||||||
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
|
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
|
||||||
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */,
|
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */,
|
||||||
|
5C4FA7FF2DC5119300CE658C /* FollowPackPreview.swift in Sources */,
|
||||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */,
|
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */,
|
||||||
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
|
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
|
||||||
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */,
|
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */,
|
||||||
@@ -4853,6 +4982,7 @@
|
|||||||
3165648B295B70D500C64604 /* LinkView.swift in Sources */,
|
3165648B295B70D500C64604 /* LinkView.swift in Sources */,
|
||||||
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */,
|
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */,
|
||||||
D7CB5D5C2B1176B200AD4105 /* MediaUploader.swift in Sources */,
|
D7CB5D5C2B1176B200AD4105 /* MediaUploader.swift in Sources */,
|
||||||
|
5C4FA7FD2DC29C3800CE658C /* FollowPackView.swift in Sources */,
|
||||||
4C1253562A76C8C60004F4B8 /* BroadcastNotify.swift in Sources */,
|
4C1253562A76C8C60004F4B8 /* BroadcastNotify.swift in Sources */,
|
||||||
4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */,
|
4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */,
|
||||||
B533694E2B66D791008A805E /* MutelistManager.swift in Sources */,
|
B533694E2B66D791008A805E /* MutelistManager.swift in Sources */,
|
||||||
@@ -4880,6 +5010,7 @@
|
|||||||
4C3AC7A728369BA200E1F516 /* SearchHomeView.swift in Sources */,
|
4C3AC7A728369BA200E1F516 /* SearchHomeView.swift in Sources */,
|
||||||
E0EE9DD42B8E5FEA00F3002D /* ImageProcessing.swift in Sources */,
|
E0EE9DD42B8E5FEA00F3002D /* ImageProcessing.swift in Sources */,
|
||||||
4CB883B0297705DD00DC99E7 /* NoteZapButton.swift in Sources */,
|
4CB883B0297705DD00DC99E7 /* NoteZapButton.swift in Sources */,
|
||||||
|
D7DF58342DFCF18D00E9AD28 /* SendPaymentView.swift in Sources */,
|
||||||
4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */,
|
4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */,
|
||||||
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
|
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
|
||||||
4C32B9502A9AD44700DC3548 /* FlatBufferBuilder.swift in Sources */,
|
4C32B9502A9AD44700DC3548 /* FlatBufferBuilder.swift in Sources */,
|
||||||
@@ -4892,6 +5023,7 @@
|
|||||||
4C32B9512A9AD44700DC3548 /* FlatbuffersErrors.swift in Sources */,
|
4C32B9512A9AD44700DC3548 /* FlatbuffersErrors.swift in Sources */,
|
||||||
5CC852A62BE00F180039FFC5 /* HighlightEventRef.swift in Sources */,
|
5CC852A62BE00F180039FFC5 /* HighlightEventRef.swift in Sources */,
|
||||||
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */,
|
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */,
|
||||||
|
5C09FD132DF283D700823661 /* FollowPackModel.swift in Sources */,
|
||||||
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
||||||
D71AD8FF2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
D71AD8FF2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
||||||
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
||||||
@@ -4904,6 +5036,7 @@
|
|||||||
4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */,
|
4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */,
|
||||||
4C32B9592A9AD44700DC3548 /* Table.swift in Sources */,
|
4C32B9592A9AD44700DC3548 /* Table.swift in Sources */,
|
||||||
4C5D5C9D2A6B2CB40024563C /* AsciiCharacter.swift in Sources */,
|
4C5D5C9D2A6B2CB40024563C /* AsciiCharacter.swift in Sources */,
|
||||||
|
3A2BAC5E2DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift in Sources */,
|
||||||
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
|
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
|
||||||
4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */,
|
4C9146FE2A2A87C200DDEA40 /* nostrscript.c in Sources */,
|
||||||
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
||||||
@@ -4951,6 +5084,7 @@
|
|||||||
4C32B95A2A9AD44700DC3548 /* Verifiable.swift in Sources */,
|
4C32B95A2A9AD44700DC3548 /* Verifiable.swift in Sources */,
|
||||||
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */,
|
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */,
|
||||||
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */,
|
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */,
|
||||||
|
3A92C0FE2DE16E9800CEEBAC /* FaviconCache.swift in Sources */,
|
||||||
4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */,
|
4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */,
|
||||||
5CC868DD2AA29B3200FB22BA /* NeutralButtonStyle.swift in Sources */,
|
5CC868DD2AA29B3200FB22BA /* NeutralButtonStyle.swift in Sources */,
|
||||||
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
||||||
@@ -4990,6 +5124,7 @@
|
|||||||
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */,
|
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */,
|
||||||
4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */,
|
4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */,
|
||||||
D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */,
|
D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */,
|
||||||
|
4C0ED07F2D7A1E260020D8A2 /* Benchmarking.swift in Sources */,
|
||||||
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */,
|
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */,
|
||||||
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */,
|
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */,
|
||||||
3AAC7A022A60FE72002B50DF /* LocalizationUtilTests.swift in Sources */,
|
3AAC7A022A60FE72002B50DF /* LocalizationUtilTests.swift in Sources */,
|
||||||
@@ -5033,6 +5168,7 @@
|
|||||||
4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */,
|
4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */,
|
||||||
4C684A552A7E91FE005E6031 /* LargeEventTests.swift in Sources */,
|
4C684A552A7E91FE005E6031 /* LargeEventTests.swift in Sources */,
|
||||||
E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */,
|
E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */,
|
||||||
|
3A92C1022DE17ACA00CEEBAC /* NIP05DomainTimelineHeaderViewTests.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -5049,6 +5185,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
5C4FA7FB2DC29C3800CE658C /* FollowPackView.swift in Sources */,
|
||||||
D7F360262CEBBD8B009D34DA /* PresentFullScreenItemNotify.swift in Sources */,
|
D7F360262CEBBD8B009D34DA /* PresentFullScreenItemNotify.swift in Sources */,
|
||||||
82D6FA9A2CD9820500C925F4 /* ShareViewController.swift in Sources */,
|
82D6FA9A2CD9820500C925F4 /* ShareViewController.swift in Sources */,
|
||||||
82D6FAA92CD99F7900C925F4 /* FbConstants.swift in Sources */,
|
82D6FAA92CD99F7900C925F4 /* FbConstants.swift in Sources */,
|
||||||
@@ -5065,6 +5202,7 @@
|
|||||||
82D6FAB42CD99F7900C925F4 /* Verifiable.swift in Sources */,
|
82D6FAB42CD99F7900C925F4 /* Verifiable.swift in Sources */,
|
||||||
82D6FAB52CD99F7900C925F4 /* NativeObject.swift in Sources */,
|
82D6FAB52CD99F7900C925F4 /* NativeObject.swift in Sources */,
|
||||||
82D6FAB62CD99F7900C925F4 /* String+extension.swift in Sources */,
|
82D6FAB62CD99F7900C925F4 /* String+extension.swift in Sources */,
|
||||||
|
3A515C552DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */,
|
||||||
82D6FAB72CD99F7900C925F4 /* FlatBufferObject.swift in Sources */,
|
82D6FAB72CD99F7900C925F4 /* FlatBufferObject.swift in Sources */,
|
||||||
82D6FAB82CD99F7900C925F4 /* Enum.swift in Sources */,
|
82D6FAB82CD99F7900C925F4 /* Enum.swift in Sources */,
|
||||||
82D6FAB92CD99F7900C925F4 /* builder.c in Sources */,
|
82D6FAB92CD99F7900C925F4 /* builder.c in Sources */,
|
||||||
@@ -5115,9 +5253,11 @@
|
|||||||
82D6FAE42CD99F7900C925F4 /* FollowNotify.swift in Sources */,
|
82D6FAE42CD99F7900C925F4 /* FollowNotify.swift in Sources */,
|
||||||
82D6FAE52CD99F7900C925F4 /* LikedNotify.swift in Sources */,
|
82D6FAE52CD99F7900C925F4 /* LikedNotify.swift in Sources */,
|
||||||
82D6FAE62CD99F7900C925F4 /* LocalNotificationNotify.swift in Sources */,
|
82D6FAE62CD99F7900C925F4 /* LocalNotificationNotify.swift in Sources */,
|
||||||
|
5C4FA8012DC5119300CE658C /* FollowPackPreview.swift in Sources */,
|
||||||
82D6FAE72CD99F7900C925F4 /* LoginNotify.swift in Sources */,
|
82D6FAE72CD99F7900C925F4 /* LoginNotify.swift in Sources */,
|
||||||
82D6FAE82CD99F7900C925F4 /* LogoutNotify.swift in Sources */,
|
82D6FAE82CD99F7900C925F4 /* LogoutNotify.swift in Sources */,
|
||||||
D706C5B12D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */,
|
D706C5B12D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */,
|
||||||
|
3ACF94482DAA006500971A4E /* NIP05DomainEventsModel.swift in Sources */,
|
||||||
82D6FAE92CD99F7900C925F4 /* NewMutesNotify.swift in Sources */,
|
82D6FAE92CD99F7900C925F4 /* NewMutesNotify.swift in Sources */,
|
||||||
82D6FAEA2CD99F7900C925F4 /* NewUnmutesNotify.swift in Sources */,
|
82D6FAEA2CD99F7900C925F4 /* NewUnmutesNotify.swift in Sources */,
|
||||||
82D6FAEB2CD99F7900C925F4 /* Notify.swift in Sources */,
|
82D6FAEB2CD99F7900C925F4 /* Notify.swift in Sources */,
|
||||||
@@ -5125,6 +5265,7 @@
|
|||||||
82D6FAED2CD99F7900C925F4 /* PostNotify.swift in Sources */,
|
82D6FAED2CD99F7900C925F4 /* PostNotify.swift in Sources */,
|
||||||
82D6FAEE2CD99F7900C925F4 /* PresentSheetNotify.swift in Sources */,
|
82D6FAEE2CD99F7900C925F4 /* PresentSheetNotify.swift in Sources */,
|
||||||
D74EA0932D2E77B9002290DD /* LoadableNostrEventView.swift in Sources */,
|
D74EA0932D2E77B9002290DD /* LoadableNostrEventView.swift in Sources */,
|
||||||
|
D74E64142DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */,
|
||||||
82D6FAEF2CD99F7900C925F4 /* ProfileUpdatedNotify.swift in Sources */,
|
82D6FAEF2CD99F7900C925F4 /* ProfileUpdatedNotify.swift in Sources */,
|
||||||
82D6FAF02CD99F7900C925F4 /* ReportNotify.swift in Sources */,
|
82D6FAF02CD99F7900C925F4 /* ReportNotify.swift in Sources */,
|
||||||
82D6FAF12CD99F7900C925F4 /* ScrollToTopNotify.swift in Sources */,
|
82D6FAF12CD99F7900C925F4 /* ScrollToTopNotify.swift in Sources */,
|
||||||
@@ -5135,13 +5276,16 @@
|
|||||||
82D6FAF62CD99F7900C925F4 /* ZappingNotify.swift in Sources */,
|
82D6FAF62CD99F7900C925F4 /* ZappingNotify.swift in Sources */,
|
||||||
82D6FAF72CD99F7900C925F4 /* MuteNotify.swift in Sources */,
|
82D6FAF72CD99F7900C925F4 /* MuteNotify.swift in Sources */,
|
||||||
82D6FAF82CD99F7900C925F4 /* RelaysChangedNotify.swift in Sources */,
|
82D6FAF82CD99F7900C925F4 /* RelaysChangedNotify.swift in Sources */,
|
||||||
|
3A2BAC5B2DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift in Sources */,
|
||||||
82D6FAF92CD99F7900C925F4 /* MuteThreadNotify.swift in Sources */,
|
82D6FAF92CD99F7900C925F4 /* MuteThreadNotify.swift in Sources */,
|
||||||
82D6FAFA2CD99F7900C925F4 /* UnmuteThreadNotify.swift in Sources */,
|
82D6FAFA2CD99F7900C925F4 /* UnmuteThreadNotify.swift in Sources */,
|
||||||
82D6FAFB2CD99F7900C925F4 /* ReconnectRelaysNotify.swift in Sources */,
|
82D6FAFB2CD99F7900C925F4 /* ReconnectRelaysNotify.swift in Sources */,
|
||||||
|
3ACF94432DA9FCAB00971A4E /* NIP05DomainTimelineView.swift in Sources */,
|
||||||
82D6FAFC2CD99F7900C925F4 /* PurpleAccountUpdateNotify.swift in Sources */,
|
82D6FAFC2CD99F7900C925F4 /* PurpleAccountUpdateNotify.swift in Sources */,
|
||||||
82D6FAFD2CD99F7900C925F4 /* IdType.swift in Sources */,
|
82D6FAFD2CD99F7900C925F4 /* IdType.swift in Sources */,
|
||||||
82D6FAFE2CD99F7900C925F4 /* Pubkey.swift in Sources */,
|
82D6FAFE2CD99F7900C925F4 /* Pubkey.swift in Sources */,
|
||||||
82D6FAFF2CD99F7900C925F4 /* NoteId.swift in Sources */,
|
82D6FAFF2CD99F7900C925F4 /* NoteId.swift in Sources */,
|
||||||
|
D7DF58332DFCF18D00E9AD28 /* SendPaymentView.swift in Sources */,
|
||||||
82D6FB002CD99F7900C925F4 /* Referenced.swift in Sources */,
|
82D6FB002CD99F7900C925F4 /* Referenced.swift in Sources */,
|
||||||
5CB0172D2D42C76A00A9ED05 /* BalanceView.swift in Sources */,
|
5CB0172D2D42C76A00A9ED05 /* BalanceView.swift in Sources */,
|
||||||
82D6FB012CD99F7900C925F4 /* Block.swift in Sources */,
|
82D6FB012CD99F7900C925F4 /* Block.swift in Sources */,
|
||||||
@@ -5163,6 +5307,7 @@
|
|||||||
82D6FB0F2CD99F7900C925F4 /* DamusLogoGradient.swift in Sources */,
|
82D6FB0F2CD99F7900C925F4 /* DamusLogoGradient.swift in Sources */,
|
||||||
82D6FB102CD99F7900C925F4 /* DamusBackground.swift in Sources */,
|
82D6FB102CD99F7900C925F4 /* DamusBackground.swift in Sources */,
|
||||||
82D6FB112CD99F7900C925F4 /* DamusLightGradient.swift in Sources */,
|
82D6FB112CD99F7900C925F4 /* DamusLightGradient.swift in Sources */,
|
||||||
|
5C4FA8042DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */,
|
||||||
82D6FB132CD99F7900C925F4 /* Shimmer.swift in Sources */,
|
82D6FB132CD99F7900C925F4 /* Shimmer.swift in Sources */,
|
||||||
82D6FB142CD99F7900C925F4 /* EndBlock.swift in Sources */,
|
82D6FB142CD99F7900C925F4 /* EndBlock.swift in Sources */,
|
||||||
82D6FB152CD99F7900C925F4 /* ImageCarousel.swift in Sources */,
|
82D6FB152CD99F7900C925F4 /* ImageCarousel.swift in Sources */,
|
||||||
@@ -5217,6 +5362,7 @@
|
|||||||
82D6FB432CD99F7900C925F4 /* KeychainStorage.swift in Sources */,
|
82D6FB432CD99F7900C925F4 /* KeychainStorage.swift in Sources */,
|
||||||
82D6FB442CD99F7900C925F4 /* Bech32.swift in Sources */,
|
82D6FB442CD99F7900C925F4 /* Bech32.swift in Sources */,
|
||||||
82D6FB452CD99F7900C925F4 /* InputDismissKeyboard.swift in Sources */,
|
82D6FB452CD99F7900C925F4 /* InputDismissKeyboard.swift in Sources */,
|
||||||
|
D7FA46E62DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */,
|
||||||
82D6FB462CD99F7900C925F4 /* Constants.swift in Sources */,
|
82D6FB462CD99F7900C925F4 /* Constants.swift in Sources */,
|
||||||
82D6FB472CD99F7900C925F4 /* LinkView.swift in Sources */,
|
82D6FB472CD99F7900C925F4 /* LinkView.swift in Sources */,
|
||||||
D7DB1FDF2D5A78CE00CF06DA /* NIP44.swift in Sources */,
|
D7DB1FDF2D5A78CE00CF06DA /* NIP44.swift in Sources */,
|
||||||
@@ -5308,7 +5454,9 @@
|
|||||||
82D6FB9B2CD99F7900C925F4 /* MutedThreadsManager.swift in Sources */,
|
82D6FB9B2CD99F7900C925F4 /* MutedThreadsManager.swift in Sources */,
|
||||||
82D6FB9C2CD99F7900C925F4 /* WalletModel.swift in Sources */,
|
82D6FB9C2CD99F7900C925F4 /* WalletModel.swift in Sources */,
|
||||||
82D6FB9D2CD99F7900C925F4 /* ZapButtonModel.swift in Sources */,
|
82D6FB9D2CD99F7900C925F4 /* ZapButtonModel.swift in Sources */,
|
||||||
|
5C09FD142DF283D700823661 /* FollowPackModel.swift in Sources */,
|
||||||
82D6FB9E2CD99F7900C925F4 /* ContentFilters.swift in Sources */,
|
82D6FB9E2CD99F7900C925F4 /* ContentFilters.swift in Sources */,
|
||||||
|
3A515C512DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */,
|
||||||
82D6FB9F2CD99F7900C925F4 /* DamusCacheManager.swift in Sources */,
|
82D6FB9F2CD99F7900C925F4 /* DamusCacheManager.swift in Sources */,
|
||||||
82D6FBA02CD99F7900C925F4 /* NotificationsManager.swift in Sources */,
|
82D6FBA02CD99F7900C925F4 /* NotificationsManager.swift in Sources */,
|
||||||
D755B28E2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */,
|
D755B28E2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */,
|
||||||
@@ -5359,13 +5507,14 @@
|
|||||||
82D6FBCC2CD99F7900C925F4 /* CameraPreview.swift in Sources */,
|
82D6FBCC2CD99F7900C925F4 /* CameraPreview.swift in Sources */,
|
||||||
82D6FBCD2CD99F7900C925F4 /* CameraController.swift in Sources */,
|
82D6FBCD2CD99F7900C925F4 /* CameraController.swift in Sources */,
|
||||||
82D6FBCE2CD99F7900C925F4 /* OnboardingSuggestionsView.swift in Sources */,
|
82D6FBCE2CD99F7900C925F4 /* OnboardingSuggestionsView.swift in Sources */,
|
||||||
|
3AA2F4EA2DF1467A00B18606 /* TrustedNetworkButtonTip.swift in Sources */,
|
||||||
82D6FBCF2CD99F7900C925F4 /* SuggestedUserView.swift in Sources */,
|
82D6FBCF2CD99F7900C925F4 /* SuggestedUserView.swift in Sources */,
|
||||||
82D6FBD02CD99F7900C925F4 /* SuggestedUsersViewModel.swift in Sources */,
|
82D6FBD02CD99F7900C925F4 /* SuggestedUsersViewModel.swift in Sources */,
|
||||||
82D6FBD12CD99F7900C925F4 /* LoadScript.swift in Sources */,
|
82D6FBD12CD99F7900C925F4 /* LoadScript.swift in Sources */,
|
||||||
82D6FBD52CD99F7900C925F4 /* ConnectWalletView.swift in Sources */,
|
82D6FBD52CD99F7900C925F4 /* ConnectWalletView.swift in Sources */,
|
||||||
82D6FBD62CD99F7900C925F4 /* WalletView.swift in Sources */,
|
82D6FBD62CD99F7900C925F4 /* WalletView.swift in Sources */,
|
||||||
82D6FBD72CD99F7900C925F4 /* NWCScannerView.swift in Sources */,
|
82D6FBD72CD99F7900C925F4 /* NWCScannerView.swift in Sources */,
|
||||||
82D6FBD82CD99F7900C925F4 /* FriendsButton.swift in Sources */,
|
82D6FBD82CD99F7900C925F4 /* TrustedNetworkButton.swift in Sources */,
|
||||||
82D6FBD92CD99F7900C925F4 /* GradientFollowButton.swift in Sources */,
|
82D6FBD92CD99F7900C925F4 /* GradientFollowButton.swift in Sources */,
|
||||||
82D6FBDA2CD99F7900C925F4 /* AlbyButton.swift in Sources */,
|
82D6FBDA2CD99F7900C925F4 /* AlbyButton.swift in Sources */,
|
||||||
82D6FBDC2CD99F7900C925F4 /* DamusVideoPlayerView.swift in Sources */,
|
82D6FBDC2CD99F7900C925F4 /* DamusVideoPlayerView.swift in Sources */,
|
||||||
@@ -5389,6 +5538,7 @@
|
|||||||
82D6FBED2CD99F7900C925F4 /* MediaView.swift in Sources */,
|
82D6FBED2CD99F7900C925F4 /* MediaView.swift in Sources */,
|
||||||
82D6FBEE2CD99F7900C925F4 /* PurpleViewPrimitives.swift in Sources */,
|
82D6FBEE2CD99F7900C925F4 /* PurpleViewPrimitives.swift in Sources */,
|
||||||
82D6FBEF2CD99F7900C925F4 /* MarketingContentView.swift in Sources */,
|
82D6FBEF2CD99F7900C925F4 /* MarketingContentView.swift in Sources */,
|
||||||
|
3A2BAC602DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift in Sources */,
|
||||||
82D6FBF02CD99F7900C925F4 /* LogoView.swift in Sources */,
|
82D6FBF02CD99F7900C925F4 /* LogoView.swift in Sources */,
|
||||||
82D6FBF12CD99F7900C925F4 /* IAPProductStateView.swift in Sources */,
|
82D6FBF12CD99F7900C925F4 /* IAPProductStateView.swift in Sources */,
|
||||||
82D6FBF22CD99F7900C925F4 /* PurpleBackdrop.swift in Sources */,
|
82D6FBF22CD99F7900C925F4 /* PurpleBackdrop.swift in Sources */,
|
||||||
@@ -5407,6 +5557,7 @@
|
|||||||
82D6FBFF2CD99F7900C925F4 /* NotificationItemView.swift in Sources */,
|
82D6FBFF2CD99F7900C925F4 /* NotificationItemView.swift in Sources */,
|
||||||
82D6FC002CD99F7900C925F4 /* ProfilePicturesView.swift in Sources */,
|
82D6FC002CD99F7900C925F4 /* ProfilePicturesView.swift in Sources */,
|
||||||
82D6FC012CD99F7900C925F4 /* DamusAppNotificationView.swift in Sources */,
|
82D6FC012CD99F7900C925F4 /* DamusAppNotificationView.swift in Sources */,
|
||||||
|
3A92C1002DE16E9800CEEBAC /* FaviconCache.swift in Sources */,
|
||||||
82D6FC022CD99F7900C925F4 /* InnerTimelineView.swift in Sources */,
|
82D6FC022CD99F7900C925F4 /* InnerTimelineView.swift in Sources */,
|
||||||
82D6FC032CD99F7900C925F4 /* PostingTimelineView.swift in Sources */,
|
82D6FC032CD99F7900C925F4 /* PostingTimelineView.swift in Sources */,
|
||||||
82D6FC042CD99F7900C925F4 /* ZapsView.swift in Sources */,
|
82D6FC042CD99F7900C925F4 /* ZapsView.swift in Sources */,
|
||||||
@@ -5443,6 +5594,7 @@
|
|||||||
82D6FC202CD99F7900C925F4 /* RelayType.swift in Sources */,
|
82D6FC202CD99F7900C925F4 /* RelayType.swift in Sources */,
|
||||||
82D6FC212CD99F7900C925F4 /* SignalView.swift in Sources */,
|
82D6FC212CD99F7900C925F4 /* SignalView.swift in Sources */,
|
||||||
82D6FC222CD99F7900C925F4 /* RelayPicView.swift in Sources */,
|
82D6FC222CD99F7900C925F4 /* RelayPicView.swift in Sources */,
|
||||||
|
5C4FA7EC2DC29AE900CE658C /* FollowPackEvent.swift in Sources */,
|
||||||
82D6FC232CD99F7900C925F4 /* UserSearch.swift in Sources */,
|
82D6FC232CD99F7900C925F4 /* UserSearch.swift in Sources */,
|
||||||
82D6FC242CD99F7900C925F4 /* AddMuteItemView.swift in Sources */,
|
82D6FC242CD99F7900C925F4 /* AddMuteItemView.swift in Sources */,
|
||||||
82D6FC252CD99F7900C925F4 /* MuteDurationMenu.swift in Sources */,
|
82D6FC252CD99F7900C925F4 /* MuteDurationMenu.swift in Sources */,
|
||||||
@@ -5463,6 +5615,7 @@
|
|||||||
82D6FC342CD99F7900C925F4 /* BuilderEventView.swift in Sources */,
|
82D6FC342CD99F7900C925F4 /* BuilderEventView.swift in Sources */,
|
||||||
82D6FC352CD99F7900C925F4 /* EventProfile.swift in Sources */,
|
82D6FC352CD99F7900C925F4 /* EventProfile.swift in Sources */,
|
||||||
82D6FC362CD99F7900C925F4 /* EventMenu.swift in Sources */,
|
82D6FC362CD99F7900C925F4 /* EventMenu.swift in Sources */,
|
||||||
|
D7AAD0002E0387B800FB7699 /* LnurlAmountView.swift in Sources */,
|
||||||
82D6FC372CD99F7900C925F4 /* EventMutingContainerView.swift in Sources */,
|
82D6FC372CD99F7900C925F4 /* EventMutingContainerView.swift in Sources */,
|
||||||
82D6FC382CD99F7900C925F4 /* ZapEvent.swift in Sources */,
|
82D6FC382CD99F7900C925F4 /* ZapEvent.swift in Sources */,
|
||||||
82D6FC392CD99F7900C925F4 /* TextEvent.swift in Sources */,
|
82D6FC392CD99F7900C925F4 /* TextEvent.swift in Sources */,
|
||||||
@@ -5561,6 +5714,7 @@
|
|||||||
D73E5E2B2C6A97F4007EB227 /* PostNotify.swift in Sources */,
|
D73E5E2B2C6A97F4007EB227 /* PostNotify.swift in Sources */,
|
||||||
D73E5E2C2C6A97F4007EB227 /* PresentSheetNotify.swift in Sources */,
|
D73E5E2C2C6A97F4007EB227 /* PresentSheetNotify.swift in Sources */,
|
||||||
D73E5E2D2C6A97F4007EB227 /* ProfileUpdatedNotify.swift in Sources */,
|
D73E5E2D2C6A97F4007EB227 /* ProfileUpdatedNotify.swift in Sources */,
|
||||||
|
3A515C522DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */,
|
||||||
D73E5E2E2C6A97F4007EB227 /* ReportNotify.swift in Sources */,
|
D73E5E2E2C6A97F4007EB227 /* ReportNotify.swift in Sources */,
|
||||||
D73E5E2F2C6A97F4007EB227 /* ScrollToTopNotify.swift in Sources */,
|
D73E5E2F2C6A97F4007EB227 /* ScrollToTopNotify.swift in Sources */,
|
||||||
D73E5E302C6A97F4007EB227 /* SwitchedTimelineNotify.swift in Sources */,
|
D73E5E302C6A97F4007EB227 /* SwitchedTimelineNotify.swift in Sources */,
|
||||||
@@ -5613,6 +5767,7 @@
|
|||||||
D73E5E602C6A97F4007EB227 /* ImageMetadata.swift in Sources */,
|
D73E5E602C6A97F4007EB227 /* ImageMetadata.swift in Sources */,
|
||||||
D73E5E612C6A97F4007EB227 /* ImageProcessing.swift in Sources */,
|
D73E5E612C6A97F4007EB227 /* ImageProcessing.swift in Sources */,
|
||||||
D73E5E622C6A97F4007EB227 /* BlurHashEncode.swift in Sources */,
|
D73E5E622C6A97F4007EB227 /* BlurHashEncode.swift in Sources */,
|
||||||
|
5C09FD122DF283D700823661 /* FollowPackModel.swift in Sources */,
|
||||||
D73E5E632C6A97F4007EB227 /* BlurHashDecode.swift in Sources */,
|
D73E5E632C6A97F4007EB227 /* BlurHashDecode.swift in Sources */,
|
||||||
D73E5F952C6AA753007EB227 /* FullScreenCarouselView.swift in Sources */,
|
D73E5F952C6AA753007EB227 /* FullScreenCarouselView.swift in Sources */,
|
||||||
D73E5E642C6A97F4007EB227 /* PostBox.swift in Sources */,
|
D73E5E642C6A97F4007EB227 /* PostBox.swift in Sources */,
|
||||||
@@ -5622,14 +5777,17 @@
|
|||||||
D73E5E682C6A97F4007EB227 /* VectorMath.swift in Sources */,
|
D73E5E682C6A97F4007EB227 /* VectorMath.swift in Sources */,
|
||||||
D73E5E692C6A97F4007EB227 /* RelayBootstrap.swift in Sources */,
|
D73E5E692C6A97F4007EB227 /* RelayBootstrap.swift in Sources */,
|
||||||
D73E5E6A2C6A97F4007EB227 /* RelayModel.swift in Sources */,
|
D73E5E6A2C6A97F4007EB227 /* RelayModel.swift in Sources */,
|
||||||
|
3A2BAC5A2DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift in Sources */,
|
||||||
D73E5E6B2C6A97F4007EB227 /* AnyCodable.swift in Sources */,
|
D73E5E6B2C6A97F4007EB227 /* AnyCodable.swift in Sources */,
|
||||||
D73E5E6C2C6A97F4007EB227 /* AnyDecodable.swift in Sources */,
|
D73E5E6C2C6A97F4007EB227 /* AnyDecodable.swift in Sources */,
|
||||||
D73E5E6D2C6A97F4007EB227 /* AnyEncodable.swift in Sources */,
|
D73E5E6D2C6A97F4007EB227 /* AnyEncodable.swift in Sources */,
|
||||||
D73E5F782C6A9A5C007EB227 /* NdbNote+.swift in Sources */,
|
D73E5F782C6A9A5C007EB227 /* NdbNote+.swift in Sources */,
|
||||||
D73E5E6E2C6A97F4007EB227 /* NIPURLBuilder.swift in Sources */,
|
D73E5E6E2C6A97F4007EB227 /* NIPURLBuilder.swift in Sources */,
|
||||||
|
3ACF94472DAA006500971A4E /* NIP05DomainEventsModel.swift in Sources */,
|
||||||
D73E5E6F2C6A97F4007EB227 /* TimeAgo.swift in Sources */,
|
D73E5E6F2C6A97F4007EB227 /* TimeAgo.swift in Sources */,
|
||||||
D73E5E702C6A97F4007EB227 /* Parser.swift in Sources */,
|
D73E5E702C6A97F4007EB227 /* Parser.swift in Sources */,
|
||||||
D73E5E722C6A97F4007EB227 /* LinkView.swift in Sources */,
|
D73E5E722C6A97F4007EB227 /* LinkView.swift in Sources */,
|
||||||
|
5C4FA7EE2DC29AE900CE658C /* FollowPackEvent.swift in Sources */,
|
||||||
D73E5F922C6AA720007EB227 /* QRCodeView.swift in Sources */,
|
D73E5F922C6AA720007EB227 /* QRCodeView.swift in Sources */,
|
||||||
D73E5E742C6A97F4007EB227 /* Lists.swift in Sources */,
|
D73E5E742C6A97F4007EB227 /* Lists.swift in Sources */,
|
||||||
D73E5E752C6A97F4007EB227 /* CoreSVG.swift in Sources */,
|
D73E5E752C6A97F4007EB227 /* CoreSVG.swift in Sources */,
|
||||||
@@ -5647,6 +5805,7 @@
|
|||||||
D73E5E802C6A97F4007EB227 /* CredentialHandler.swift in Sources */,
|
D73E5E802C6A97F4007EB227 /* CredentialHandler.swift in Sources */,
|
||||||
D73E5E812C6A97F4007EB227 /* KeyboardVisible.swift in Sources */,
|
D73E5E812C6A97F4007EB227 /* KeyboardVisible.swift in Sources */,
|
||||||
D73E5E832C6A97F4007EB227 /* AVPlayer+Additions.swift in Sources */,
|
D73E5E832C6A97F4007EB227 /* AVPlayer+Additions.swift in Sources */,
|
||||||
|
5C4FA7FC2DC29C3800CE658C /* FollowPackView.swift in Sources */,
|
||||||
D73E5E842C6A97F4007EB227 /* Zaps+.swift in Sources */,
|
D73E5E842C6A97F4007EB227 /* Zaps+.swift in Sources */,
|
||||||
D73E5E852C6A97F4007EB227 /* WalletConnect+.swift in Sources */,
|
D73E5E852C6A97F4007EB227 /* WalletConnect+.swift in Sources */,
|
||||||
D73E5E862C6A97F4007EB227 /* DamusPurpleNotificationManagement.swift in Sources */,
|
D73E5E862C6A97F4007EB227 /* DamusPurpleNotificationManagement.swift in Sources */,
|
||||||
@@ -5665,6 +5824,7 @@
|
|||||||
D73E5E922C6A97F4007EB227 /* EventGroup.swift in Sources */,
|
D73E5E922C6A97F4007EB227 /* EventGroup.swift in Sources */,
|
||||||
D73E5E932C6A97F4007EB227 /* ZapGroup.swift in Sources */,
|
D73E5E932C6A97F4007EB227 /* ZapGroup.swift in Sources */,
|
||||||
D73E5E942C6A97F4007EB227 /* NotificationStatusModel.swift in Sources */,
|
D73E5E942C6A97F4007EB227 /* NotificationStatusModel.swift in Sources */,
|
||||||
|
3A515C542DF5371D002D3B34 /* TrustedNetworkButtonTipViewStyle.swift in Sources */,
|
||||||
D73E5E952C6A97F4007EB227 /* ThreadModel.swift in Sources */,
|
D73E5E952C6A97F4007EB227 /* ThreadModel.swift in Sources */,
|
||||||
D73E5E962C6A97F4007EB227 /* ReplyMap.swift in Sources */,
|
D73E5E962C6A97F4007EB227 /* ReplyMap.swift in Sources */,
|
||||||
D73E5E972C6A97F4007EB227 /* ProfileModel.swift in Sources */,
|
D73E5E972C6A97F4007EB227 /* ProfileModel.swift in Sources */,
|
||||||
@@ -5681,6 +5841,7 @@
|
|||||||
D73E5E9F2C6A97F4007EB227 /* CreateAccountModel.swift in Sources */,
|
D73E5E9F2C6A97F4007EB227 /* CreateAccountModel.swift in Sources */,
|
||||||
D73E5EA12C6A97F4007EB227 /* SignalModel.swift in Sources */,
|
D73E5EA12C6A97F4007EB227 /* SignalModel.swift in Sources */,
|
||||||
5CB017272D42C5C400A9ED05 /* TransactionsView.swift in Sources */,
|
5CB017272D42C5C400A9ED05 /* TransactionsView.swift in Sources */,
|
||||||
|
D7FA46E52DBDAA7E002C9BB0 /* ImageCacheMigrations.swift in Sources */,
|
||||||
D73E5EA22C6A97F4007EB227 /* FollowTarget.swift in Sources */,
|
D73E5EA22C6A97F4007EB227 /* FollowTarget.swift in Sources */,
|
||||||
D73E5EA32C6A97F4007EB227 /* BookmarksManager.swift in Sources */,
|
D73E5EA32C6A97F4007EB227 /* BookmarksManager.swift in Sources */,
|
||||||
D73E5EA42C6A97F4007EB227 /* EventsModel.swift in Sources */,
|
D73E5EA42C6A97F4007EB227 /* EventsModel.swift in Sources */,
|
||||||
@@ -5712,6 +5873,7 @@
|
|||||||
D73E5EB92C6A97F4007EB227 /* RelayLog.swift in Sources */,
|
D73E5EB92C6A97F4007EB227 /* RelayLog.swift in Sources */,
|
||||||
D73E5EBA2C6A97F4007EB227 /* NostrFilter.swift in Sources */,
|
D73E5EBA2C6A97F4007EB227 /* NostrFilter.swift in Sources */,
|
||||||
D73E5EBB2C6A97F4007EB227 /* Nip98HTTPAuth.swift in Sources */,
|
D73E5EBB2C6A97F4007EB227 /* Nip98HTTPAuth.swift in Sources */,
|
||||||
|
3A92C0FF2DE16E9800CEEBAC /* FaviconCache.swift in Sources */,
|
||||||
D73E5EBC2C6A97F4007EB227 /* Relay.swift in Sources */,
|
D73E5EBC2C6A97F4007EB227 /* Relay.swift in Sources */,
|
||||||
D73E5EBD2C6A97F4007EB227 /* NostrRequest.swift in Sources */,
|
D73E5EBD2C6A97F4007EB227 /* NostrRequest.swift in Sources */,
|
||||||
5CB017222D2D985E00A9ED05 /* CoinosButton.swift in Sources */,
|
5CB017222D2D985E00A9ED05 /* CoinosButton.swift in Sources */,
|
||||||
@@ -5721,6 +5883,7 @@
|
|||||||
D73E5EC02C6A97F4007EB227 /* NostrEvent+.swift in Sources */,
|
D73E5EC02C6A97F4007EB227 /* NostrEvent+.swift in Sources */,
|
||||||
D73E5EC12C6A97F4007EB227 /* NIP98AuthenticatedRequest.swift in Sources */,
|
D73E5EC12C6A97F4007EB227 /* NIP98AuthenticatedRequest.swift in Sources */,
|
||||||
D73E5EC22C6A97F4007EB227 /* NostrAuth.swift in Sources */,
|
D73E5EC22C6A97F4007EB227 /* NostrAuth.swift in Sources */,
|
||||||
|
3AA2F4E92DF1467A00B18606 /* TrustedNetworkButtonTip.swift in Sources */,
|
||||||
D73E5EC42C6A97F4007EB227 /* ReplyQuoteView.swift in Sources */,
|
D73E5EC42C6A97F4007EB227 /* ReplyQuoteView.swift in Sources */,
|
||||||
D73E5EC62C6A97F4007EB227 /* ChatBubbleView.swift in Sources */,
|
D73E5EC62C6A97F4007EB227 /* ChatBubbleView.swift in Sources */,
|
||||||
D73E5EC72C6A97F4007EB227 /* VisibilityTracker.swift in Sources */,
|
D73E5EC72C6A97F4007EB227 /* VisibilityTracker.swift in Sources */,
|
||||||
@@ -5731,7 +5894,8 @@
|
|||||||
D73E5ECC2C6A97F4007EB227 /* SuggestedUsersViewModel.swift in Sources */,
|
D73E5ECC2C6A97F4007EB227 /* SuggestedUsersViewModel.swift in Sources */,
|
||||||
D73E5ED22C6A97F4007EB227 /* WalletView.swift in Sources */,
|
D73E5ED22C6A97F4007EB227 /* WalletView.swift in Sources */,
|
||||||
D73E5ED32C6A97F4007EB227 /* NWCScannerView.swift in Sources */,
|
D73E5ED32C6A97F4007EB227 /* NWCScannerView.swift in Sources */,
|
||||||
D73E5ED42C6A97F4007EB227 /* FriendsButton.swift in Sources */,
|
D74E64132DC95CC7004C7892 /* HumanReadableErrors.swift in Sources */,
|
||||||
|
D73E5ED42C6A97F4007EB227 /* TrustedNetworkButton.swift in Sources */,
|
||||||
D73E5ED52C6A97F4007EB227 /* GradientFollowButton.swift in Sources */,
|
D73E5ED52C6A97F4007EB227 /* GradientFollowButton.swift in Sources */,
|
||||||
D73E5ED62C6A97F4007EB227 /* AlbyButton.swift in Sources */,
|
D73E5ED62C6A97F4007EB227 /* AlbyButton.swift in Sources */,
|
||||||
D73E5ED82C6A97F4007EB227 /* DamusVideoPlayerView.swift in Sources */,
|
D73E5ED82C6A97F4007EB227 /* DamusVideoPlayerView.swift in Sources */,
|
||||||
@@ -5780,6 +5944,7 @@
|
|||||||
D73E5EFF2C6A97F4007EB227 /* ZapsView.swift in Sources */,
|
D73E5EFF2C6A97F4007EB227 /* ZapsView.swift in Sources */,
|
||||||
D73E5F002C6A97F4007EB227 /* CustomizeZapView.swift in Sources */,
|
D73E5F002C6A97F4007EB227 /* CustomizeZapView.swift in Sources */,
|
||||||
D73E5F012C6A97F4007EB227 /* ZapTypePicker.swift in Sources */,
|
D73E5F012C6A97F4007EB227 /* ZapTypePicker.swift in Sources */,
|
||||||
|
5C4FA8052DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */,
|
||||||
D73E5F022C6A97F4007EB227 /* ZapUserView.swift in Sources */,
|
D73E5F022C6A97F4007EB227 /* ZapUserView.swift in Sources */,
|
||||||
D73E5F032C6A97F4007EB227 /* ProfileZapLinkView.swift in Sources */,
|
D73E5F032C6A97F4007EB227 /* ProfileZapLinkView.swift in Sources */,
|
||||||
D7DB930C2D69486700DA1EE5 /* NIP65.swift in Sources */,
|
D7DB930C2D69486700DA1EE5 /* NIP65.swift in Sources */,
|
||||||
@@ -5799,6 +5964,7 @@
|
|||||||
D73E5F0F2C6A97F4007EB227 /* CondensedProfilePicturesView.swift in Sources */,
|
D73E5F0F2C6A97F4007EB227 /* CondensedProfilePicturesView.swift in Sources */,
|
||||||
D73E5F102C6A97F4007EB227 /* ProfileEditButton.swift in Sources */,
|
D73E5F102C6A97F4007EB227 /* ProfileEditButton.swift in Sources */,
|
||||||
D73E5F112C6A97F4007EB227 /* RelayPaidDetail.swift in Sources */,
|
D73E5F112C6A97F4007EB227 /* RelayPaidDetail.swift in Sources */,
|
||||||
|
D7AACFFF2E0387B800FB7699 /* LnurlAmountView.swift in Sources */,
|
||||||
D73E5F122C6A97F4007EB227 /* RelayAuthenticationDetail.swift in Sources */,
|
D73E5F122C6A97F4007EB227 /* RelayAuthenticationDetail.swift in Sources */,
|
||||||
D73E5F132C6A97F4007EB227 /* RelaySoftwareDetail.swift in Sources */,
|
D73E5F132C6A97F4007EB227 /* RelaySoftwareDetail.swift in Sources */,
|
||||||
D73E5F142C6A97F4007EB227 /* RelayAdminDetail.swift in Sources */,
|
D73E5F142C6A97F4007EB227 /* RelayAdminDetail.swift in Sources */,
|
||||||
@@ -5809,6 +5975,7 @@
|
|||||||
D73E5F192C6A97F4007EB227 /* RelayToggle.swift in Sources */,
|
D73E5F192C6A97F4007EB227 /* RelayToggle.swift in Sources */,
|
||||||
D73E5F1A2C6A97F4007EB227 /* RelayStatusView.swift in Sources */,
|
D73E5F1A2C6A97F4007EB227 /* RelayStatusView.swift in Sources */,
|
||||||
D73E5F1B2C6A97F4007EB227 /* RelayType.swift in Sources */,
|
D73E5F1B2C6A97F4007EB227 /* RelayType.swift in Sources */,
|
||||||
|
3A2BAC5F2DE02E8600EBB4CC /* NIP05DomainPubkeysView.swift in Sources */,
|
||||||
D73E5F1C2C6A97F4007EB227 /* SignalView.swift in Sources */,
|
D73E5F1C2C6A97F4007EB227 /* SignalView.swift in Sources */,
|
||||||
D73E5F1D2C6A97F4007EB227 /* RelayPicView.swift in Sources */,
|
D73E5F1D2C6A97F4007EB227 /* RelayPicView.swift in Sources */,
|
||||||
D73E5F1E2C6A97F4007EB227 /* UserSearch.swift in Sources */,
|
D73E5F1E2C6A97F4007EB227 /* UserSearch.swift in Sources */,
|
||||||
@@ -5875,6 +6042,7 @@
|
|||||||
D73E5F512C6A97F5007EB227 /* EventDetailView.swift in Sources */,
|
D73E5F512C6A97F5007EB227 /* EventDetailView.swift in Sources */,
|
||||||
D73E5F522C6A97F5007EB227 /* FollowButtonView.swift in Sources */,
|
D73E5F522C6A97F5007EB227 /* FollowButtonView.swift in Sources */,
|
||||||
D73E5F532C6A97F5007EB227 /* FollowingView.swift in Sources */,
|
D73E5F532C6A97F5007EB227 /* FollowingView.swift in Sources */,
|
||||||
|
D7DF58322DFCF18D00E9AD28 /* SendPaymentView.swift in Sources */,
|
||||||
D73E5F542C6A97F5007EB227 /* LoginView.swift in Sources */,
|
D73E5F542C6A97F5007EB227 /* LoginView.swift in Sources */,
|
||||||
D73E5F552C6A97F5007EB227 /* QRScanNSECView.swift in Sources */,
|
D73E5F552C6A97F5007EB227 /* QRScanNSECView.swift in Sources */,
|
||||||
D73E5F562C6A97F5007EB227 /* NoteContentView.swift in Sources */,
|
D73E5F562C6A97F5007EB227 /* NoteContentView.swift in Sources */,
|
||||||
@@ -5917,6 +6085,7 @@
|
|||||||
D703D7552C670A3700A400EA /* DamusUserDefaults.swift in Sources */,
|
D703D7552C670A3700A400EA /* DamusUserDefaults.swift in Sources */,
|
||||||
D703D7A32C670E1D00A400EA /* nostr_bech32.c in Sources */,
|
D703D7A32C670E1D00A400EA /* nostr_bech32.c in Sources */,
|
||||||
D703D7992C670DF900A400EA /* sha256.c in Sources */,
|
D703D7992C670DF900A400EA /* sha256.c in Sources */,
|
||||||
|
5C4FA8002DC5119300CE658C /* FollowPackPreview.swift in Sources */,
|
||||||
D703D7972C670DED00A400EA /* wasm.c in Sources */,
|
D703D7972C670DED00A400EA /* wasm.c in Sources */,
|
||||||
5C8498042D5D150000F74FEB /* ZapExplainer.swift in Sources */,
|
5C8498042D5D150000F74FEB /* ZapExplainer.swift in Sources */,
|
||||||
D703D7842C670C4700A400EA /* SequenceUtils.swift in Sources */,
|
D703D7842C670C4700A400EA /* SequenceUtils.swift in Sources */,
|
||||||
@@ -5941,6 +6110,7 @@
|
|||||||
D73E5E1B2C6A9672007EB227 /* LikeCounter.swift in Sources */,
|
D73E5E1B2C6A9672007EB227 /* LikeCounter.swift in Sources */,
|
||||||
D703D7A92C670E5A00A400EA /* refmap.c in Sources */,
|
D703D7A92C670E5A00A400EA /* refmap.c in Sources */,
|
||||||
D703D77B2C670BF000A400EA /* TableVerifier.swift in Sources */,
|
D703D77B2C670BF000A400EA /* TableVerifier.swift in Sources */,
|
||||||
|
3ACF94442DA9FCAB00971A4E /* NIP05DomainTimelineView.swift in Sources */,
|
||||||
D703D76D2C670B4500A400EA /* ZapDataModel.swift in Sources */,
|
D703D76D2C670B4500A400EA /* ZapDataModel.swift in Sources */,
|
||||||
D703D79D2C670E0700A400EA /* node_id.c in Sources */,
|
D703D79D2C670E0700A400EA /* node_id.c in Sources */,
|
||||||
D703D79B2C670E0000A400EA /* bech32_util.c in Sources */,
|
D703D79B2C670E0000A400EA /* bech32_util.c in Sources */,
|
||||||
@@ -6403,7 +6573,7 @@
|
|||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
||||||
MARKETING_VERSION = 1.14;
|
MARKETING_VERSION = 1.15;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
@@ -6468,7 +6638,7 @@
|
|||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
||||||
MARKETING_VERSION = 1.14;
|
MARKETING_VERSION = 1.15;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -6923,6 +7093,14 @@
|
|||||||
minimumVersion = 0.2.0;
|
minimumVersion = 0.2.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/will-lumley/FaviconFinder.git";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 5.1.4;
|
||||||
|
};
|
||||||
|
};
|
||||||
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
|
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/onevcat/Kingfisher";
|
repositoryURL = "https://github.com/onevcat/Kingfisher";
|
||||||
@@ -7003,6 +7181,21 @@
|
|||||||
package = 3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */;
|
package = 3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */;
|
||||||
productName = EmojiPicker;
|
productName = EmojiPicker;
|
||||||
};
|
};
|
||||||
|
3ACF94372DA9A52F00971A4E /* FaviconFinder */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */;
|
||||||
|
productName = FaviconFinder;
|
||||||
|
};
|
||||||
|
3ACF943D2DA9B10800971A4E /* FaviconFinder */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */;
|
||||||
|
productName = FaviconFinder;
|
||||||
|
};
|
||||||
|
3ACF943F2DA9B11200971A4E /* FaviconFinder */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 3ACF94362DA9A52F00971A4E /* XCRemoteSwiftPackageReference "FaviconFinder" */;
|
||||||
|
productName = FaviconFinder;
|
||||||
|
};
|
||||||
4C06670328FC7EC500038D2A /* Kingfisher */ = {
|
4C06670328FC7EC500038D2A /* Kingfisher */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
package = 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "06318d35ee2e6bd681b95591e67da33a9461b48a3c652e58bd9d1a6f0d82bdac",
|
"originHash" : "1fc7e0b44329ba72cd285eeb022b5b92582cd01586b920d243cb0485c2e69dcc",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "codescanner",
|
"identity" : "codescanner",
|
||||||
@@ -35,6 +35,15 @@
|
|||||||
"version" : "0.2.0"
|
"version" : "0.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "faviconfinder",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/will-lumley/FaviconFinder.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "9279f4371f4877ca302ba3bf1015f3f58ae4a56c",
|
||||||
|
"version" : "5.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "gsplayer",
|
"identity" : "gsplayer",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -105,6 +114,15 @@
|
|||||||
"version" : "0.1.2"
|
"version" : "0.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftsoup",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/scinfu/SwiftSoup.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "bba848db50462894e7fc0891d018dfecad4ef11e",
|
||||||
|
"version" : "2.8.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swiftycrop",
|
"identity" : "swiftycrop",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "bbw.jpg",
|
"filename" : "blink.png",
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
@@ -94,26 +94,30 @@ enum OpenWalletError: Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func open_with_wallet(wallet: Wallet.Model, invoice: String) throws {
|
func open_with_wallet(wallet: Wallet.Model, invoice: String) throws {
|
||||||
if let url = URL(string: "\(wallet.link)\(invoice)"), this_app.canOpenURL(url) {
|
let url = try getUrlToOpen(invoice: invoice, with: wallet)
|
||||||
this_app.open(url)
|
this_app.open(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUrlToOpen(invoice: String, with wallet: Wallet.Model) throws(OpenWalletError) -> URL {
|
||||||
|
if let url = URL(string: "\(wallet.link)\(invoice)"), this_app.canOpenURL(url) {
|
||||||
|
return url
|
||||||
} else {
|
} else {
|
||||||
guard let store_link = wallet.appStoreLink else {
|
guard let store_link = wallet.appStoreLink else {
|
||||||
throw OpenWalletError.no_wallet_to_open
|
throw .no_wallet_to_open
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let url = URL(string: store_link) else {
|
guard let url = URL(string: store_link) else {
|
||||||
throw OpenWalletError.store_link_invalid
|
throw .store_link_invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
guard this_app.canOpenURL(url) else {
|
guard this_app.canOpenURL(url) else {
|
||||||
throw OpenWalletError.system_cannot_open_store_link
|
throw .system_cannot_open_store_link
|
||||||
}
|
}
|
||||||
|
|
||||||
this_app.open(url)
|
return url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let test_invoice = Invoice(description: .description("this is a description"), amount: .specific(10000), string: "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r", expiry: 604800, payment_hash: Data(), created_at: 1666139119)
|
let test_invoice = Invoice(description: .description("this is a description"), amount: .specific(10000), string: "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r", expiry: 604800, payment_hash: Data(), created_at: 1666139119)
|
||||||
|
|
||||||
struct InvoiceView_Previews: PreviewProvider {
|
struct InvoiceView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -5,27 +5,27 @@
|
|||||||
// Created by William Casarin on 2023-01-11.
|
// Created by William Casarin on 2023-01-11.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import FaviconFinder
|
||||||
|
import Kingfisher
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct NIP05Badge: View {
|
struct NIP05Badge: View {
|
||||||
let nip05: NIP05
|
let nip05: NIP05
|
||||||
let pubkey: Pubkey
|
let pubkey: Pubkey
|
||||||
let contacts: Contacts
|
let damus_state: DamusState
|
||||||
let show_domain: Bool
|
let show_domain: Bool
|
||||||
let profiles: Profiles
|
let nip05_domain_favicon: FaviconURL?
|
||||||
|
|
||||||
@Environment(\.openURL) var openURL
|
init(nip05: NIP05, pubkey: Pubkey, damus_state: DamusState, show_domain: Bool, nip05_domain_favicon: FaviconURL?) {
|
||||||
|
|
||||||
init(nip05: NIP05, pubkey: Pubkey, contacts: Contacts, show_domain: Bool, profiles: Profiles) {
|
|
||||||
self.nip05 = nip05
|
self.nip05 = nip05
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.contacts = contacts
|
self.damus_state = damus_state
|
||||||
self.show_domain = show_domain
|
self.show_domain = show_domain
|
||||||
self.profiles = profiles
|
self.nip05_domain_favicon = nip05_domain_favicon
|
||||||
}
|
}
|
||||||
|
|
||||||
var nip05_color: Bool {
|
var nip05_color: Bool {
|
||||||
return use_nip05_color(pubkey: pubkey, contacts: contacts)
|
return use_nip05_color(pubkey: pubkey, contacts: damus_state.contacts)
|
||||||
}
|
}
|
||||||
|
|
||||||
var Seal: some View {
|
var Seal: some View {
|
||||||
@@ -44,8 +44,23 @@ struct NIP05Badge: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var domainBadge: some View {
|
||||||
|
Group {
|
||||||
|
if let nip05_domain_favicon {
|
||||||
|
KFImage(nip05_domain_favicon.source)
|
||||||
|
.imageContext(.favicon, disable_animation: true)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 18, height: 18)
|
||||||
|
.clipped()
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var username_matches_nip05: Bool {
|
var username_matches_nip05: Bool {
|
||||||
guard let name = profiles.lookup(id: pubkey)?.map({ p in p?.name }).value
|
guard let name = damus_state.profiles.lookup(id: pubkey)?.map({ p in p?.name }).value
|
||||||
else {
|
else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -65,14 +80,18 @@ struct NIP05Badge: View {
|
|||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Seal
|
Seal
|
||||||
|
|
||||||
|
Group {
|
||||||
if show_domain {
|
if show_domain {
|
||||||
Text(nip05_string)
|
Text(nip05_string)
|
||||||
.nip05_colorized(gradient: nip05_color)
|
.nip05_colorized(gradient: nip05_color)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nip05_domain_favicon != nil {
|
||||||
|
domainBadge
|
||||||
|
}
|
||||||
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if let nip5url = nip05.siteUrl {
|
damus_state.nav.push(route: Route.NIP05DomainEvents(events: NIP05DomainEventsModel(state: damus_state, domain: nip05.host), nip05_domain_favicon: nip05_domain_favicon))
|
||||||
openURL(nip5url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,13 +117,9 @@ struct NIP05Badge_Previews: PreviewProvider {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let test_state = test_damus_state
|
let test_state = test_damus_state
|
||||||
VStack {
|
VStack {
|
||||||
NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: test_state.contacts, show_domain: true, profiles: test_state.profiles)
|
NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, damus_state: test_state, show_domain: true, nip05_domain_favicon: nil)
|
||||||
|
|
||||||
NIP05Badge(nip05: NIP05(username: "_", host: "jb55.com"), pubkey: test_state.pubkey, contacts: test_state.contacts, show_domain: true, profiles: test_state.profiles)
|
NIP05Badge(nip05: NIP05(username: "_", host: "jb55.com"), pubkey: test_state.pubkey, damus_state: test_state, show_domain: true, nip05_domain_favicon: nil)
|
||||||
|
|
||||||
NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: test_state.contacts, show_domain: true, profiles: test_state.profiles)
|
|
||||||
|
|
||||||
NIP05Badge(nip05: NIP05(username: "jb55", host: "jb55.com"), pubkey: test_state.pubkey, contacts: Contacts(our_pubkey: test_pubkey), show_domain: true, profiles: test_state.profiles)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
|
|||||||
// we don't have a delay on one-tap nozaps (since this will be from customize zap view)
|
// we don't have a delay on one-tap nozaps (since this will be from customize zap view)
|
||||||
let delay = damus_state.settings.nozaps ? nil : 5.0
|
let delay = damus_state.settings.nozaps ? nil : 5.0
|
||||||
|
|
||||||
let nwc_req = WalletConnect.pay(url: nwc_state.url, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, invoice: inv, delay: delay, on_flush: flusher)
|
let nwc_req = WalletConnect.pay(url: nwc_state.url, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, invoice: inv, zap_request: zapreq, delay: delay, on_flush: flusher)
|
||||||
|
|
||||||
guard let nwc_req, case .nwc(let pzap_state) = pending_zap_state else {
|
guard let nwc_req, case .nwc(let pzap_state) = pending_zap_state else {
|
||||||
print("nwc: failed to send nwc request for zapreq \(reqid.reqid)")
|
print("nwc: failed to send nwc request for zapreq \(reqid.reqid)")
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ func translate_note(profiles: Profiles, keypair: Keypair, event: NostrEvent, set
|
|||||||
|
|
||||||
// Render translated note
|
// Render translated note
|
||||||
let translated_blocks = parse_note_content(content: .content(translated_note, event.tags))
|
let translated_blocks = parse_note_content(content: .content(translated_note, event.tags))
|
||||||
let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles)
|
let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles, can_hide_last_previewable_refs: true)
|
||||||
|
|
||||||
// and cache it
|
// and cache it
|
||||||
return .translated(Translated(artifacts: artifacts, language: note_lang))
|
return .translated(Translated(artifacts: artifacts, language: note_lang))
|
||||||
|
|||||||
+23
-2
@@ -9,6 +9,7 @@ import SwiftUI
|
|||||||
import AVKit
|
import AVKit
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
import EmojiPicker
|
import EmojiPicker
|
||||||
|
import TipKit
|
||||||
|
|
||||||
struct ZapSheet {
|
struct ZapSheet {
|
||||||
let target: ZapTarget
|
let target: ZapTarget
|
||||||
@@ -178,7 +179,7 @@ struct ContentView: View {
|
|||||||
NotificationsView(state: damus, notifications: home.notifications, subtitle: $menu_subtitle)
|
NotificationsView(state: damus, notifications: home.notifications, subtitle: $menu_subtitle)
|
||||||
|
|
||||||
case .dms:
|
case .dms:
|
||||||
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms, settings: damus_state!.settings)
|
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms, settings: damus_state!.settings, subtitle: $menu_subtitle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(DamusColors.adaptableWhite)
|
.background(DamusColors.adaptableWhite)
|
||||||
@@ -686,7 +687,8 @@ struct ContentView: View {
|
|||||||
video: DamusVideoCoordinator(),
|
video: DamusVideoCoordinator(),
|
||||||
ndb: ndb,
|
ndb: ndb,
|
||||||
quote_reposts: .init(our_pubkey: pubkey),
|
quote_reposts: .init(our_pubkey: pubkey),
|
||||||
emoji_provider: DefaultEmojiProvider(showAllVariations: true)
|
emoji_provider: DefaultEmojiProvider(showAllVariations: true),
|
||||||
|
favicon_cache: FaviconCache()
|
||||||
)
|
)
|
||||||
|
|
||||||
home.damus_state = self.damus_state!
|
home.damus_state = self.damus_state!
|
||||||
@@ -704,6 +706,21 @@ struct ContentView: View {
|
|||||||
|
|
||||||
damus_state.nostrNetwork.pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
damus_state.nostrNetwork.pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
||||||
damus_state.nostrNetwork.connect()
|
damus_state.nostrNetwork.connect()
|
||||||
|
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
if damus_state.settings.developer_mode && damus_state.settings.reset_tips_on_launch {
|
||||||
|
do {
|
||||||
|
try Tips.resetDatastore()
|
||||||
|
} catch {
|
||||||
|
Log.error("Failed to reset tips datastore: %s", for: .tips, error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
try Tips.configure()
|
||||||
|
} catch {
|
||||||
|
Log.error("Failed to configure tips: %s", for: .tips, error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func music_changed(_ state: MusicState) {
|
func music_changed(_ state: MusicState) {
|
||||||
@@ -742,6 +759,8 @@ struct ContentView: View {
|
|||||||
case route(Route)
|
case route(Route)
|
||||||
/// Open a sheet
|
/// Open a sheet
|
||||||
case sheet(Sheets)
|
case sheet(Sheets)
|
||||||
|
/// Open an external URL
|
||||||
|
case external_url(URL)
|
||||||
/// Do nothing.
|
/// Do nothing.
|
||||||
///
|
///
|
||||||
/// ## Implementation notes
|
/// ## Implementation notes
|
||||||
@@ -758,6 +777,8 @@ struct ContentView: View {
|
|||||||
navigationCoordinator.push(route: route)
|
navigationCoordinator.push(route: route)
|
||||||
case .sheet(let sheet):
|
case .sheet(let sheet):
|
||||||
self.active_sheet = sheet
|
self.active_sheet = sheet
|
||||||
|
case .external_url(let url):
|
||||||
|
this_app.open(url)
|
||||||
case .no_action:
|
case .no_action:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ class Contacts {
|
|||||||
return friends
|
return friends
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func get_friend_of_friends_list() -> Set<Pubkey> {
|
||||||
|
return friend_of_friends
|
||||||
|
}
|
||||||
|
|
||||||
func get_followed_hashtags() -> Set<String> {
|
func get_followed_hashtags() -> Set<String> {
|
||||||
guard let ev = self.event else { return Set() }
|
guard let ev = self.event else { return Set() }
|
||||||
return Set(ev.referenced_hashtags.map({ $0.hashtag }))
|
return Set(ev.referenced_hashtags.map({ $0.hashtag }))
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ enum FilterState : Int {
|
|||||||
case posts = 0
|
case posts = 0
|
||||||
case posts_and_replies = 1
|
case posts_and_replies = 1
|
||||||
case conversations = 2
|
case conversations = 2
|
||||||
|
case follow_list = 3
|
||||||
|
|
||||||
func filter(ev: NostrEvent) -> Bool {
|
func filter(ev: NostrEvent) -> Bool {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -22,6 +23,8 @@ enum FilterState : Int {
|
|||||||
return true
|
return true
|
||||||
case .conversations:
|
case .conversations:
|
||||||
return true
|
return true
|
||||||
|
case .follow_list:
|
||||||
|
return ev.known_kind == .follow_list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,10 @@ class DamusState: HeadlessDamusState {
|
|||||||
var purple: DamusPurple
|
var purple: DamusPurple
|
||||||
var push_notification_client: PushNotificationClient
|
var push_notification_client: PushNotificationClient
|
||||||
let emoji_provider: EmojiProvider
|
let emoji_provider: EmojiProvider
|
||||||
|
let favicon_cache: FaviconCache
|
||||||
private(set) var nostrNetwork: NostrNetworkManager
|
private(set) var nostrNetwork: NostrNetworkManager
|
||||||
|
|
||||||
init(keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: DamusVideoCoordinator, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter, emoji_provider: EmojiProvider) {
|
init(keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: DamusVideoCoordinator, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter, emoji_provider: EmojiProvider, favicon_cache: FaviconCache) {
|
||||||
self.keypair = keypair
|
self.keypair = keypair
|
||||||
self.likes = likes
|
self.likes = likes
|
||||||
self.boosts = boosts
|
self.boosts = boosts
|
||||||
@@ -68,6 +69,7 @@ class DamusState: HeadlessDamusState {
|
|||||||
self.quote_reposts = quote_reposts
|
self.quote_reposts = quote_reposts
|
||||||
self.push_notification_client = PushNotificationClient(keypair: keypair, settings: settings)
|
self.push_notification_client = PushNotificationClient(keypair: keypair, settings: settings)
|
||||||
self.emoji_provider = emoji_provider
|
self.emoji_provider = emoji_provider
|
||||||
|
self.favicon_cache = FaviconCache()
|
||||||
|
|
||||||
let networkManagerDelegate = NostrNetworkManagerDelegate(settings: settings, contacts: contacts, ndb: ndb, keypair: keypair, relayModelCache: relay_model_cache, relayFilters: relay_filters)
|
let networkManagerDelegate = NostrNetworkManagerDelegate(settings: settings, contacts: contacts, ndb: ndb, keypair: keypair, relayModelCache: relay_model_cache, relayFilters: relay_filters)
|
||||||
self.nostrNetwork = NostrNetworkManager(delegate: networkManagerDelegate)
|
self.nostrNetwork = NostrNetworkManager(delegate: networkManagerDelegate)
|
||||||
@@ -126,7 +128,8 @@ class DamusState: HeadlessDamusState {
|
|||||||
video: DamusVideoCoordinator(),
|
video: DamusVideoCoordinator(),
|
||||||
ndb: ndb,
|
ndb: ndb,
|
||||||
quote_reposts: .init(our_pubkey: pubkey),
|
quote_reposts: .init(our_pubkey: pubkey),
|
||||||
emoji_provider: DefaultEmojiProvider(showAllVariations: true)
|
emoji_provider: DefaultEmojiProvider(showAllVariations: true),
|
||||||
|
favicon_cache: FaviconCache()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +197,8 @@ class DamusState: HeadlessDamusState {
|
|||||||
video: DamusVideoCoordinator(),
|
video: DamusVideoCoordinator(),
|
||||||
ndb: .empty,
|
ndb: .empty,
|
||||||
quote_reposts: .init(our_pubkey: empty_pub),
|
quote_reposts: .init(our_pubkey: empty_pub),
|
||||||
emoji_provider: DefaultEmojiProvider(showAllVariations: true)
|
emoji_provider: DefaultEmojiProvider(showAllVariations: true),
|
||||||
|
favicon_cache: FaviconCache()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// FollowPackEvent.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/30/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct FollowPackEvent {
|
||||||
|
let event: NostrEvent
|
||||||
|
var title: String? = nil
|
||||||
|
var uuid: String? = nil
|
||||||
|
var image: URL? = nil
|
||||||
|
var description: String? = nil
|
||||||
|
var publicKeys: [Pubkey] = []
|
||||||
|
|
||||||
|
|
||||||
|
static func parse(from ev: NostrEvent) -> FollowPackEvent {
|
||||||
|
var followlist = FollowPackEvent(event: ev)
|
||||||
|
|
||||||
|
for tag in ev.tags {
|
||||||
|
guard tag.count >= 2 else { continue }
|
||||||
|
switch tag[0].string() {
|
||||||
|
case "title": followlist.title = tag[1].string()
|
||||||
|
case "d": followlist.uuid = tag[1].string()
|
||||||
|
case "image": followlist.image = URL(string: tag[1].string())
|
||||||
|
case "description": followlist.description = tag[1].string()
|
||||||
|
case "p":
|
||||||
|
followlist.publicKeys.append(Pubkey(Data(hex: tag[1].string())))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return followlist
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
//
|
||||||
|
// FollowPackModel.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 6/5/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
class FollowPackModel: ObservableObject {
|
||||||
|
var events: EventHolder
|
||||||
|
@Published var loading: Bool = false
|
||||||
|
|
||||||
|
let damus_state: DamusState
|
||||||
|
let subid = UUID().description
|
||||||
|
let limit: UInt32 = 500
|
||||||
|
|
||||||
|
init(damus_state: DamusState) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
self.events = EventHolder(on_queue: { ev in
|
||||||
|
preload_events(state: damus_state, events: [ev])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func subscribe(follow_pack_users: [Pubkey]) {
|
||||||
|
loading = true
|
||||||
|
let to_relays = determine_to_relays(pool: damus_state.nostrNetwork.pool, filters: damus_state.relay_filters)
|
||||||
|
var filter = NostrFilter(kinds: [.text, .chat])
|
||||||
|
filter.until = UInt32(Date.now.timeIntervalSince1970)
|
||||||
|
filter.authors = follow_pack_users
|
||||||
|
filter.limit = 500
|
||||||
|
|
||||||
|
damus_state.nostrNetwork.pool.subscribe(sub_id: subid, filters: [filter], handler: handle_event, to: to_relays)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsubscribe(to: RelayURL? = nil) {
|
||||||
|
loading = false
|
||||||
|
damus_state.nostrNetwork.pool.unsubscribe(sub_id: subid, to: to.map { [$0] })
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
|
||||||
|
guard case .nostr_event(let event) = conn_ev else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch event {
|
||||||
|
case .event(let sub_id, let ev):
|
||||||
|
guard sub_id == self.subid else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
|
||||||
|
{
|
||||||
|
if self.events.insert(ev) {
|
||||||
|
self.objectWillChange.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .notice(let msg):
|
||||||
|
print("follow pack notice: \(msg)")
|
||||||
|
case .ok:
|
||||||
|
break
|
||||||
|
case .eose(let sub_id):
|
||||||
|
loading = false
|
||||||
|
|
||||||
|
if sub_id == self.subid {
|
||||||
|
unsubscribe(to: relay_id)
|
||||||
|
|
||||||
|
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -35,9 +35,9 @@ enum FriendFilter: String, StringCodable {
|
|||||||
func description() -> String {
|
func description() -> String {
|
||||||
switch self {
|
switch self {
|
||||||
case .all:
|
case .all:
|
||||||
return NSLocalizedString("All", comment: "Human-readable short description of the 'friends filter' when it is set to 'all'")
|
return NSLocalizedString("All", comment: "Human-readable short description of the 'trusted network filter' when it is disabled, and therefore is showing all content.")
|
||||||
case .friends_of_friends:
|
case .friends_of_friends:
|
||||||
return NSLocalizedString("Friends of friends", comment: "Human-readable short description of the 'friends filter' when it is set to 'friends-of-friends'")
|
return NSLocalizedString("Trusted Network", comment: "Human-readable short description of the 'trusted network filter' when it is enabled, and therefore showing content from only the trusted network.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ class HomeModel: ContactsDelegate {
|
|||||||
break
|
break
|
||||||
case .relay_list:
|
case .relay_list:
|
||||||
break // This will be handled by `UserRelayListManager`
|
break // This will be handled by `UserRelayListManager`
|
||||||
|
case .follow_list:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,34 +263,41 @@ class HomeModel: ContactsDelegate {
|
|||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
|
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
|
||||||
guard let nwc_str = damus_state.settings.nostr_wallet_connect,
|
guard let nwc_str = damus_state.settings.nostr_wallet_connect,
|
||||||
let nwc = WalletConnectURL(str: nwc_str),
|
let nwc = WalletConnectURL(str: nwc_str) else {
|
||||||
let resp = await WalletConnect.FullWalletResponse(from: ev, nwc: nwc) else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard nwc.relay == relay else { return } // Don't process NWC responses coming from relays other than our designated one
|
||||||
|
guard ev.referenced_pubkeys.first == nwc.keypair.pubkey else {
|
||||||
|
return // This message is not for us. Ignore it.
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp: WalletConnect.FullWalletResponse? = nil
|
||||||
|
do {
|
||||||
|
resp = try await WalletConnect.FullWalletResponse(from: ev, nwc: nwc)
|
||||||
|
} catch {
|
||||||
|
Log.error("HomeModel: Error on NWC wallet response handling: %s", for: .nwc, error.localizedDescription)
|
||||||
|
if let initError = error as? WalletConnect.FullWalletResponse.InitializationError,
|
||||||
|
let humanReadableError = initError.humanReadableError {
|
||||||
|
present_sheet(.error(humanReadableError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard let resp else { return }
|
||||||
|
|
||||||
// since command results are not returned for ephemeral events,
|
// since command results are not returned for ephemeral events,
|
||||||
// remove the request from the postbox which is likely failing over and over
|
// remove the request from the postbox which is likely failing over and over
|
||||||
if damus_state.nostrNetwork.postbox.remove_relayer(relay_id: nwc.relay, event_id: resp.req_id) {
|
if damus_state.nostrNetwork.postbox.remove_relayer(relay_id: nwc.relay, event_id: resp.req_id) {
|
||||||
print("nwc: got response, removed \(resp.req_id) from the postbox [\(relay)]")
|
Log.debug("HomeModel: got NWC response, removed %s from the postbox [%s]", for: .nwc, resp.req_id.hex(), relay.absoluteString)
|
||||||
} else {
|
} else {
|
||||||
print("nwc: \(resp.req_id) not found in the postbox, nothing to remove [\(relay)]")
|
Log.debug("HomeModel: got NWC response, %s not found in the postbox, nothing to remove [%s]", for: .nwc, resp.req_id.hex(), relay.absoluteString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
damus_state.wallet.handle_nwc_response(response: resp) // This can handle success or error cases
|
||||||
|
|
||||||
guard resp.response.error == nil else {
|
guard resp.response.error == nil else {
|
||||||
print("nwc error: \(resp.response)")
|
Log.error("HomeModel: NWC wallet raised an error: %s", for: .nwc, String(describing: resp.response))
|
||||||
WalletConnect.handle_error(zapcache: self.damus_state.zaps, evcache: self.damus_state.events, resp: resp)
|
WalletConnect.handle_error(zapcache: self.damus_state.zaps, evcache: self.damus_state.events, resp: resp)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.response.result_type == .list_transactions {
|
|
||||||
Log.info("Received NWC transaction list from %s", for: .nwc, relay.absoluteString)
|
|
||||||
damus_state.wallet.handle_nwc_response(response: resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.response.result_type == .get_balance {
|
|
||||||
Log.info("Received NWC balance information from %s", for: .nwc, relay.absoluteString)
|
|
||||||
damus_state.wallet.handle_nwc_response(response: resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,10 @@ struct LightningInvoice<T> {
|
|||||||
let payment_hash: Data
|
let payment_hash: Data
|
||||||
let created_at: UInt64
|
let created_at: UInt64
|
||||||
|
|
||||||
|
var abbreviated: String {
|
||||||
|
return self.string.prefix(8) + "…" + self.string.suffix(8)
|
||||||
|
}
|
||||||
|
|
||||||
var description_string: String {
|
var description_string: String {
|
||||||
switch description {
|
switch description {
|
||||||
case .description(let string):
|
case .description(let string):
|
||||||
@@ -171,6 +175,17 @@ struct LightningInvoice<T> {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func from(string: String) -> Invoice? {
|
||||||
|
// This feels a bit hacky at first, but it is actually clean
|
||||||
|
// because it reuses the same well-tested parsing logic as the rest of the app,
|
||||||
|
// avoiding code duplication and utilizing the guarantees acquired from age and testing.
|
||||||
|
// We could also use the C function `parse_invoice`, but it requires extra C bridging logic.
|
||||||
|
// NDBTODO: This may need updating on the nostrdb upgrade.
|
||||||
|
let parsedBlocks = parse_note_content(content: .content(string,nil)).blocks
|
||||||
|
guard parsedBlocks.count == 1 else { return nil }
|
||||||
|
return parsedBlocks[0].asInvoice
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>!) -> T? {
|
func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>!) -> T? {
|
||||||
@@ -192,6 +207,13 @@ enum Amount: Equatable {
|
|||||||
return format_msats(amt)
|
return format_msats(amt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func amount_sats() -> Int64? {
|
||||||
|
switch self {
|
||||||
|
case .any: nil
|
||||||
|
case .specific(let amount): amount / 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func format_msats_abbrev(_ msats: Int64) -> String {
|
func format_msats_abbrev(_ msats: Int64) -> String {
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
//
|
||||||
|
// NIP05DomainEventsModel.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 4/11/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FaviconFinder
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class NIP05DomainEventsModel: ObservableObject {
|
||||||
|
let state: DamusState
|
||||||
|
var events: EventHolder
|
||||||
|
@Published var loading: Bool = false
|
||||||
|
|
||||||
|
let domain: String
|
||||||
|
var filter: NostrFilter
|
||||||
|
let sub_id = UUID().description
|
||||||
|
let profiles_subid = UUID().description
|
||||||
|
let limit: UInt32 = 500
|
||||||
|
|
||||||
|
init(state: DamusState, domain: String) {
|
||||||
|
self.state = state
|
||||||
|
self.domain = domain
|
||||||
|
self.events = EventHolder(on_queue: { ev in
|
||||||
|
preload_events(state: state, events: [ev])
|
||||||
|
})
|
||||||
|
self.filter = NostrFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor func subscribe() {
|
||||||
|
filter.limit = self.limit
|
||||||
|
filter.kinds = [.text, .longform, .highlight]
|
||||||
|
|
||||||
|
var authors = Set<Pubkey>()
|
||||||
|
for pubkey in state.contacts.get_friend_of_friends_list() {
|
||||||
|
let profile_txn = state.profiles.lookup(id: pubkey)
|
||||||
|
|
||||||
|
guard let profile = profile_txn?.unsafeUnownedValue,
|
||||||
|
let nip05_str = profile.nip05,
|
||||||
|
let nip05 = NIP05.parse(nip05_str),
|
||||||
|
nip05.host.caseInsensitiveCompare(domain) == .orderedSame else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
authors.insert(pubkey)
|
||||||
|
}
|
||||||
|
if authors.isEmpty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter.authors = Array(authors)
|
||||||
|
|
||||||
|
print("subscribing to notes from friends of friends with '\(domain)' NIP-05 domain with sub_id \(sub_id)")
|
||||||
|
state.nostrNetwork.pool.register_handler(sub_id: sub_id, handler: handle_event)
|
||||||
|
loading = true
|
||||||
|
state.nostrNetwork.pool.send(.subscribe(.init(filters: [filter], sub_id: sub_id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsubscribe() {
|
||||||
|
state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
|
||||||
|
loading = false
|
||||||
|
print("unsubscribing from notes from friends of friends with '\(domain)' NIP-05 domain with sub_id \(sub_id)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func add_event(_ ev: NostrEvent) {
|
||||||
|
if !event_matches_filter(ev, filter: filter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard should_show_event(state: state, ev: ev) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.events.insert(ev) {
|
||||||
|
objectWillChange.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||||
|
let (sub_id, done) = handle_subid_event(pool: state.nostrNetwork.pool, relay_id: relay_id, ev: ev) { sub_id, ev in
|
||||||
|
if sub_id == self.sub_id && ev.is_textlike && ev.should_show_event {
|
||||||
|
self.add_event(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard done else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.loading = false
|
||||||
|
|
||||||
|
if sub_id == self.sub_id {
|
||||||
|
guard let txn = NdbTxn(ndb: state.ndb) else { return }
|
||||||
|
load_profiles(context: "search", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(self.events.all_events), damus_state: state, txn: txn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+115
-50
@@ -73,85 +73,143 @@ func render_note_content(ev: NostrEvent, profiles: Profiles, keypair: Keypair) -
|
|||||||
return .longform(LongformContent(ev.content))
|
return .longform(LongformContent(ev.content))
|
||||||
}
|
}
|
||||||
|
|
||||||
return .separated(render_blocks(blocks: blocks, profiles: profiles))
|
return .separated(render_blocks(blocks: blocks, profiles: profiles, can_hide_last_previewable_refs: true))
|
||||||
}
|
}
|
||||||
|
|
||||||
func render_blocks(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsSeparated {
|
func render_blocks(blocks bs: Blocks, profiles: Profiles, can_hide_last_previewable_refs: Bool = false) -> NoteArtifactsSeparated {
|
||||||
var invoices: [Invoice] = []
|
var invoices: [Invoice] = []
|
||||||
var urls: [UrlType] = []
|
var urls: [UrlType] = []
|
||||||
let blocks = bs.blocks
|
let blocks = bs.blocks
|
||||||
|
|
||||||
let one_note_ref = blocks
|
var end_mention_count = 0
|
||||||
.filter({
|
var end_url_count = 0
|
||||||
if case .mention(let mention) = $0,
|
|
||||||
case .note = mention.ref {
|
// Search backwards until we find the beginning index of the chain of previewables that reach the end of the content.
|
||||||
return true
|
var hide_text_index = blocks.endIndex
|
||||||
|
if can_hide_last_previewable_refs {
|
||||||
|
outerLoop: for (i, block) in blocks.enumerated().reversed() {
|
||||||
|
if block.is_previewable {
|
||||||
|
switch block {
|
||||||
|
case .mention:
|
||||||
|
end_mention_count += 1
|
||||||
|
|
||||||
|
// If there is more than one previewable mention,
|
||||||
|
// do not hide anything because we allow rich rendering of only one mention currently.
|
||||||
|
// This should be fixed in the future to show events inline instead.
|
||||||
|
if end_mention_count > 1 {
|
||||||
|
hide_text_index = blocks.endIndex
|
||||||
|
break outerLoop
|
||||||
|
}
|
||||||
|
case .url(let url):
|
||||||
|
let url_type = classify_url(url)
|
||||||
|
if case .link = url_type {
|
||||||
|
end_url_count += 1
|
||||||
|
|
||||||
|
// If there is more than one link, do not hide anything because we allow rich rendering of only
|
||||||
|
// one link.
|
||||||
|
if end_url_count > 1 {
|
||||||
|
hide_text_index = blocks.endIndex
|
||||||
|
break outerLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
hide_text_index = i
|
||||||
|
} else if case .text(let txt) = block, txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
// We should hide whitespace at the end sequence.
|
||||||
|
hide_text_index = i
|
||||||
|
} else if case .hashtag = block {
|
||||||
|
// We should keep hashtags at the end sequence but hide all the other previewables around it.
|
||||||
|
hide_text_index = i
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.count == 1
|
|
||||||
|
|
||||||
var ind: Int = -1
|
var ind: Int = -1
|
||||||
let txt: CompatibleText = blocks.reduce(CompatibleText()) { str, block in
|
let txt: CompatibleText = blocks.reduce(CompatibleText()) { str, block in
|
||||||
ind = ind + 1
|
ind = ind + 1
|
||||||
|
|
||||||
|
// Add the rendered previewable blocks to their type-specific lists.
|
||||||
switch block {
|
switch block {
|
||||||
case .mention(let m):
|
case .invoice(let invoice):
|
||||||
if case .note = m.ref, one_note_ref {
|
invoices.append(invoice)
|
||||||
|
case .url(let url):
|
||||||
|
let url_type = classify_url(url)
|
||||||
|
urls.append(url_type)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if can_hide_last_previewable_refs {
|
||||||
|
// If there are previewable blocks that occur before the consecutive sequence of them at the end of the content,
|
||||||
|
// we should not hide the text representation of any previewable block to avoid altering the format of the note.
|
||||||
|
if ind < hide_text_index && block.is_previewable {
|
||||||
|
hide_text_index = blocks.endIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// No need to show the text representation of the block if the only previewables are the sequence of them
|
||||||
|
// found at the end of the content.
|
||||||
|
// This is to save unnecessary use of screen space.
|
||||||
|
// The only exception is that if there are hashtags embedded in the end sequence, which is not uncommon,
|
||||||
|
// then we still want to show those hashtags but hide everything else that is previewable in the end sequence.
|
||||||
|
if ind >= hide_text_index {
|
||||||
|
if case .text(let txt) = block, txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
if case .hashtag = blocks[safe: ind+1] {
|
||||||
|
return str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: -1, txt: txt))
|
||||||
|
}
|
||||||
|
} else if case .hashtag(let htag) = block {
|
||||||
|
return str + hashtag_str(htag)
|
||||||
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch block {
|
||||||
|
case .mention(let m):
|
||||||
return str + mention_str(m, profiles: profiles)
|
return str + mention_str(m, profiles: profiles)
|
||||||
case .text(let txt):
|
case .text(let txt):
|
||||||
return str + CompatibleText(stringLiteral: reduce_text_block(blocks: blocks, ind: ind, txt: txt, one_note_ref: one_note_ref))
|
return str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: hide_text_index, txt: txt))
|
||||||
|
|
||||||
case .relay(let relay):
|
case .relay(let relay):
|
||||||
return str + CompatibleText(stringLiteral: relay)
|
return str + CompatibleText(stringLiteral: relay)
|
||||||
|
|
||||||
case .hashtag(let htag):
|
case .hashtag(let htag):
|
||||||
return str + hashtag_str(htag)
|
return str + hashtag_str(htag)
|
||||||
case .invoice(let invoice):
|
case .invoice(let invoice):
|
||||||
invoices.append(invoice)
|
return str + invoice_str(invoice)
|
||||||
return str
|
|
||||||
case .url(let url):
|
case .url(let url):
|
||||||
let url_type = classify_url(url)
|
|
||||||
switch url_type {
|
|
||||||
case .media:
|
|
||||||
urls.append(url_type)
|
|
||||||
return str
|
|
||||||
case .link(let url):
|
|
||||||
urls.append(url_type)
|
|
||||||
return str + url_str(url)
|
return str + url_str(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return NoteArtifactsSeparated(content: txt, words: bs.words, urls: urls, invoices: invoices)
|
return NoteArtifactsSeparated(content: txt, words: bs.words, urls: urls, invoices: invoices)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reduce_text_block(blocks: [Block], ind: Int, txt: String, one_note_ref: Bool) -> String {
|
func reduce_text_block(ind: Int, hide_text_index: Int, txt: String) -> String {
|
||||||
var trimmed = txt
|
var trimmed = txt
|
||||||
|
|
||||||
if let prev = blocks[safe: ind-1],
|
// Trim leading whitespaces.
|
||||||
case .url(let u) = prev,
|
if ind == 0 {
|
||||||
classify_url(u).is_media != nil {
|
trimmed = trim_prefix(trimmed)
|
||||||
trimmed = " " + trim_prefix(trimmed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let next = blocks[safe: ind+1] {
|
// Trim trailing whitespaces if the following blocks will be hidden or if this is the last block.
|
||||||
if case .url(let u) = next, classify_url(u).is_media != nil {
|
if ind == hide_text_index - 1 {
|
||||||
trimmed = trim_suffix(trimmed)
|
trimmed = trim_suffix(trimmed)
|
||||||
} else if case .mention(let m) = next,
|
|
||||||
case .note = m.ref,
|
|
||||||
one_note_ref {
|
|
||||||
trimmed = trim_suffix(trimmed)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return trimmed
|
return trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invoice_str(_ invoice: Invoice) -> CompatibleText {
|
||||||
|
var attributedString = AttributedString(stringLiteral: abbrev_identifier(invoice.string))
|
||||||
|
attributedString.link = URL(string: "damus:lightning:\(invoice.string)")
|
||||||
|
attributedString.foregroundColor = DamusColors.purple
|
||||||
|
|
||||||
|
return CompatibleText(attributed: attributedString)
|
||||||
|
}
|
||||||
|
|
||||||
func url_str(_ url: URL) -> CompatibleText {
|
func url_str(_ url: URL) -> CompatibleText {
|
||||||
var attributedString = AttributedString(stringLiteral: url.absoluteString)
|
var attributedString = AttributedString(stringLiteral: url.absoluteString)
|
||||||
attributedString.link = url
|
attributedString.link = url
|
||||||
@@ -161,17 +219,16 @@ func url_str(_ url: URL) -> CompatibleText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func classify_url(_ url: URL) -> UrlType {
|
func classify_url(_ url: URL) -> UrlType {
|
||||||
let str = url.lastPathComponent.lowercased()
|
let fileExtension = url.lastPathComponent.lowercased().components(separatedBy: ".").last
|
||||||
|
|
||||||
if str.hasSuffix(".png") || str.hasSuffix(".jpg") || str.hasSuffix(".jpeg") || str.hasSuffix(".gif") || str.hasSuffix(".webp") {
|
switch fileExtension {
|
||||||
|
case "png", "jpg", "jpeg", "gif", "webp":
|
||||||
return .media(.image(url))
|
return .media(.image(url))
|
||||||
}
|
case "mp4", "mov", "m3u8":
|
||||||
|
|
||||||
if str.hasSuffix(".mp4") || str.hasSuffix(".mov") || str.hasSuffix(".m3u8") {
|
|
||||||
return .media(.video(url))
|
return .media(.video(url))
|
||||||
}
|
default:
|
||||||
|
|
||||||
return .link(url)
|
return .link(url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func attributed_string_attach_icon(_ astr: inout AttributedString, img: UIImage) {
|
func attributed_string_attach_icon(_ astr: inout AttributedString, img: UIImage) {
|
||||||
@@ -194,11 +251,11 @@ func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText
|
|||||||
let display_str: String = {
|
let display_str: String = {
|
||||||
switch m.ref {
|
switch m.ref {
|
||||||
case .pubkey(let pk): return getDisplayName(pk: pk, profiles: profiles)
|
case .pubkey(let pk): return getDisplayName(pk: pk, profiles: profiles)
|
||||||
case .note: return abbrev_pubkey(bech32String)
|
case .note: return abbrev_identifier(bech32String)
|
||||||
case .nevent: return abbrev_pubkey(bech32String)
|
case .nevent: return abbrev_identifier(bech32String)
|
||||||
case .nprofile(let nprofile): return getDisplayName(pk: nprofile.author, profiles: profiles)
|
case .nprofile(let nprofile): return getDisplayName(pk: nprofile.author, profiles: profiles)
|
||||||
case .nrelay(let url): return url
|
case .nrelay(let url): return url
|
||||||
case .naddr: return abbrev_pubkey(bech32String)
|
case .naddr: return abbrev_identifier(bech32String)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -213,12 +270,20 @@ func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText
|
|||||||
|
|
||||||
// trim suffix whitespace and newlines
|
// trim suffix whitespace and newlines
|
||||||
func trim_suffix(_ str: String) -> String {
|
func trim_suffix(_ str: String) -> String {
|
||||||
return str.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression)
|
var result = str
|
||||||
|
while result.last?.isWhitespace == true {
|
||||||
|
result.removeLast()
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// trim prefix whitespace and newlines
|
// trim prefix whitespace and newlines
|
||||||
func trim_prefix(_ str: String) -> String {
|
func trim_prefix(_ str: String) -> String {
|
||||||
return str.replacingOccurrences(of: "^\\s+", with: "", options: .regularExpression)
|
var result = str
|
||||||
|
while result.first?.isWhitespace == true {
|
||||||
|
result.removeFirst()
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LongformContent {
|
struct LongformContent {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
//
|
|
||||||
// SearchHomeModel.swift
|
// SearchHomeModel.swift
|
||||||
// damus
|
// damus
|
||||||
//
|
//
|
||||||
@@ -16,6 +15,7 @@ class SearchHomeModel: ObservableObject {
|
|||||||
var seen_pubkey: Set<Pubkey> = Set()
|
var seen_pubkey: Set<Pubkey> = Set()
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let base_subid = UUID().description
|
let base_subid = UUID().description
|
||||||
|
let follow_pack_subid = UUID().description
|
||||||
let profiles_subid = UUID().description
|
let profiles_subid = UUID().description
|
||||||
let limit: UInt32 = 500
|
let limit: UInt32 = 500
|
||||||
//let multiple_events_per_pubkey: Bool = false
|
//let multiple_events_per_pubkey: Bool = false
|
||||||
@@ -42,12 +42,18 @@ class SearchHomeModel: ObservableObject {
|
|||||||
func subscribe() {
|
func subscribe() {
|
||||||
loading = true
|
loading = true
|
||||||
let to_relays = determine_to_relays(pool: damus_state.nostrNetwork.pool, filters: damus_state.relay_filters)
|
let to_relays = determine_to_relays(pool: damus_state.nostrNetwork.pool, filters: damus_state.relay_filters)
|
||||||
|
|
||||||
|
var follow_list_filter = NostrFilter(kinds: [.follow_list])
|
||||||
|
follow_list_filter.until = UInt32(Date.now.timeIntervalSince1970)
|
||||||
|
|
||||||
damus_state.nostrNetwork.pool.subscribe(sub_id: base_subid, filters: [get_base_filter()], handler: handle_event, to: to_relays)
|
damus_state.nostrNetwork.pool.subscribe(sub_id: base_subid, filters: [get_base_filter()], handler: handle_event, to: to_relays)
|
||||||
|
damus_state.nostrNetwork.pool.subscribe(sub_id: follow_pack_subid, filters: [follow_list_filter], handler: handle_event, to: to_relays)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsubscribe(to: RelayURL? = nil) {
|
func unsubscribe(to: RelayURL? = nil) {
|
||||||
loading = false
|
loading = false
|
||||||
damus_state.nostrNetwork.pool.unsubscribe(sub_id: base_subid, to: to.map { [$0] })
|
damus_state.nostrNetwork.pool.unsubscribe(sub_id: base_subid, to: to.map { [$0] })
|
||||||
|
damus_state.nostrNetwork.pool.unsubscribe(sub_id: follow_pack_subid, to: to.map { [$0] })
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
|
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
|
||||||
@@ -57,7 +63,7 @@ class SearchHomeModel: ObservableObject {
|
|||||||
|
|
||||||
switch event {
|
switch event {
|
||||||
case .event(let sub_id, let ev):
|
case .event(let sub_id, let ev):
|
||||||
guard sub_id == self.base_subid || sub_id == self.profiles_subid else {
|
guard sub_id == self.base_subid || sub_id == self.profiles_subid || sub_id == self.follow_pack_subid else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
|
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class SearchModel: ObservableObject {
|
|||||||
func subscribe() {
|
func subscribe() {
|
||||||
// since 1 month
|
// since 1 month
|
||||||
search.limit = self.limit
|
search.limit = self.limit
|
||||||
search.kinds = [.text, .like, .longform, .highlight]
|
search.kinds = [.text, .like, .longform, .highlight, .follow_list]
|
||||||
|
|
||||||
//likes_filter.ids = ref_events.referenced_ids!
|
//likes_filter.ids = ref_events.referenced_ids!
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,15 @@ struct DamusURLHandler {
|
|||||||
return .route(.Script(script: model))
|
return .route(.Script(script: model))
|
||||||
case .purple(let purple_url):
|
case .purple(let purple_url):
|
||||||
return await damus_state.purple.handle(purple_url: purple_url)
|
return await damus_state.purple.handle(purple_url: purple_url)
|
||||||
|
case .invoice(let invoice):
|
||||||
|
if damus_state.settings.show_wallet_selector {
|
||||||
|
return .sheet(.select_wallet(invoice: invoice.string))
|
||||||
|
} else {
|
||||||
|
guard let url = try? getUrlToOpen(invoice: invoice.string, with: damus_state.settings.default_wallet.model) else {
|
||||||
|
return .sheet(.select_wallet(invoice: invoice.string))
|
||||||
|
}
|
||||||
|
return .external_url(url)
|
||||||
|
}
|
||||||
case nil:
|
case nil:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -91,6 +100,11 @@ struct DamusURLHandler {
|
|||||||
return .filter(filt)
|
return .filter(filt)
|
||||||
case .script(let script):
|
case .script(let script):
|
||||||
return .script(script)
|
return .script(script)
|
||||||
|
case .invoice(let bolt11):
|
||||||
|
if let invoice = decode_bolt11(bolt11) {
|
||||||
|
return .invoice(invoice)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -103,5 +117,6 @@ struct DamusURLHandler {
|
|||||||
case wallet_connect(WalletConnectURL)
|
case wallet_connect(WalletConnectURL)
|
||||||
case script([UInt8])
|
case script([UInt8])
|
||||||
case purple(DamusPurpleURL)
|
case purple(DamusPurpleURL)
|
||||||
|
case invoice(Invoice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ class UserSettingsStore: ObservableObject {
|
|||||||
@Setting(key: "show_wallet_selector", default_value: false)
|
@Setting(key: "show_wallet_selector", default_value: false)
|
||||||
var show_wallet_selector: Bool
|
var show_wallet_selector: Bool
|
||||||
|
|
||||||
|
@Setting(key: "dismiss_wallet_high_balance_warning", default_value: false)
|
||||||
|
var dismiss_wallet_high_balance_warning: Bool
|
||||||
|
|
||||||
|
@Setting(key: "hide_wallet_balance", default_value: false)
|
||||||
|
var hide_wallet_balance: Bool
|
||||||
|
|
||||||
@Setting(key: "left_handed", default_value: false)
|
@Setting(key: "left_handed", default_value: false)
|
||||||
var left_handed: Bool
|
var left_handed: Bool
|
||||||
|
|
||||||
@@ -122,6 +128,12 @@ class UserSettingsStore: ObservableObject {
|
|||||||
@Setting(key: "media_previews", default_value: true)
|
@Setting(key: "media_previews", default_value: true)
|
||||||
var media_previews: Bool
|
var media_previews: Bool
|
||||||
|
|
||||||
|
@Setting(key: "show_trusted_replies_first", default_value: true)
|
||||||
|
var show_trusted_replies_first: Bool
|
||||||
|
|
||||||
|
@Setting(key: "reset_tips_on_launch", default_value: false)
|
||||||
|
var reset_tips_on_launch: Bool
|
||||||
|
|
||||||
@Setting(key: "hide_nsfw_tagged_content", default_value: false)
|
@Setting(key: "hide_nsfw_tagged_content", default_value: false)
|
||||||
var hide_nsfw_tagged_content: Bool
|
var hide_nsfw_tagged_content: Bool
|
||||||
|
|
||||||
@@ -174,8 +186,12 @@ class UserSettingsStore: ObservableObject {
|
|||||||
var truncate_timeline_text: Bool
|
var truncate_timeline_text: Bool
|
||||||
|
|
||||||
/// Nozaps mode gimps note zapping to fit into apple's content-tipping guidelines. It can not be configurable to end-users on the app store
|
/// Nozaps mode gimps note zapping to fit into apple's content-tipping guidelines. It can not be configurable to end-users on the app store
|
||||||
@Setting(key: "nozaps", default_value: true)
|
///
|
||||||
var nozaps: Bool
|
/// Update 2025-05-12: This can be re-enabled 🥳. See https://github.com/damus-io/damus/issues/3016
|
||||||
|
// @Setting(key: "nozaps", default_value: true)
|
||||||
|
var nozaps: Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
@Setting(key: "truncate_mention_text", default_value: true)
|
@Setting(key: "truncate_mention_text", default_value: true)
|
||||||
var truncate_mention_text: Bool
|
var truncate_mention_text: Bool
|
||||||
|
|||||||
@@ -83,8 +83,10 @@ enum Wallet: String, CaseIterable, Identifiable, StringCodable {
|
|||||||
return .init(index: 9, tag: "breez", displayName: "Breez", link: "breez:",
|
return .init(index: 9, tag: "breez", displayName: "Breez", link: "breez:",
|
||||||
appStoreLink: "https://apps.apple.com/us/app/breez-lightning-client-pos/id1463604142", image: "breez")
|
appStoreLink: "https://apps.apple.com/us/app/breez-lightning-client-pos/id1463604142", image: "breez")
|
||||||
case .bitcoinbeach:
|
case .bitcoinbeach:
|
||||||
return .init(index: 10, tag: "bitcoinbeach", displayName: "Bitcoin Beach", link: "bitcoinbeach://",
|
// Blink used to be called Bitcoin Beach.
|
||||||
appStoreLink: "https://apps.apple.com/sv/app/bitcoin-beach-wallet/id1531383905", image: "bbw")
|
// We have to keep the tag called "bitcoinbeach" for backwards compatibility.
|
||||||
|
return .init(index: 10, tag: "bitcoinbeach", displayName: "Blink", link: "blink://",
|
||||||
|
appStoreLink: "https://apps.apple.com/app/blink-bitcoin-wallet/id1531383905", image: "blink")
|
||||||
case .blixtwallet:
|
case .blixtwallet:
|
||||||
return .init(index: 11, tag: "blixtwallet", displayName: "Blixt Wallet", link: "blixtwallet:lightning:",
|
return .init(index: 11, tag: "blixtwallet", displayName: "Blixt Wallet", link: "blixtwallet:lightning:",
|
||||||
appStoreLink: "https://testflight.apple.com/join/EXvGhRzS", image: "blixt-wallet")
|
appStoreLink: "https://testflight.apple.com/join/EXvGhRzS", image: "blixt-wallet")
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ class WalletModel: ObservableObject {
|
|||||||
|
|
||||||
@Published private(set) var connect_state: WalletConnectState
|
@Published private(set) var connect_state: WalletConnectState
|
||||||
|
|
||||||
|
/// A dictionary listing continuations waiting for a response for each request note id.
|
||||||
|
///
|
||||||
|
/// Please see the `waitForResponse` method for context.
|
||||||
|
private var continuations: [NoteId: CheckedContinuation<WalletConnect.Response.Result, any Error>] = [:]
|
||||||
|
|
||||||
init(state: WalletConnectState, settings: UserSettingsStore) {
|
init(state: WalletConnectState, settings: UserSettingsStore) {
|
||||||
self.connect_state = state
|
self.connect_state = state
|
||||||
self.previous_state = .none
|
self.previous_state = .none
|
||||||
@@ -75,12 +80,16 @@ class WalletModel: ObservableObject {
|
|||||||
///
|
///
|
||||||
/// - Parameter response: The NWC response received from the network
|
/// - Parameter response: The NWC response received from the network
|
||||||
func handle_nwc_response(response: WalletConnect.FullWalletResponse) {
|
func handle_nwc_response(response: WalletConnect.FullWalletResponse) {
|
||||||
switch response.response.result {
|
if let error = response.response.error {
|
||||||
|
self.resume(request: response.req_id, throwing: error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let result = response.response.result else { return }
|
||||||
|
self.resume(request: response.req_id, with: result)
|
||||||
|
switch result {
|
||||||
case .get_balance(let balanceResp):
|
case .get_balance(let balanceResp):
|
||||||
self.balance = balanceResp.balance / 1000
|
self.balance = balanceResp.balance / 1000
|
||||||
case .none:
|
case .pay_invoice(_):
|
||||||
return
|
|
||||||
case .some(.pay_invoice(_)):
|
|
||||||
return
|
return
|
||||||
case .list_transactions(let transactionsResp):
|
case .list_transactions(let transactionsResp):
|
||||||
self.transactions = transactionsResp.transactions
|
self.transactions = transactionsResp.transactions
|
||||||
@@ -91,4 +100,41 @@ class WalletModel: ObservableObject {
|
|||||||
self.transactions = nil
|
self.transactions = nil
|
||||||
self.balance = nil
|
self.balance = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Async wallet response waiting mechanism
|
||||||
|
|
||||||
|
func waitForResponse(for requestId: NoteId, timeout: Duration = .seconds(10)) async throws -> WalletConnect.Response.Result {
|
||||||
|
return try await withCheckedThrowingContinuation({ continuation in
|
||||||
|
self.continuations[requestId] = continuation
|
||||||
|
|
||||||
|
let timeoutTask = Task {
|
||||||
|
try? await Task.sleep(for: timeout)
|
||||||
|
self.resume(request: requestId, throwing: WaitError.timeout) // Must resume the continuation exactly once even if there is no response
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resume(request requestId: NoteId, with result: WalletConnect.Response.Result) {
|
||||||
|
continuations[requestId]?.resume(returning: result)
|
||||||
|
continuations[requestId] = nil // Never resume a continuation twice
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resume(request requestId: NoteId, throwing error: any Error) {
|
||||||
|
if let continuation = continuations[requestId] {
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
continuations[requestId] = nil // Never resume a continuation twice
|
||||||
|
return // Error will be handled by the listener, no need for the generic error sheet
|
||||||
|
}
|
||||||
|
|
||||||
|
// No listeners to catch the error, show generic error sheet
|
||||||
|
if let error = error as? WalletConnect.WalletResponseErr,
|
||||||
|
let humanReadableError = error.humanReadableError {
|
||||||
|
present_sheet(.error(humanReadableError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WaitError: Error {
|
||||||
|
case timeout
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,4 +52,28 @@ extension NIP04 {
|
|||||||
|
|
||||||
return create_encrypted_event(message, to_pk: to_pk, tags: tags, keypair: keypair, created_at: created, kind: 4)
|
return create_encrypted_event(message, to_pk: to_pk, tags: tags, keypair: keypair, created_at: created, kind: 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decrypts string content
|
||||||
|
static func decryptContent(recipientPrivateKey: Privkey, senderPubkey: Pubkey, content: String, encoding: EncEncoding) throws(NIP04DecryptionError) -> String {
|
||||||
|
guard let shared_sec = get_shared_secret(privkey: recipientPrivateKey, pubkey: senderPubkey) else {
|
||||||
|
throw .failedToComputeSharedSecret
|
||||||
|
}
|
||||||
|
guard let dat = (encoding == .base64 ? decode_dm_base64(content) : decode_dm_bech32(content)) else {
|
||||||
|
throw .failedToDecodeEncryptedContent
|
||||||
|
}
|
||||||
|
guard let dat = aes_decrypt(data: dat.content, iv: dat.iv, shared_sec: shared_sec) else {
|
||||||
|
throw .failedToDecryptAES
|
||||||
|
}
|
||||||
|
guard let decryptedString = String(data: dat, encoding: .utf8) else {
|
||||||
|
throw .utf8DecodingFailedOnDecryptedPayload
|
||||||
|
}
|
||||||
|
return decryptedString
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NIP04DecryptionError: Error {
|
||||||
|
case failedToComputeSharedSecret
|
||||||
|
case failedToDecodeEncryptedContent
|
||||||
|
case failedToDecryptAES
|
||||||
|
case utf8DecodingFailedOnDecryptedPayload
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,9 +143,17 @@ struct ReplaceableParam: TagConvertible {
|
|||||||
var keychar: AsciiCharacter { "d" }
|
var keychar: AsciiCharacter { "d" }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Signature: Hashable, Equatable {
|
struct Signature: Codable, Hashable, Equatable {
|
||||||
let data: Data
|
let data: Data
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
self.init(try hex_decoder(decoder, expected_len: 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
try hex_encoder(to: encoder, data: self.data)
|
||||||
|
}
|
||||||
|
|
||||||
init(_ p: Data) {
|
init(_ p: Data) {
|
||||||
self.data = p
|
self.data = p
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,6 +379,10 @@ func decode_json<T: Decodable>(_ val: String) -> T? {
|
|||||||
return try? JSONDecoder().decode(T.self, from: Data(val.utf8))
|
return try? JSONDecoder().decode(T.self, from: Data(val.utf8))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decode_json_throwing<T: Decodable>(_ val: String) throws -> T {
|
||||||
|
return try JSONDecoder().decode(T.self, from: Data(val.utf8))
|
||||||
|
}
|
||||||
|
|
||||||
func decode_data<T: Decodable>(_ data: Data) -> T? {
|
func decode_data<T: Decodable>(_ data: Data) -> T? {
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
do {
|
do {
|
||||||
@@ -539,6 +543,7 @@ func event_to_json(ev: NostrEvent) -> String {
|
|||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, renamed: "NIP04.decryptContent", message: "Deprecated, please use NIP04.decryptContent instead")
|
||||||
func decrypt_dm(_ privkey: Privkey?, pubkey: Pubkey, content: String, encoding: EncEncoding) -> String? {
|
func decrypt_dm(_ privkey: Privkey?, pubkey: Pubkey, content: String, encoding: EncEncoding) -> String? {
|
||||||
guard let privkey = privkey else {
|
guard let privkey = privkey else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -30,4 +30,5 @@ enum NostrKind: UInt32, Codable {
|
|||||||
case nwc_response = 23195
|
case nwc_response = 23195
|
||||||
case http_auth = 27235
|
case http_auth = 27235
|
||||||
case status = 30315
|
case status = 30315
|
||||||
|
case follow_list = 39089
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ enum NostrLink: Equatable {
|
|||||||
case ref(RefId)
|
case ref(RefId)
|
||||||
case filter(NostrFilter)
|
case filter(NostrFilter)
|
||||||
case script([UInt8])
|
case script([UInt8])
|
||||||
|
case invoice(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
func encode_pubkey_uri(_ pubkey: Pubkey) -> String {
|
func encode_pubkey_uri(_ pubkey: Pubkey) -> String {
|
||||||
@@ -93,8 +94,15 @@ func decode_nostr_uri(_ s: String) -> NostrLink? {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if parts.count >= 2 && parts[0] == "t" {
|
if parts.count >= 2 {
|
||||||
|
switch parts[0] {
|
||||||
|
case "t":
|
||||||
return .filter(NostrFilter(hashtag: [parts[1].lowercased()]))
|
return .filter(NostrFilter(hashtag: [parts[1].lowercased()]))
|
||||||
|
case "lightning":
|
||||||
|
return .invoice(parts[1])
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard parts.count == 1 else {
|
guard parts.count == 1 else {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class Profiles {
|
|||||||
@MainActor
|
@MainActor
|
||||||
private var profiles: [Pubkey: ProfileData] = [:]
|
private var profiles: [Pubkey: ProfileData] = [:]
|
||||||
|
|
||||||
|
// Map of validated NIP-05 address to pubkey.
|
||||||
@MainActor
|
@MainActor
|
||||||
var nip05_pubkey: [String: Pubkey] = [:]
|
var nip05_pubkey: [String: Pubkey] = [:]
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ var test_damus_state: DamusState = ({
|
|||||||
video: .init(),
|
video: .init(),
|
||||||
ndb: ndb,
|
ndb: ndb,
|
||||||
quote_reposts: .init(our_pubkey: our_pubkey),
|
quote_reposts: .init(our_pubkey: our_pubkey),
|
||||||
emoji_provider: DefaultEmojiProvider(showAllVariations: true)
|
emoji_provider: DefaultEmojiProvider(showAllVariations: true),
|
||||||
|
favicon_cache: .init()
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -38,6 +38,22 @@ enum Block: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var is_previewable: Bool {
|
||||||
|
switch self {
|
||||||
|
case .mention(let m):
|
||||||
|
switch m.ref {
|
||||||
|
case .note, .nevent: return true
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
case .invoice:
|
||||||
|
return true
|
||||||
|
case .url:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case text(String)
|
case text(String)
|
||||||
case mention(Mention<MentionRef>)
|
case mention(Mention<MentionRef>)
|
||||||
case hashtag(String)
|
case hashtag(String)
|
||||||
@@ -186,3 +202,13 @@ extension Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
extension Block {
|
||||||
|
var asInvoice: Invoice? {
|
||||||
|
switch self {
|
||||||
|
case .invoice(let invoice):
|
||||||
|
return invoice
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -80,9 +80,9 @@ func parse_display_name(name: String?, display_name: String?, pubkey: Pubkey) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
func abbrev_bech32_pubkey(pubkey: Pubkey) -> String {
|
func abbrev_bech32_pubkey(pubkey: Pubkey) -> String {
|
||||||
return abbrev_pubkey(String(pubkey.npub.dropFirst(4)))
|
return abbrev_identifier(String(pubkey.npub.dropFirst(4)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func abbrev_pubkey(_ pubkey: String, amount: Int = 8) -> String {
|
func abbrev_identifier(_ pubkey: String, amount: Int = 8) -> String {
|
||||||
return pubkey.prefix(amount) + ":" + pubkey.suffix(amount)
|
return pubkey.prefix(amount) + ":" + pubkey.suffix(amount)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ extension KFOptionSetter {
|
|||||||
options.onlyLoadFirstFrame = disable_animation
|
options.onlyLoadFirstFrame = disable_animation
|
||||||
|
|
||||||
switch imageContext {
|
switch imageContext {
|
||||||
case .pfp:
|
case .pfp, .favicon:
|
||||||
options.diskCacheExpiration = .days(60)
|
options.diskCacheExpiration = .days(60)
|
||||||
break
|
break
|
||||||
case .banner:
|
case .banner:
|
||||||
@@ -82,11 +82,14 @@ enum ImageContext {
|
|||||||
case pfp
|
case pfp
|
||||||
case banner
|
case banner
|
||||||
case note
|
case note
|
||||||
|
case favicon
|
||||||
|
|
||||||
func maxMebibyteSize() -> Int {
|
func maxMebibyteSize() -> Int {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .favicon:
|
||||||
|
return 512_000 // 500KiB
|
||||||
case .pfp:
|
case .pfp:
|
||||||
return 5_242_880 // 5Mib
|
return 5_242_880 // 5MiB
|
||||||
case .banner, .note:
|
case .banner, .note:
|
||||||
return 20_971_520 // 20MiB
|
return 20_971_520 // 20MiB
|
||||||
}
|
}
|
||||||
@@ -94,6 +97,8 @@ enum ImageContext {
|
|||||||
|
|
||||||
func downsampleSize() -> CGSize {
|
func downsampleSize() -> CGSize {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .favicon:
|
||||||
|
return CGSize(width: 18, height: 18)
|
||||||
case .pfp:
|
case .pfp:
|
||||||
return CGSize(width: 200, height: 200)
|
return CGSize(width: 200, height: 200)
|
||||||
case .banner:
|
case .banner:
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// FaviconCache.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 5/23/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FaviconFinder
|
||||||
|
|
||||||
|
class FaviconCache {
|
||||||
|
private var nip05DomainFavicons: [String: [FaviconURL]] = [:]
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func lookup(_ domain: String) async -> [FaviconURL] {
|
||||||
|
let lowercasedDomain = domain.lowercased()
|
||||||
|
if let faviconURLs = nip05DomainFavicons[lowercasedDomain] {
|
||||||
|
return faviconURLs
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let siteURL = URL(string: "https://\(lowercasedDomain)"),
|
||||||
|
let faviconURLs = try? await FaviconFinder(
|
||||||
|
url: siteURL,
|
||||||
|
configuration: .init(
|
||||||
|
preferredSource: .ico, // Prefer using common favicon .ico filenames at root level to avoid scraping HTML when possible.
|
||||||
|
preferences: [
|
||||||
|
.html: FaviconFormatType.appleTouchIcon.rawValue,
|
||||||
|
.ico: "favicon.ico",
|
||||||
|
.webApplicationManifestFile: FaviconFormatType.launcherIcon4x.rawValue
|
||||||
|
]
|
||||||
|
)
|
||||||
|
).fetchFaviconURLs()
|
||||||
|
else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
nip05DomainFavicons[lowercasedDomain] = faviconURLs
|
||||||
|
|
||||||
|
return faviconURLs
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
//
|
||||||
|
// ImageCacheMigrations.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2025-04-26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct ImageCacheMigrations {
|
||||||
|
static func migrateKingfisherCacheIfNeeded() {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
let defaults = UserDefaults.standard
|
||||||
|
let migration1Key = "KingfisherCacheMigrated" // Never ever changes
|
||||||
|
let migration2Key = "KingfisherCacheMigratedV2" // Never ever changes
|
||||||
|
|
||||||
|
let migration1Done = defaults.bool(forKey: migration1Key)
|
||||||
|
let migration2Done = defaults.bool(forKey: migration2Key)
|
||||||
|
|
||||||
|
guard !migration1Done || !migration2Done else {
|
||||||
|
// All migrations are already done. Skip.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let oldCachePath = migration1Done ? migration1KingfisherCachePath() : migration0KingfisherCachePath()
|
||||||
|
|
||||||
|
// New shared cache location
|
||||||
|
let newCachePath = kingfisherCachePath().path
|
||||||
|
|
||||||
|
if fileManager.fileExists(atPath: oldCachePath) {
|
||||||
|
do {
|
||||||
|
// Move the old cache to the new location
|
||||||
|
try fileManager.moveItem(atPath: oldCachePath, toPath: newCachePath)
|
||||||
|
Log.info("Successfully migrated Kingfisher cache to %s", for: .storage, newCachePath)
|
||||||
|
} catch {
|
||||||
|
do {
|
||||||
|
// Cache data is not essential, fallback to deleting the cache and starting all over
|
||||||
|
// It's better than leaving significant garbage data stuck indefinitely on the user's phone
|
||||||
|
try fileManager.removeItem(atPath: newCachePath)
|
||||||
|
try fileManager.removeItem(atPath: oldCachePath)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Log.error("Failed to migrate cache: %s", for: .storage, error.localizedDescription)
|
||||||
|
return // Do not mark them as complete, we can try again next time the user reloads the app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark migrations as complete
|
||||||
|
defaults.set(true, forKey: migration1Key)
|
||||||
|
defaults.set(true, forKey: migration2Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
static private func migration0KingfisherCachePath() -> String {
|
||||||
|
// Implementation note: These are old, so they should not be changed
|
||||||
|
let defaultCache = ImageCache.default
|
||||||
|
return defaultCache.diskStorage.directoryURL.path
|
||||||
|
}
|
||||||
|
|
||||||
|
static private func migration1KingfisherCachePath() -> String {
|
||||||
|
// Implementation note: These are old, so they are hard-coded on purpose, because we can't change these values from the past.
|
||||||
|
let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.damus")!
|
||||||
|
return groupURL.appendingPathComponent("ImageCache").path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The latest path for kingfisher to store cached images on.
|
||||||
|
///
|
||||||
|
/// Documentation references:
|
||||||
|
/// - https://developer.apple.com/documentation/foundation/filemanager/containerurl(forsecurityapplicationgroupidentifier:)#:~:text=The%20system%20creates%20only%20the%20Library/Caches%20subdirectory%20automatically
|
||||||
|
/// - https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#:~:text=Put%20data%20cache,files%20as%20needed.
|
||||||
|
static func kingfisherCachePath() -> URL {
|
||||||
|
let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.DAMUS_APP_GROUP_IDENTIFIER)!
|
||||||
|
return groupURL
|
||||||
|
.appendingPathComponent("Library")
|
||||||
|
.appendingPathComponent("Caches")
|
||||||
|
.appendingPathComponent(Constants.IMAGE_CACHE_DIRNAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ enum LogCategory: String {
|
|||||||
case damus_purple
|
case damus_purple
|
||||||
case image_uploading
|
case image_uploading
|
||||||
case video_coordination
|
case video_coordination
|
||||||
|
case tips
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Damus structured logger
|
/// Damus structured logger
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// Created by Scott Penrose on 5/7/23.
|
// Created by Scott Penrose on 5/7/23.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import FaviconFinder
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum Route: Hashable {
|
enum Route: Hashable {
|
||||||
@@ -46,6 +47,9 @@ enum Route: Hashable {
|
|||||||
case Wallet(wallet: WalletModel)
|
case Wallet(wallet: WalletModel)
|
||||||
case WalletScanner(result: Binding<WalletScanResult>)
|
case WalletScanner(result: Binding<WalletScanResult>)
|
||||||
case FollowersYouKnow(friendedFollowers: [Pubkey], followers: FollowersModel)
|
case FollowersYouKnow(friendedFollowers: [Pubkey], followers: FollowersModel)
|
||||||
|
case NIP05DomainEvents(events: NIP05DomainEventsModel, nip05_domain_favicon: FaviconURL?)
|
||||||
|
case NIP05DomainPubkeys(domain: String, nip05_domain_favicon: FaviconURL?, pubkeys: [Pubkey])
|
||||||
|
case FollowPack(followPack: NostrEvent, model: FollowPackModel, blur_imgs: Bool)
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func view(navigationCoordinator: NavigationCoordinator, damusState: DamusState) -> some View {
|
func view(navigationCoordinator: NavigationCoordinator, damusState: DamusState) -> some View {
|
||||||
@@ -127,6 +131,12 @@ enum Route: Hashable {
|
|||||||
FollowersYouKnowView(damus_state: damusState, friended_followers: friendedFollowers, followers: followers)
|
FollowersYouKnowView(damus_state: damusState, friended_followers: friendedFollowers, followers: followers)
|
||||||
case .Script(let load_model):
|
case .Script(let load_model):
|
||||||
LoadScript(pool: damusState.nostrNetwork.pool, model: load_model)
|
LoadScript(pool: damusState.nostrNetwork.pool, model: load_model)
|
||||||
|
case .NIP05DomainEvents(let events, let nip05_domain_favicon):
|
||||||
|
NIP05DomainTimelineView(damus_state: damusState, model: events, nip05_domain_favicon: nip05_domain_favicon)
|
||||||
|
case .NIP05DomainPubkeys(let domain, let nip05_domain_favicon, let pubkeys):
|
||||||
|
NIP05DomainPubkeysView(damus_state: damusState, domain: domain, nip05_domain_favicon: nip05_domain_favicon, pubkeys: pubkeys)
|
||||||
|
case .FollowPack(let followPack, let followPackModel, let blur_imgs):
|
||||||
|
FollowPackView(state: damusState, ev: followPack, model: followPackModel, blur_imgs: blur_imgs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +241,15 @@ enum Route: Hashable {
|
|||||||
case .Script(let model):
|
case .Script(let model):
|
||||||
hasher.combine("script")
|
hasher.combine("script")
|
||||||
hasher.combine(model.data.count)
|
hasher.combine(model.data.count)
|
||||||
|
case .NIP05DomainEvents(let events, _):
|
||||||
|
hasher.combine("nip05DomainEvents")
|
||||||
|
hasher.combine(events.domain)
|
||||||
|
case .NIP05DomainPubkeys(let domain, _, _):
|
||||||
|
hasher.combine("nip05DomainPubkeys")
|
||||||
|
hasher.combine(domain)
|
||||||
|
case .FollowPack(let followPack, let followPackModel, let blur_imgs):
|
||||||
|
hasher.combine("followPack")
|
||||||
|
hasher.combine(followPack.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
//
|
||||||
|
// HumanReadableErrors.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2025-05-05.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension WalletConnect.FullWalletResponse.InitializationError {
|
||||||
|
var humanReadableError: ErrorView.UserPresentableError? {
|
||||||
|
switch self {
|
||||||
|
case .incorrectAuthorPubkey:
|
||||||
|
nil // Anyone can send a response event with an incorrect author pubkey, it is not really an "error". We should silently ignore it.
|
||||||
|
case .missingRequestIdReference:
|
||||||
|
.init(
|
||||||
|
user_visible_description: NSLocalizedString("Wallet provider returned an invalid response.", comment: "Error description shown to the user when a response from the wallet provider is invalid"),
|
||||||
|
tip: NSLocalizedString("Please copy the technical info and send it to our support team.", comment: "Tip on how to resolve issue when wallet returns an invalid response"),
|
||||||
|
technical_info: "Wallet response does not make a reference to any request; No request ID `e` tag was found."
|
||||||
|
)
|
||||||
|
case .failedToDecodeJSON(let error):
|
||||||
|
.init(
|
||||||
|
user_visible_description: NSLocalizedString("Wallet provider returned a response that we do not understand.", comment: "Error description shown to the user when a response from the wallet provider contains data the app does not understand"),
|
||||||
|
tip: NSLocalizedString("Please copy the technical info and send it to our support team.", comment: "Tip on how to resolve issue when wallet returns an invalid response"),
|
||||||
|
technical_info: "Failed to decode NWC Wallet response JSON. Error: \(error)"
|
||||||
|
)
|
||||||
|
case .failedToDecrypt(let error):
|
||||||
|
.init(
|
||||||
|
user_visible_description: NSLocalizedString("Wallet provider returned a response that we could not decrypt.", comment: "Error description shown to the user when a response from the wallet provider contains data the app could not decrypt."),
|
||||||
|
tip: NSLocalizedString("Please copy the technical info and send it to our support team.", comment: "Tip on how to resolve issue when wallet returns an invalid response"),
|
||||||
|
technical_info: "Failed to decrypt NWC Wallet response. Error: \(error)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WalletConnect.WalletResponseErr {
|
||||||
|
var humanReadableError: ErrorView.UserPresentableError? {
|
||||||
|
guard let code = self.code else {
|
||||||
|
return .init(
|
||||||
|
user_visible_description: String(format: NSLocalizedString("Your connected wallet raised an unknown error. Message: %s", comment: "Human readable error description for unknown error"), self.message ?? NSLocalizedString("Empty error message", comment: "A human readable placeholder to indicate that the error message is empty")),
|
||||||
|
tip: NSLocalizedString("Please contact the developer of your wallet provider for help.", comment: "Human readable error description for an unknown error raised by a wallet provider."),
|
||||||
|
technical_info: "NWC wallet provider returned an error response without a valid reason code. Message: \(self.message ?? "Empty error message")"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
switch code {
|
||||||
|
case .rateLimited:
|
||||||
|
return .init(
|
||||||
|
user_visible_description: NSLocalizedString("Your wallet is temporarily being rate limited.", comment: "Error description for rate limit error"),
|
||||||
|
tip: NSLocalizedString("Wait a few moments, and then try again.", comment: "Tip for rate limit error"),
|
||||||
|
technical_info: "Wallet returned a rate limit error with message: \(self.message ?? "No further details provided")"
|
||||||
|
)
|
||||||
|
case .notImplemented:
|
||||||
|
return .init(
|
||||||
|
user_visible_description: NSLocalizedString("This feature is not implemented by your wallet.", comment: "Error description for not implemented feature"),
|
||||||
|
tip: NSLocalizedString("Please check for updates or contact your wallet provider.", comment: "Tip for not implemented error"),
|
||||||
|
technical_info: "Wallet reported a not implemented error. Message: \(self.message ?? "No further details provided")"
|
||||||
|
)
|
||||||
|
case .insufficientBalance:
|
||||||
|
return .init(
|
||||||
|
user_visible_description: NSLocalizedString("Your wallet does not have sufficient balance for this transaction.", comment: "Error description for insufficient balance"),
|
||||||
|
tip: NSLocalizedString("Please deposit more funds and try again.", comment: "Tip for insufficient balance errors"),
|
||||||
|
technical_info: "Wallet returned an insufficient balance error. Message: \(self.message ?? "No further details provided")"
|
||||||
|
)
|
||||||
|
case .quotaExceeded:
|
||||||
|
return .init(
|
||||||
|
user_visible_description: NSLocalizedString("Your transaction quota has been exceeded.", comment: "Error description for quota exceeded"),
|
||||||
|
tip: NSLocalizedString("Wait for the quota to reset, or configure your wallet provider to allow a higher limit.", comment: "Tip for quota exceeded"),
|
||||||
|
technical_info: "Wallet reported a quota exceeded error. Message: \(self.message ?? "No further details provided")"
|
||||||
|
)
|
||||||
|
case .restricted:
|
||||||
|
return .init(
|
||||||
|
user_visible_description: NSLocalizedString("This operation is restricted by your wallet.", comment: "Error description for restricted operation"),
|
||||||
|
tip: NSLocalizedString("Check your account permissions or contact support.", comment: "Tip for restricted operation"),
|
||||||
|
technical_info: "Wallet returned a restricted error. Message: \(self.message ?? "No further details provided")"
|
||||||
|
)
|
||||||
|
case .unauthorized:
|
||||||
|
return .init(
|
||||||
|
user_visible_description: NSLocalizedString("You are not authorized to perform this action with your wallet.", comment: "Error description for unauthorized access"),
|
||||||
|
tip: NSLocalizedString("Please verify your credentials or permissions.", comment: "Tip for unauthorized access"),
|
||||||
|
technical_info: "Wallet returned an unauthorized error. Message: \(self.message ?? "No further details provided")"
|
||||||
|
)
|
||||||
|
case .internalError:
|
||||||
|
return .init(
|
||||||
|
user_visible_description: NSLocalizedString("An internal error occurred in your wallet.", comment: "Error description for an internal error"),
|
||||||
|
tip: NSLocalizedString("Try restarting your wallet or contacting support if the problem persists.", comment: "Tip for internal error"),
|
||||||
|
technical_info: "Wallet reported an internal error. Message: \(self.message ?? "No further details provided")"
|
||||||
|
)
|
||||||
|
case .other:
|
||||||
|
return .init(
|
||||||
|
user_visible_description: NSLocalizedString("An unspecified error occurred in your wallet.", comment: "Error description for an unspecified error"),
|
||||||
|
tip: NSLocalizedString("Please try again or contact your wallet provider for further assistance.", comment: "Tip for unspecified error"),
|
||||||
|
technical_info: "Wallet returned an error: \(self.message ?? "No further details provided")"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,11 @@ extension WalletConnect {
|
|||||||
/// Pay an invoice
|
/// Pay an invoice
|
||||||
case payInvoice(
|
case payInvoice(
|
||||||
/// bolt-11 invoice string
|
/// bolt-11 invoice string
|
||||||
invoice: String
|
invoice: String,
|
||||||
|
/// The full description of the invoice (If description does not fit in the BOLT-11 invoice, this is the pre-image of the description hash)
|
||||||
|
description: String?,
|
||||||
|
/// Optional metadata object containing more information
|
||||||
|
metadata: Metadata?
|
||||||
)
|
)
|
||||||
/// Get the current wallet balance
|
/// Get the current wallet balance
|
||||||
case getBalance
|
case getBalance
|
||||||
@@ -33,6 +37,38 @@ extension WalletConnect {
|
|||||||
type: String?
|
type: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
static func payZapRequest(invoice: String, zapRequest: NostrEvent?) -> Self {
|
||||||
|
guard let zapRequest, let zapRequestEncoded = encode_json(zapRequest) else {
|
||||||
|
return WalletConnect.Request.payInvoice(
|
||||||
|
invoice: invoice,
|
||||||
|
description: nil,
|
||||||
|
metadata: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return WalletConnect.Request.payInvoice(
|
||||||
|
invoice: invoice,
|
||||||
|
description: zapRequestEncoded,
|
||||||
|
metadata: .init(nostr: zapRequest)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Metadata: Codable, Equatable, Hashable {
|
||||||
|
/// NIP-57-compliant `kind:9734` zap request event
|
||||||
|
let nostr: NostrEvent?
|
||||||
|
|
||||||
|
init(nostr: NostrEvent?) {
|
||||||
|
self.nostr = nostr
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: any Decoder) throws {
|
||||||
|
let container: KeyedDecodingContainer<WalletConnect.Request.Metadata.CodingKeys> = try decoder.container(keyedBy: WalletConnect.Request.Metadata.CodingKeys.self)
|
||||||
|
guard let decodedZapRequest = try? container.decodeIfPresent(NostrEvent.self, forKey: WalletConnect.Request.Metadata.CodingKeys.nostr) else {
|
||||||
|
self.nostr = nil // Be lenient and fallback to nil if the NWC provider provided something invalid, since metadata is not strictly spec'd yet.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.nostr = decodedZapRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Interface
|
// MARK: - Interface
|
||||||
|
|
||||||
@@ -61,7 +97,7 @@ extension WalletConnect {
|
|||||||
|
|
||||||
/// Keys for the JSON inside the "params" object
|
/// Keys for the JSON inside the "params" object
|
||||||
private enum ParamKeys: String, CodingKey {
|
private enum ParamKeys: String, CodingKey {
|
||||||
case invoice
|
case invoice, description, metadata
|
||||||
case from, until, limit, offset, unpaid, type
|
case from, until, limit, offset, unpaid, type
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +118,9 @@ extension WalletConnect {
|
|||||||
case Method.payInvoice.rawValue:
|
case Method.payInvoice.rawValue:
|
||||||
let paramsContainer = try container.nestedContainer(keyedBy: ParamKeys.self, forKey: .params)
|
let paramsContainer = try container.nestedContainer(keyedBy: ParamKeys.self, forKey: .params)
|
||||||
let invoice = try paramsContainer.decode(String.self, forKey: .invoice)
|
let invoice = try paramsContainer.decode(String.self, forKey: .invoice)
|
||||||
self = .payInvoice(invoice: invoice)
|
let description: String? = try paramsContainer.decodeIfPresent(String.self, forKey: .description)
|
||||||
|
let metadata: Metadata? = try paramsContainer.decodeIfPresent(Metadata.self, forKey: .metadata)
|
||||||
|
self = .payInvoice(invoice: invoice, description: description, metadata: metadata)
|
||||||
|
|
||||||
case Method.getBalance.rawValue:
|
case Method.getBalance.rawValue:
|
||||||
// No params to decode
|
// No params to decode
|
||||||
@@ -112,10 +150,12 @@ extension WalletConnect {
|
|||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
case .payInvoice(let invoice):
|
case .payInvoice(let invoice, let description, let metadata):
|
||||||
try container.encode(Method.payInvoice.rawValue, forKey: .method)
|
try container.encode(Method.payInvoice.rawValue, forKey: .method)
|
||||||
var paramsContainer = container.nestedContainer(keyedBy: ParamKeys.self, forKey: .params)
|
var paramsContainer = container.nestedContainer(keyedBy: ParamKeys.self, forKey: .params)
|
||||||
try paramsContainer.encode(invoice, forKey: .invoice)
|
try paramsContainer.encode(invoice, forKey: .invoice)
|
||||||
|
try paramsContainer.encodeIfPresent(description, forKey: .description)
|
||||||
|
try paramsContainer.encodeIfPresent(metadata, forKey: .metadata)
|
||||||
|
|
||||||
case .getBalance:
|
case .getBalance:
|
||||||
try container.encode(Method.getBalance.rawValue, forKey: .method)
|
try container.encode(Method.getBalance.rawValue, forKey: .method)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
// Created by Daniel D’Aquino on 2025-03-10.
|
// Created by Daniel D’Aquino on 2025-03-10.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
|
||||||
extension WalletConnect {
|
extension WalletConnect {
|
||||||
/// Models a response from the NWC provider
|
/// Models a response from the NWC provider
|
||||||
struct Response: Decodable {
|
struct Response: Decodable {
|
||||||
@@ -50,35 +52,80 @@ extension WalletConnect {
|
|||||||
let req_id: NoteId
|
let req_id: NoteId
|
||||||
let response: Response
|
let response: Response
|
||||||
|
|
||||||
init?(from: NostrEvent, nwc: WalletConnect.ConnectURL) async {
|
init(from event: NostrEvent, nwc: WalletConnect.ConnectURL) throws(InitializationError) {
|
||||||
guard let note_id = from.referenced_ids.first else {
|
guard event.pubkey == nwc.pubkey else { throw .incorrectAuthorPubkey }
|
||||||
return nil
|
|
||||||
|
guard let referencedNoteId = event.referenced_ids.first else { throw .missingRequestIdReference }
|
||||||
|
|
||||||
|
self.req_id = referencedNoteId
|
||||||
|
|
||||||
|
var json = ""
|
||||||
|
do {
|
||||||
|
json = try NIP04.decryptContent(
|
||||||
|
recipientPrivateKey: nwc.keypair.privkey,
|
||||||
|
senderPubkey: nwc.pubkey,
|
||||||
|
content: event.content,
|
||||||
|
encoding: .base64
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch { throw .failedToDecrypt(error) }
|
||||||
|
|
||||||
|
do {
|
||||||
|
let response: WalletConnect.Response = try decode_json_throwing(json)
|
||||||
|
self.response = response
|
||||||
|
}
|
||||||
|
catch { throw .failedToDecodeJSON(error) }
|
||||||
}
|
}
|
||||||
|
|
||||||
self.req_id = note_id
|
enum InitializationError: Error {
|
||||||
|
case incorrectAuthorPubkey
|
||||||
let ares = Task {
|
case missingRequestIdReference
|
||||||
guard let json = decrypt_dm(nwc.keypair.privkey, pubkey: nwc.pubkey, content: from.content, encoding: .base64),
|
case failedToDecodeJSON(any Error)
|
||||||
let resp: WalletConnect.Response = decode_json(json)
|
case failedToDecrypt(any Error)
|
||||||
else {
|
|
||||||
let resp: WalletConnect.Response? = nil
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let res = await ares.value else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
self.response = res
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WalletResponseErr: Codable {
|
struct WalletResponseErr: Codable, Error {
|
||||||
let code: String?
|
let code: Code?
|
||||||
let message: String?
|
let message: String?
|
||||||
|
|
||||||
|
enum Code: String, Codable {
|
||||||
|
/// The client is sending commands too fast. It should retry in a few seconds.
|
||||||
|
case rateLimited = "RATE_LIMITED"
|
||||||
|
/// The command is not known or is intentionally not implemented.
|
||||||
|
case notImplemented = "NOT_IMPLEMENTED"
|
||||||
|
/// The wallet does not have enough funds to cover a fee reserve or the payment amount.
|
||||||
|
case insufficientBalance = "INSUFFICIENT_BALANCE"
|
||||||
|
/// The wallet has exceeded its spending quota.
|
||||||
|
case quotaExceeded = "QUOTA_EXCEEDED"
|
||||||
|
/// This public key is not allowed to do this operation.
|
||||||
|
case restricted = "RESTRICTED"
|
||||||
|
/// This public key has no wallet connected.
|
||||||
|
case unauthorized = "UNAUTHORIZED"
|
||||||
|
/// An internal error.
|
||||||
|
case internalError = "INTERNAL"
|
||||||
|
/// Other error.
|
||||||
|
case other = "OTHER"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case code, message
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
// Attempt to decode the code as a String
|
||||||
|
if let codeString = try container.decodeIfPresent(String.self, forKey: .code),
|
||||||
|
let validCode = Code(rawValue: codeString) {
|
||||||
|
self.code = validCode
|
||||||
|
} else {
|
||||||
|
// If the code is either missing or not one of the allowed cases, set it to nil
|
||||||
|
self.code = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.message = try container.decodeIfPresent(String.self, forKey: .message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ extension WalletConnect {
|
|||||||
static func subscribe(url: WalletConnectURL, pool: RelayPool) {
|
static func subscribe(url: WalletConnectURL, pool: RelayPool) {
|
||||||
var filter = NostrFilter(kinds: [.nwc_response])
|
var filter = NostrFilter(kinds: [.nwc_response])
|
||||||
filter.authors = [url.pubkey]
|
filter.authors = [url.pubkey]
|
||||||
|
filter.pubkeys = [url.keypair.pubkey]
|
||||||
filter.limit = 0
|
filter.limit = 0
|
||||||
let sub = NostrSubscribe(filters: [filter], sub_id: "nwc")
|
let sub = NostrSubscribe(filters: [filter], sub_id: "nwc")
|
||||||
|
|
||||||
@@ -40,8 +41,9 @@ extension WalletConnect {
|
|||||||
/// - on_flush: A callback to call after the event has been flushed to the network
|
/// - on_flush: A callback to call after the event has been flushed to the network
|
||||||
/// - Returns: The Nostr Event that was sent to the network, representing the request that was made
|
/// - Returns: The Nostr Event that was sent to the network, representing the request that was made
|
||||||
@discardableResult
|
@discardableResult
|
||||||
static func pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: String, delay: TimeInterval? = 5.0, on_flush: OnFlush? = nil) -> NostrEvent? {
|
static func pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: String, zap_request: NostrEvent?, delay: TimeInterval? = 5.0, on_flush: OnFlush? = nil) -> NostrEvent? {
|
||||||
let req = WalletConnect.Request.payInvoice(invoice: invoice)
|
|
||||||
|
let req = WalletConnect.Request.payZapRequest(invoice: invoice, zapRequest: zap_request)
|
||||||
guard let ev = req.to_nostr_event(to_pk: url.pubkey, keypair: url.keypair) else {
|
guard let ev = req.to_nostr_event(to_pk: url.pubkey, keypair: url.keypair) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -104,6 +106,28 @@ extension WalletConnect {
|
|||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
static func refresh_wallet_information(damus_state: DamusState) async {
|
||||||
|
damus_state.wallet.resetWalletStateInformation()
|
||||||
|
await Self.update_wallet_information(damus_state: damus_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
static func update_wallet_information(damus_state: DamusState) async {
|
||||||
|
guard let url = damus_state.settings.nostr_wallet_connect,
|
||||||
|
let nwc = WalletConnectURL(str: url) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let flusher: OnFlush? = nil
|
||||||
|
|
||||||
|
let delay = 0.0 // We don't need a delay when fetching a transaction list or balance
|
||||||
|
|
||||||
|
WalletConnect.request_transaction_list(url: nwc, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, delay: delay, on_flush: flusher)
|
||||||
|
WalletConnect.request_balance_information(url: nwc, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, delay: delay, on_flush: flusher)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
static func handle_zap_success(state: DamusState, resp: WalletConnect.FullWalletResponse) {
|
static func handle_zap_success(state: DamusState, resp: WalletConnect.FullWalletResponse) {
|
||||||
// find the pending zap and mark it as pending-confirmed
|
// find the pending zap and mark it as pending-confirmed
|
||||||
for kv in state.zaps.our_zaps {
|
for kv in state.zaps.our_zaps {
|
||||||
@@ -142,7 +166,7 @@ extension WalletConnect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print("damus-donation donating...")
|
print("damus-donation donating...")
|
||||||
WalletConnect.pay(url: nwc, pool: pool, post: postbox, invoice: invoice, delay: nil)
|
WalletConnect.pay(url: nwc, pool: pool, post: postbox, invoice: invoice, zap_request: nil, delay: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a received Nostr Wallet Connect error
|
/// Handles a received Nostr Wallet Connect error
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ extension WalletConnect {
|
|||||||
let created_at: UInt64 // unixtimestamp, // invoice/payment creation time
|
let created_at: UInt64 // unixtimestamp, // invoice/payment creation time
|
||||||
let expires_at: UInt64? // unixtimestamp, // invoice expiration time, optional if not applicable
|
let expires_at: UInt64? // unixtimestamp, // invoice expiration time, optional if not applicable
|
||||||
let settled_at: UInt64? // unixtimestamp, // invoice/payment settlement time, optional if unpaid
|
let settled_at: UInt64? // unixtimestamp, // invoice/payment settlement time, optional if unpaid
|
||||||
//"metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
|
let metadata: WalletConnect.Request.Metadata? // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
//
|
|
||||||
// FriendsButton.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2023-04-21.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct FriendsButton: View {
|
|
||||||
@Binding var filter: FriendFilter
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Button(action: {
|
|
||||||
switch self.filter {
|
|
||||||
case .all:
|
|
||||||
self.filter = .friends_of_friends
|
|
||||||
case .friends_of_friends:
|
|
||||||
self.filter = .all
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
if filter == .friends_of_friends {
|
|
||||||
LINEAR_GRADIENT
|
|
||||||
.mask(Image("user-added")
|
|
||||||
.resizable()
|
|
||||||
).frame(width: 28, height: 28)
|
|
||||||
} else {
|
|
||||||
Image("user-added")
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 28, height: 28)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FriendsButton_Previews: PreviewProvider {
|
|
||||||
@State static var enabled: FriendFilter = .all
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
FriendsButton(filter: $enabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// TrustedNetworkButton.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-04-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct TrustedNetworkButton: View {
|
||||||
|
@Binding var filter: FriendFilter
|
||||||
|
var action: (@MainActor () -> Void)? = nil
|
||||||
|
|
||||||
|
var MainButton: some View {
|
||||||
|
Button(action: {
|
||||||
|
switch self.filter {
|
||||||
|
case .all:
|
||||||
|
self.filter = .friends_of_friends
|
||||||
|
case .friends_of_friends:
|
||||||
|
self.filter = .all
|
||||||
|
}
|
||||||
|
|
||||||
|
if let action {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
if filter == .friends_of_friends {
|
||||||
|
LINEAR_GRADIENT
|
||||||
|
.mask(Image(systemName: "network.badge.shield.half.filled")
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
)
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
} else {
|
||||||
|
Image(systemName: "network.slash")
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
MainButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TrustedNetworkButton_Previews: PreviewProvider {
|
||||||
|
@State static var enabled: FriendFilter = .all
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
TrustedNetworkButton(filter: $enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -337,12 +337,6 @@ struct ChatEventView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notification.Name {
|
|
||||||
static var toggle_thread_view: Notification.Name {
|
|
||||||
return Notification.Name("convert_to_thread")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
let bar = make_actionbar_model(ev: test_note.id, damus: test_damus_state)
|
let bar = make_actionbar_model(ev: test_note.id, damus: test_damus_state)
|
||||||
return ChatEventView(event: test_note, selected_event: test_note, prev_ev: nil, next_ev: nil, damus_state: test_damus_state, thread: ThreadModel(event: test_note, damus_state: test_damus_state), scroll_to_event: nil, focus_event: nil, highlight_bubble: false, bar: bar)
|
return ChatEventView(event: test_note, selected_event: test_note, prev_ev: nil, next_ev: nil, damus_state: test_damus_state, thread: ThreadModel(event: test_note, damus_state: test_damus_state), scroll_to_event: nil, focus_event: nil, highlight_bubble: false, bar: bar)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SwipeActions
|
import SwipeActions
|
||||||
|
import TipKit
|
||||||
|
|
||||||
struct ChatroomThreadView: View {
|
struct ChatroomThreadView: View {
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
@@ -15,11 +16,20 @@ struct ChatroomThreadView: View {
|
|||||||
@ObservedObject var thread: ThreadModel
|
@ObservedObject var thread: ThreadModel
|
||||||
@State var highlighted_note_id: NoteId? = nil
|
@State var highlighted_note_id: NoteId? = nil
|
||||||
@State var user_just_posted_flag: Bool = false
|
@State var user_just_posted_flag: Bool = false
|
||||||
|
@State var untrusted_network_expanded: Bool = true
|
||||||
@Namespace private var animation
|
@Namespace private var animation
|
||||||
|
|
||||||
|
// Add state for sticky header
|
||||||
|
@State var showStickyHeader: Bool = false
|
||||||
|
@State var untrustedSectionOffset: CGFloat = 0
|
||||||
|
|
||||||
|
private static let untrusted_network_section_id = "untrusted-network-section"
|
||||||
|
private static let sticky_header_adjusted_anchor = UnitPoint(x: UnitPoint.top.x, y: 0.2)
|
||||||
|
|
||||||
func go_to_event(scroller: ScrollViewProxy, note_id: NoteId) {
|
func go_to_event(scroller: ScrollViewProxy, note_id: NoteId) {
|
||||||
scroll_to_event(scroller: scroller, id: note_id, delay: 0, animate: true, anchor: .top)
|
let adjustedAnchor: UnitPoint = showStickyHeader ? ChatroomThreadView.sticky_header_adjusted_anchor : .top
|
||||||
|
|
||||||
|
scroll_to_event(scroller: scroller, id: note_id, delay: 0, animate: true, anchor: adjustedAnchor)
|
||||||
highlighted_note_id = note_id
|
highlighted_note_id = note_id
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
@@ -35,8 +45,69 @@ struct ChatroomThreadView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trusted_event_filter(_ event: NostrEvent) -> Bool {
|
||||||
|
!damus.settings.show_trusted_replies_first || damus.contacts.is_in_friendosphere(event.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ThreadedSwipeViewGroup(scroller: ScrollViewProxy, events: [NostrEvent]) -> some View {
|
||||||
|
SwipeViewGroup {
|
||||||
|
ForEach(Array(zip(events, events.indices)), id: \.0.id) { (ev, ind) in
|
||||||
|
ChatEventView(event: events[ind],
|
||||||
|
selected_event: self.thread.selected_event,
|
||||||
|
prev_ev: ind > 0 ? events[ind-1] : nil,
|
||||||
|
next_ev: ind == events.count-1 ? nil : events[ind+1],
|
||||||
|
damus_state: damus,
|
||||||
|
thread: thread,
|
||||||
|
scroll_to_event: { note_id in
|
||||||
|
self.go_to_event(scroller: scroller, note_id: note_id)
|
||||||
|
},
|
||||||
|
focus_event: {
|
||||||
|
self.set_active_event(scroller: scroller, ev: ev)
|
||||||
|
},
|
||||||
|
highlight_bubble: highlighted_note_id == ev.id,
|
||||||
|
bar: make_actionbar_model(ev: ev.id, damus: damus)
|
||||||
|
)
|
||||||
|
.id(ev.id)
|
||||||
|
.matchedGeometryEffect(id: ev.id.hex(), in: animation, anchor: .center)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var OutsideTrustedNetworkLabel: some View {
|
||||||
|
HStack {
|
||||||
|
Label(
|
||||||
|
NSLocalizedString(
|
||||||
|
"Replies outside your trusted network",
|
||||||
|
comment: "Section title in thread for replies from outside of the current user's trusted network, which is their follows and follows of follows."),
|
||||||
|
systemImage: "network.slash"
|
||||||
|
)
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.rotationEffect(.degrees(untrusted_network_expanded ? 90 : 0))
|
||||||
|
.animation(.easeInOut(duration: 0.1), value: untrusted_network_expanded)
|
||||||
|
}
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
var StickyHeaderView: some View {
|
||||||
|
OutsideTrustedNetworkLabel
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.vertical, 12)
|
||||||
|
.background(
|
||||||
|
Color(UIColor.systemBackground)
|
||||||
|
.shadow(color: .black.opacity(0.15), radius: 3, x: 0, y: 2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollViewReader { scroller in
|
ScrollViewReader { scroller in
|
||||||
|
let sorted_child_events = thread.sorted_child_events
|
||||||
|
|
||||||
|
let untrusted_events = sorted_child_events.filter { !trusted_event_filter($0) }
|
||||||
|
let trusted_events = sorted_child_events.filter { trusted_event_filter($0) }
|
||||||
|
|
||||||
|
ZStack(alignment: .top) {
|
||||||
ScrollView(.vertical) {
|
ScrollView(.vertical) {
|
||||||
LazyVStack(alignment: .leading, spacing: 8) {
|
LazyVStack(alignment: .leading, spacing: 8) {
|
||||||
// MARK: - Parents events view
|
// MARK: - Parents events view
|
||||||
@@ -56,11 +127,8 @@ struct ChatroomThreadView: View {
|
|||||||
.padding(.leading, 25 * 2)
|
.padding(.leading, 25 * 2)
|
||||||
|
|
||||||
}.background(GeometryReader { geometry in
|
}.background(GeometryReader { geometry in
|
||||||
// get the height and width of the EventView view
|
|
||||||
let eventHeight = geometry.frame(in: .global).height
|
let eventHeight = geometry.frame(in: .global).height
|
||||||
// let eventWidth = geometry.frame(in: .global).width
|
|
||||||
|
|
||||||
// vertical gray line in the background
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(Color.gray.opacity(0.25))
|
.fill(Color.gray.opacity(0.25))
|
||||||
.frame(width: 2, height: eventHeight)
|
.frame(width: 2, height: eventHeight)
|
||||||
@@ -83,39 +151,90 @@ struct ChatroomThreadView: View {
|
|||||||
}
|
}
|
||||||
.id(self.thread.selected_event.id)
|
.id(self.thread.selected_event.id)
|
||||||
|
|
||||||
|
// MARK: - Children view - inside trusted network
|
||||||
// MARK: - Children view
|
if !trusted_events.isEmpty {
|
||||||
let events = thread.sorted_child_events
|
ThreadedSwipeViewGroup(scroller: scroller, events: trusted_events)
|
||||||
let count = events.count
|
|
||||||
SwipeViewGroup {
|
|
||||||
ForEach(Array(zip(events, events.indices)), id: \.0.id) { (ev, ind) in
|
|
||||||
ChatEventView(event: events[ind],
|
|
||||||
selected_event: self.thread.selected_event,
|
|
||||||
prev_ev: ind > 0 ? events[ind-1] : nil,
|
|
||||||
next_ev: ind == count-1 ? nil : events[ind+1],
|
|
||||||
damus_state: damus,
|
|
||||||
thread: thread,
|
|
||||||
scroll_to_event: { note_id in
|
|
||||||
self.go_to_event(scroller: scroller, note_id: note_id)
|
|
||||||
},
|
|
||||||
focus_event: {
|
|
||||||
self.set_active_event(scroller: scroller, ev: ev)
|
|
||||||
},
|
|
||||||
highlight_bubble: highlighted_note_id == ev.id,
|
|
||||||
bar: make_actionbar_model(ev: ev.id, damus: damus)
|
|
||||||
)
|
|
||||||
.padding(.horizontal)
|
|
||||||
.id(ev.id)
|
|
||||||
.matchedGeometryEffect(id: ev.id.hex(), in: animation, anchor: .center)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
|
|
||||||
|
// MARK: - Children view - outside trusted network
|
||||||
|
if !untrusted_events.isEmpty {
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
TipView(TrustedNetworkRepliesTip.shared, arrowEdge: .bottom)
|
||||||
|
.padding(.top, 10)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
// Track this section's position
|
||||||
|
Color.clear
|
||||||
|
.frame(height: 1)
|
||||||
|
.background(
|
||||||
|
GeometryReader { proxy in
|
||||||
|
Color.clear
|
||||||
|
.onAppear {
|
||||||
|
untrustedSectionOffset = proxy.frame(in: .global).minY
|
||||||
|
}
|
||||||
|
.onChange(of: proxy.frame(in: .global).minY) { newY in
|
||||||
|
let shouldShow = newY <= 100 // Adjust this threshold as needed
|
||||||
|
if shouldShow != showStickyHeader {
|
||||||
|
withAnimation(.easeInOut(duration: 0.3)) {
|
||||||
|
showStickyHeader = shouldShow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
withAnimation {
|
||||||
|
untrusted_network_expanded.toggle()
|
||||||
|
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
TrustedNetworkRepliesTip.shared.invalidate(reason: .actionPerformed)
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll_to_event(scroller: scroller, id: ChatroomThreadView.untrusted_network_section_id, delay: 0.1, animate: true, anchor: ChatroomThreadView.sticky_header_adjusted_anchor)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
OutsideTrustedNetworkLabel
|
||||||
|
}
|
||||||
|
.id(ChatroomThreadView.untrusted_network_section_id)
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
if untrusted_network_expanded {
|
||||||
|
withAnimation {
|
||||||
|
LazyVStack(alignment: .leading, spacing: 8) {
|
||||||
|
ThreadedSwipeViewGroup(scroller: scroller, events: untrusted_events)
|
||||||
|
}
|
||||||
|
.padding(.top, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EndBlock()
|
EndBlock()
|
||||||
|
|
||||||
HStack {}
|
HStack {}
|
||||||
.frame(height: tabHeight + getSafeAreaBottom())
|
.frame(height: tabHeight + getSafeAreaBottom())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if showStickyHeader && !untrusted_events.isEmpty {
|
||||||
|
VStack {
|
||||||
|
StickyHeaderView
|
||||||
|
.onTapGesture {
|
||||||
|
withAnimation {
|
||||||
|
untrusted_network_expanded.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.transition(.move(edge: .top).combined(with: .opacity))
|
||||||
|
.zIndex(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
.onReceive(handle_notify(.post), perform: { notify in
|
.onReceive(handle_notify(.post), perform: { notify in
|
||||||
switch notify {
|
switch notify {
|
||||||
case .post(_):
|
case .post(_):
|
||||||
@@ -139,15 +258,8 @@ struct ChatroomThreadView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggle_thread_view() {
|
|
||||||
NotificationCenter.default.post(name: .toggle_thread_view, object: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct ChatroomView_Previews: PreviewProvider {
|
struct ChatroomView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
Group {
|
Group {
|
||||||
@@ -167,8 +279,3 @@ struct ChatroomView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func scroll_after_load(thread: ThreadModel, proxy: ScrollViewProxy) {
|
|
||||||
scroll_to_event(scroller: proxy, id: thread.selected_event.id, delay: 0.1, animate: false)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import TipKit
|
||||||
|
|
||||||
enum DMType: Hashable {
|
enum DMType: Hashable {
|
||||||
case rando
|
case rando
|
||||||
@@ -18,6 +19,7 @@ struct DirectMessagesView: View {
|
|||||||
@State var dm_type: DMType = .friend
|
@State var dm_type: DMType = .friend
|
||||||
@ObservedObject var model: DirectMessagesModel
|
@ObservedObject var model: DirectMessagesModel
|
||||||
@ObservedObject var settings: UserSettingsStore
|
@ObservedObject var settings: UserSettingsStore
|
||||||
|
@Binding var subtitle: String?
|
||||||
|
|
||||||
func MainContent(requests: Bool) -> some View {
|
func MainContent(requests: Bool) -> some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@@ -72,7 +74,15 @@ struct DirectMessagesView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let showTrustedButton = would_filter_non_friends_from_dms(contacts: damus_state.contacts, dms: self.model.dms)
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
if #available(iOS 17, *), showTrustedButton {
|
||||||
|
TipView(TrustedNetworkButtonTip.shared)
|
||||||
|
.tipBackground(.clear)
|
||||||
|
.tipViewStyle(TrustedNetworkButtonTipViewStyle())
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
|
||||||
CustomPicker(tabs: [
|
CustomPicker(tabs: [
|
||||||
(NSLocalizedString("DMs", comment: "Picker option for DM selector for seeing only DMs that have been responded to. DM is the English abbreviation for Direct Message."), DMType.friend),
|
(NSLocalizedString("DMs", comment: "Picker option for DM selector for seeing only DMs that have been responded to. DM is the English abbreviation for Direct Message."), DMType.friend),
|
||||||
(NSLocalizedString("Requests", comment: "Picker option for DM selector for seeing only message requests (DMs that someone else sent the user which has not been responded to yet"), DMType.rando),
|
(NSLocalizedString("Requests", comment: "Picker option for DM selector for seeing only message requests (DMs that someone else sent the user which has not been responded to yet"), DMType.rando),
|
||||||
@@ -92,11 +102,21 @@ struct DirectMessagesView: View {
|
|||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
if would_filter_non_friends_from_dms(contacts: damus_state.contacts, dms: self.model.dms) {
|
if showTrustedButton {
|
||||||
|
TrustedNetworkButton(filter: $settings.friend_filter) {
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
TrustedNetworkButtonTip.shared.invalidate(reason: .actionPerformed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
self.subtitle = settings.friend_filter.description()
|
||||||
|
|
||||||
FriendsButton(filter: $settings.friend_filter)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.onChange(of: settings.friend_filter) { val in
|
||||||
|
self.subtitle = val.description()
|
||||||
}
|
}
|
||||||
.navigationTitle(NSLocalizedString("DMs", comment: "Navigation title for view of DMs, where DM is an English abbreviation for Direct Message."))
|
.navigationTitle(NSLocalizedString("DMs", comment: "Navigation title for view of DMs, where DM is an English abbreviation for Direct Message."))
|
||||||
}
|
}
|
||||||
@@ -115,6 +135,6 @@ func would_filter_non_friends_from_dms(contacts: Contacts, dms: [DirectMessageMo
|
|||||||
struct DirectMessagesView_Previews: PreviewProvider {
|
struct DirectMessagesView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let ds = test_damus_state
|
let ds = test_damus_state
|
||||||
DirectMessagesView(damus_state: ds, model: ds.dms, settings: ds.settings)
|
DirectMessagesView(damus_state: ds, model: ds.dms, settings: ds.settings, subtitle: .constant(nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ struct ErrorView: View {
|
|||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.padding(.vertical, 30)
|
.padding(.vertical, 30)
|
||||||
|
|
||||||
|
if let technical_info = error.technical_info {
|
||||||
|
ErrorTechInfoCopyButton(errorInfo: technical_info)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
if let damus_state, damus_state.is_privkey_user {
|
if let damus_state, damus_state.is_privkey_user {
|
||||||
@@ -69,6 +73,39 @@ struct ErrorView: View {
|
|||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ErrorTechInfoCopyButton: View {
|
||||||
|
let errorInfo: String
|
||||||
|
@State var copied: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
if !copied {
|
||||||
|
Button(action: {
|
||||||
|
UIPasteboard.general.string = errorInfo
|
||||||
|
copied = true
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||||
|
copied = false
|
||||||
|
}
|
||||||
|
}, label: {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "square.on.square.dashed")
|
||||||
|
Text("Copy technical information", comment: "Button label to allow user to copy technical information from an error screen (usually to provide our support team for further troubleshooting)")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "checkmark.circle")
|
||||||
|
Text("Copied!", comment: "Label indicating that the error technical information was successfully copied to the clipboard, which shows up as soon as the user clicks the copy button.")
|
||||||
|
}
|
||||||
|
.foregroundStyle(.damusGreen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An error that is displayed to the user, and can be sent to the Developers as well.
|
/// An error that is displayed to the user, and can be sent to the Developers as well.
|
||||||
struct UserPresentableError {
|
struct UserPresentableError {
|
||||||
/// The description of the error to be shown to the user
|
/// The description of the error to be shown to the user
|
||||||
@@ -113,7 +150,7 @@ struct ErrorView: View {
|
|||||||
error: .init(
|
error: .init(
|
||||||
user_visible_description: "We are still too early",
|
user_visible_description: "We are still too early",
|
||||||
tip: "Stay humble, keep building, stack sats",
|
tip: "Stay humble, keep building, stack sats",
|
||||||
technical_info: nil
|
technical_info: "UTXOs too small, must stack more sats"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
//
|
||||||
|
// FollowPackPreview.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/30/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct FollowPackUsers: View {
|
||||||
|
let state: DamusState
|
||||||
|
var publicKeys: [Pubkey]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
|
||||||
|
if !publicKeys.isEmpty {
|
||||||
|
CondensedProfilePicturesView(state: state, pubkeys: publicKeys, maxPictures: 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
let followPackUserCount = publicKeys.count
|
||||||
|
let nounString = pluralizedString(key: "follow_pack_user_count", count: followPackUserCount)
|
||||||
|
let nounText = Text(verbatim: nounString).font(.subheadline).foregroundColor(.gray)
|
||||||
|
Text("\(Text(verbatim: followPackUserCount.formatted()).font(.subheadline.weight(.medium))) \(nounText)", comment: "Sentence composed of 2 variables to describe how many people are in the follow pack. In source English, the first variable is the number of users, and the second variable is 'user' or 'users'.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FollowPackBannerImage: View {
|
||||||
|
let state: DamusState
|
||||||
|
let options: EventViewOptions
|
||||||
|
var image: URL? = nil
|
||||||
|
var preview: Bool
|
||||||
|
@State var blur_imgs: Bool
|
||||||
|
|
||||||
|
func Placeholder(url: URL, preview: Bool) -> some View {
|
||||||
|
Group {
|
||||||
|
if let meta = state.events.lookup_img_metadata(url: url),
|
||||||
|
case .processed(let blurhash) = meta.state {
|
||||||
|
Image(uiImage: blurhash)
|
||||||
|
.resizable()
|
||||||
|
.frame(maxWidth: preview ? 350 : UIScreen.main.bounds.width, minHeight: preview ? 180 : 200, maxHeight: preview ? 180 : 200)
|
||||||
|
} else {
|
||||||
|
DamusColors.adaptableWhite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func titleImage(url: URL, preview: Bool) -> some View {
|
||||||
|
KFAnimatedImage(url)
|
||||||
|
.callbackQueue(.dispatch(.global(qos:.background)))
|
||||||
|
.backgroundDecode(true)
|
||||||
|
.imageContext(.note, disable_animation: state.settings.disable_animation)
|
||||||
|
.image_fade(duration: 0.25)
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.configure { view in
|
||||||
|
view.framePreloadCount = 3
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
Placeholder(url: url, preview: preview)
|
||||||
|
}
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(maxWidth: preview ? 350 : UIScreen.main.bounds.width, minHeight: preview ? 180 : 200, maxHeight: preview ? 180 : 200)
|
||||||
|
.kfClickable()
|
||||||
|
.cornerRadius(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if let url = image {
|
||||||
|
if (self.options.contains(.no_media)) {
|
||||||
|
EmptyView()
|
||||||
|
} else if !blur_imgs {
|
||||||
|
titleImage(url: url, preview: preview)
|
||||||
|
} else {
|
||||||
|
ZStack {
|
||||||
|
titleImage(url: url, preview: preview)
|
||||||
|
BlurOverlayView(blur_images: $blur_imgs, artifacts: nil, size: nil, damus_state: nil, parentView: .longFormView)
|
||||||
|
.frame(maxWidth: preview ? 350 : UIScreen.main.bounds.width, minHeight: preview ? 180 : 200, maxHeight: preview ? 180 : 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(NSLocalizedString("No cover image", comment: "Text letting user know there is no cover image."))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.frame(width: 350, height: 180)
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FollowPackPreviewBody: View {
|
||||||
|
let state: DamusState
|
||||||
|
let event: FollowPackEvent
|
||||||
|
let options: EventViewOptions
|
||||||
|
let header: Bool
|
||||||
|
@State var blur_imgs: Bool
|
||||||
|
|
||||||
|
@ObservedObject var artifacts: NoteArtifactsModel
|
||||||
|
|
||||||
|
init(state: DamusState, ev: FollowPackEvent, options: EventViewOptions, header: Bool, blur_imgs: Bool) {
|
||||||
|
self.state = state
|
||||||
|
self.event = ev
|
||||||
|
self.options = options
|
||||||
|
self.header = header
|
||||||
|
self.blur_imgs = blur_imgs
|
||||||
|
|
||||||
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.event.id).artifacts_model)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(state: DamusState, ev: NostrEvent, options: EventViewOptions, header: Bool, blur_imgs: Bool) {
|
||||||
|
self.state = state
|
||||||
|
self.event = FollowPackEvent.parse(from: ev)
|
||||||
|
self.options = options
|
||||||
|
self.header = header
|
||||||
|
self.blur_imgs = blur_imgs
|
||||||
|
|
||||||
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.id).artifacts_model)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if options.contains(.wide) {
|
||||||
|
Main.padding(.horizontal)
|
||||||
|
} else {
|
||||||
|
Main
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var Main: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
|
||||||
|
if state.settings.media_previews {
|
||||||
|
FollowPackBannerImage(state: state, options: options, image: event.image, preview: true, blur_imgs: blur_imgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(event.title ?? NSLocalizedString("Untitled", comment: "Title of follow list event if it is untitled."))
|
||||||
|
.font(header ? .title : .headline)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.top, 5)
|
||||||
|
|
||||||
|
if let description = event.description {
|
||||||
|
Text(description)
|
||||||
|
.font(header ? .body : .caption)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
} else {
|
||||||
|
Text("")
|
||||||
|
.font(header ? .body : .caption)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
ProfilePicView(pubkey: event.event.pubkey, size: 25, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, show_zappability: true)
|
||||||
|
.onTapGesture {
|
||||||
|
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
||||||
|
}
|
||||||
|
let profile_txn = state.profiles.lookup(id: event.event.pubkey)
|
||||||
|
let profile = profile_txn?.unsafeUnownedValue
|
||||||
|
let displayName = Profile.displayName(profile: profile, pubkey: event.event.pubkey)
|
||||||
|
switch displayName {
|
||||||
|
case .one(let one):
|
||||||
|
Text(one)
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
|
||||||
|
case .both(username: let username, displayName: let displayName):
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Text(verbatim: displayName)
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
|
||||||
|
Text(verbatim: "@\(username)")
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
|
||||||
|
FollowPackUsers(state: state, publicKeys: event.publicKeys)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
}
|
||||||
|
.frame(width: 350, height: state.settings.media_previews ? 330 : 150, alignment: .leading)
|
||||||
|
.background(DamusColors.neutral3)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.stroke(DamusColors.neutral1, lineWidth: 1)
|
||||||
|
)
|
||||||
|
.padding(.top, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FollowPackPreview: View {
|
||||||
|
let state: DamusState
|
||||||
|
let event: FollowPackEvent
|
||||||
|
let options: EventViewOptions
|
||||||
|
@State var blur_imgs: Bool
|
||||||
|
|
||||||
|
init(state: DamusState, ev: NostrEvent, options: EventViewOptions, blur_imgs: Bool) {
|
||||||
|
self.state = state
|
||||||
|
self.event = FollowPackEvent.parse(from: ev)
|
||||||
|
self.options = options.union(.no_mentions)
|
||||||
|
self.blur_imgs = blur_imgs
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
FollowPackPreviewBody(state: state, ev: event, options: options, header: false, blur_imgs: blur_imgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let test_follow_list_event = FollowPackEvent.parse(from: NostrEvent(
|
||||||
|
content: "",
|
||||||
|
keypair: test_keypair,
|
||||||
|
kind: NostrKind.longform.rawValue,
|
||||||
|
tags: [
|
||||||
|
["title", "DAMUSES"],
|
||||||
|
["description", "Damus Team"],
|
||||||
|
["published_at", "1685638715"],
|
||||||
|
["p", "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],
|
||||||
|
["p", "8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6"],
|
||||||
|
["p", "17538dc2a62769d09443f18c37cbe358fab5bbf981173542aa7c5ff171ed77c4"],
|
||||||
|
["p", "520830c334a3f79f88cac934580d26f91a7832c6b21fb9625690ea2ed81b5626"],
|
||||||
|
["p", "2779f3d9f42c7dee17f0e6bcdcf89a8f9d592d19e3b1bbd27ef1cffd1a7f98d1"],
|
||||||
|
["p", "bd1e19980e2c91e6dc657e92c25762ca882eb9272d2579e221f037f93788de91"],
|
||||||
|
["p", "e7424ad457e512fdf4764a56bf6d428a06a13a1006af1fb8e0fe32f6d03265c7"],
|
||||||
|
["p", "b88c7f007bbf3bc2fcaeff9e513f186bab33782c0baa6a6cc12add78b9110ba3"],
|
||||||
|
["p", "4a0510f26880d40e432f4865cb5714d9d3c200ca6ebb16b418ae6c555f574967"],
|
||||||
|
["image", "https://damus.io/img/logo.png"],
|
||||||
|
])!
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
struct FollowPackPreview_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
VStack {
|
||||||
|
FollowPackPreview(state: test_damus_state, ev: test_follow_list_event.event, options: [], blur_imgs: false)
|
||||||
|
}
|
||||||
|
.frame(height: 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
//
|
||||||
|
// FollowPackTimeline.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 5/6/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FollowPackTimelineView<Content: View>: View {
|
||||||
|
@ObservedObject var events: EventHolder
|
||||||
|
@Binding var loading: Bool
|
||||||
|
|
||||||
|
let damus: DamusState
|
||||||
|
let show_friend_icon: Bool
|
||||||
|
let filter: (NostrEvent) -> Bool
|
||||||
|
let content: Content?
|
||||||
|
let apply_mute_rules: Bool
|
||||||
|
|
||||||
|
init(events: EventHolder, loading: Binding<Bool>, headerHeight: Binding<CGFloat>, headerOffset: Binding<CGFloat>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) {
|
||||||
|
self.events = events
|
||||||
|
self._loading = loading
|
||||||
|
self.damus = damus
|
||||||
|
self.show_friend_icon = show_friend_icon
|
||||||
|
self.filter = filter
|
||||||
|
self.apply_mute_rules = apply_mute_rules
|
||||||
|
self.content = content?()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(events: EventHolder, loading: Binding<Bool>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) {
|
||||||
|
self.events = events
|
||||||
|
self._loading = loading
|
||||||
|
self.damus = damus
|
||||||
|
self.show_friend_icon = show_friend_icon
|
||||||
|
self.filter = filter
|
||||||
|
self.apply_mute_rules = apply_mute_rules
|
||||||
|
self.content = content?()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
MainContent
|
||||||
|
}
|
||||||
|
|
||||||
|
var MainContent: some View {
|
||||||
|
ScrollViewReader { scroller in
|
||||||
|
ScrollView(.horizontal) {
|
||||||
|
if let content {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
|
||||||
|
Color.clear
|
||||||
|
.id("startblock")
|
||||||
|
.frame(height: 0)
|
||||||
|
|
||||||
|
FollowPackInnerView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules)
|
||||||
|
.redacted(reason: loading ? .placeholder : [])
|
||||||
|
.shimmer(loading)
|
||||||
|
.disabled(loading)
|
||||||
|
.background {
|
||||||
|
GeometryReader { proxy -> Color in
|
||||||
|
handle_scroll_queue(proxy, queue: self.events)
|
||||||
|
return Color.clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.coordinateSpace(name: "scroll")
|
||||||
|
.onReceive(handle_notify(.scroll_to_top)) { () in
|
||||||
|
events.flush()
|
||||||
|
self.events.should_queue = false
|
||||||
|
scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
events.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FollowPackInnerView: View {
|
||||||
|
@ObservedObject var events: EventHolder
|
||||||
|
let state: DamusState
|
||||||
|
let filter: (NostrEvent) -> Bool
|
||||||
|
|
||||||
|
init(events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true) {
|
||||||
|
self.events = events
|
||||||
|
self.state = damus
|
||||||
|
self.filter = apply_mute_rules ? { filter($0) && !damus.mutelist_manager.is_event_muted($0) } : filter
|
||||||
|
}
|
||||||
|
|
||||||
|
var event_options: EventViewOptions {
|
||||||
|
if self.state.settings.truncate_timeline_text {
|
||||||
|
return [.wide, .truncate_content]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.wide]
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
LazyHStack(spacing: 0) {
|
||||||
|
let events = self.events.events
|
||||||
|
if events.isEmpty {
|
||||||
|
EmptyTimelineView()
|
||||||
|
} else {
|
||||||
|
let evs = events.filter(filter)
|
||||||
|
let indexed = Array(zip(evs, 0...))
|
||||||
|
ForEach(indexed, id: \.0.id) { tup in
|
||||||
|
let ev = tup.0
|
||||||
|
let ind = tup.1
|
||||||
|
let blur_imgs = should_blur_images(settings: state.settings, contacts: state.contacts, ev: ev, our_pubkey: state.pubkey)
|
||||||
|
if ev.kind == NostrKind.follow_list.rawValue {
|
||||||
|
FollowPackPreview(state: state, ev: ev, options: event_options, blur_imgs: blur_imgs)
|
||||||
|
.onTapGesture {
|
||||||
|
state.nav.push(route: Route.FollowPack(followPack: ev, model: FollowPackModel(damus_state: state), blur_imgs: blur_imgs))
|
||||||
|
}
|
||||||
|
.padding(.top, 7)
|
||||||
|
.onAppear {
|
||||||
|
let to_preload =
|
||||||
|
Array([indexed[safe: ind+1]?.0,
|
||||||
|
indexed[safe: ind+2]?.0,
|
||||||
|
indexed[safe: ind+3]?.0,
|
||||||
|
indexed[safe: ind+4]?.0,
|
||||||
|
indexed[safe: ind+5]?.0
|
||||||
|
].compactMap({ $0 }))
|
||||||
|
|
||||||
|
preload_events(state: state, events: to_preload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
//
|
||||||
|
// FollowPackView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/30/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct FollowPackView: View {
|
||||||
|
let state: DamusState
|
||||||
|
let event: FollowPackEvent
|
||||||
|
@StateObject var model: FollowPackModel
|
||||||
|
@State var blur_imgs: Bool
|
||||||
|
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
|
@ObservedObject var artifacts: NoteArtifactsModel
|
||||||
|
|
||||||
|
init(state: DamusState, ev: FollowPackEvent, model: FollowPackModel, blur_imgs: Bool) {
|
||||||
|
self.state = state
|
||||||
|
self.event = ev
|
||||||
|
self._model = StateObject(wrappedValue: model)
|
||||||
|
self.blur_imgs = blur_imgs
|
||||||
|
|
||||||
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.event.id).artifacts_model)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(state: DamusState, ev: NostrEvent, model: FollowPackModel, blur_imgs: Bool) {
|
||||||
|
self.state = state
|
||||||
|
self.event = FollowPackEvent.parse(from: ev)
|
||||||
|
self._model = StateObject(wrappedValue: model)
|
||||||
|
self.blur_imgs = blur_imgs
|
||||||
|
|
||||||
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.id).artifacts_model)
|
||||||
|
}
|
||||||
|
|
||||||
|
func content_filter(_ pubkeys: [Pubkey]) -> ((NostrEvent) -> Bool) {
|
||||||
|
var filters = ContentFilters.defaults(damus_state: self.state)
|
||||||
|
filters.append({ pubkeys.contains($0.pubkey) })
|
||||||
|
return ContentFilters(filters: filters).filter
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FollowPackTabSelection: Int {
|
||||||
|
case people = 0
|
||||||
|
case posts = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@State var tab_selection: FollowPackTabSelection = .people
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
ScrollView {
|
||||||
|
FollowPackHeader
|
||||||
|
|
||||||
|
FollowPackTabs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
if model.events.events.isEmpty {
|
||||||
|
model.subscribe(follow_pack_users: event.publicKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
model.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tabs: [(String, FollowPackTabSelection)] {
|
||||||
|
let tabs = [
|
||||||
|
(NSLocalizedString("People", comment: "Label for filter for seeing the people in this follow pack."), FollowPackTabSelection.people),
|
||||||
|
(NSLocalizedString("Posts", comment: "Label for filter for seeing the posts from the people in this follow pack."), FollowPackTabSelection.posts)
|
||||||
|
]
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
var FollowPackTabs: some View {
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
CustomPicker(tabs: tabs, selection: $tab_selection)
|
||||||
|
Divider()
|
||||||
|
.frame(height: 1)
|
||||||
|
}
|
||||||
|
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||||
|
|
||||||
|
if tab_selection == FollowPackTabSelection.people {
|
||||||
|
LazyVStack(alignment: .leading) {
|
||||||
|
ForEach(event.publicKeys.reversed(), id: \.self) { pk in
|
||||||
|
FollowUserView(target: .pubkey(pk), damus_state: state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.padding(.bottom, 50)
|
||||||
|
.tag(FollowPackTabSelection.people)
|
||||||
|
.id(FollowPackTabSelection.people)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tab_selection == FollowPackTabSelection.posts {
|
||||||
|
InnerTimelineView(events: model.events, damus: state, filter: content_filter(event.publicKeys))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear() {
|
||||||
|
model.subscribe(follow_pack_users: event.publicKeys)
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
model.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var FollowPackHeader: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
|
||||||
|
if state.settings.media_previews {
|
||||||
|
FollowPackBannerImage(state: state, options: EventViewOptions(), image: event.image, preview: false, blur_imgs: blur_imgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(event.title ?? NSLocalizedString("Untitled", comment: "Title of follow list event if it is untitled."))
|
||||||
|
.font(.title)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.top, 5)
|
||||||
|
|
||||||
|
if let description = event.description {
|
||||||
|
Text(description)
|
||||||
|
.font(.body)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
ProfilePicView(pubkey: event.event.pubkey, size: 25, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, show_zappability: true)
|
||||||
|
.onTapGesture {
|
||||||
|
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
||||||
|
}
|
||||||
|
let profile_txn = state.profiles.lookup(id: event.event.pubkey)
|
||||||
|
let profile = profile_txn?.unsafeUnownedValue
|
||||||
|
let displayName = Profile.displayName(profile: profile, pubkey: event.event.pubkey)
|
||||||
|
switch displayName {
|
||||||
|
case .one(let one):
|
||||||
|
Text("Created by \(one)", comment: "Lets the user know who created this follow pack.")
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
|
||||||
|
case .both(username: let username, displayName: let displayName):
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Text("Created by \(displayName)", comment: "Lets the user know who created this follow pack.")
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
|
||||||
|
Text(verbatim: "@\(username)")
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
FollowPackUsers(state: state, publicKeys: event.publicKeys)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct FollowPackView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
VStack {
|
||||||
|
FollowPackView(state: test_damus_state, ev: test_follow_list_event, model: FollowPackModel(damus_state: test_damus_state), blur_imgs: false)
|
||||||
|
}
|
||||||
|
.frame(height: 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,7 +74,7 @@ class LoadableNostrEventViewModel: ObservableObject {
|
|||||||
case .zap, .zap_request:
|
case .zap, .zap_request:
|
||||||
guard let zap = await get_zap(from: ev, state: damus_state) else { return .not_found }
|
guard let zap = await get_zap(from: ev, state: damus_state) else { return .not_found }
|
||||||
return .loaded(route: Route.Zaps(target: zap.target))
|
return .loaded(route: Route.Zaps(target: zap.target))
|
||||||
case .contacts, .metadata, .delete, .boost, .chat, .mute_list, .list_deprecated, .draft, .longform, .nwc_request, .nwc_response, .http_auth, .status, .relay_list:
|
case .contacts, .metadata, .delete, .boost, .chat, .mute_list, .list_deprecated, .draft, .longform, .nwc_request, .nwc_response, .http_auth, .status, .relay_list, .follow_list:
|
||||||
return .unknown_or_unsupported_kind
|
return .unknown_or_unsupported_kind
|
||||||
}
|
}
|
||||||
case .naddr(let naddr):
|
case .naddr(let naddr):
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// NIP05DomainPubkeysView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 5/23/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FaviconFinder
|
||||||
|
import Kingfisher
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct NIP05DomainPubkeysView: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
let domain: String
|
||||||
|
let nip05_domain_favicon: FaviconURL?
|
||||||
|
let pubkeys: [Pubkey]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack(alignment: .leading) {
|
||||||
|
ForEach(pubkeys, id: \.self) { pk in
|
||||||
|
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .principal) {
|
||||||
|
HStack {
|
||||||
|
if let nip05_domain_favicon {
|
||||||
|
KFImage(nip05_domain_favicon.source)
|
||||||
|
.imageContext(.favicon, disable_animation: true)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 18, height: 18)
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
Text(domain)
|
||||||
|
.font(.headline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
let nip05_domain_favicon = FaviconURL(source: URL(string: "https://damus.io/favicon.ico")!, format: .ico, sourceType: .ico)
|
||||||
|
let pubkeys = [test_pubkey, test_pubkey_2]
|
||||||
|
NIP05DomainPubkeysView(damus_state: test_damus_state, domain: "damus.io", nip05_domain_favicon: nip05_domain_favicon, pubkeys: pubkeys)
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
//
|
||||||
|
// NIP05DomainTimelineHeaderView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 5/16/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FaviconFinder
|
||||||
|
import Kingfisher
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct NIP05DomainTimelineHeaderView: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
@ObservedObject var model: NIP05DomainEventsModel
|
||||||
|
let nip05_domain_favicon: FaviconURL?
|
||||||
|
|
||||||
|
@Environment(\.openURL) var openURL
|
||||||
|
|
||||||
|
var Icon: some View {
|
||||||
|
ZStack {
|
||||||
|
if let nip05_domain_favicon {
|
||||||
|
KFImage(nip05_domain_favicon.source)
|
||||||
|
.imageContext(.favicon, disable_animation: true)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 18, height: 18)
|
||||||
|
.clipped()
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var friendsOfFriends: [Pubkey] {
|
||||||
|
// Order it such that the pubkeys that have events come first in the array so that their profile pictures
|
||||||
|
// show first.
|
||||||
|
let pubkeys = model.events.all_events.map { $0.pubkey } + (model.filter.authors ?? [])
|
||||||
|
|
||||||
|
// Filter out duplicates but retain order, and filter out any that do not have a validated NIP-05.
|
||||||
|
return (NSMutableOrderedSet(array: pubkeys).array as? [Pubkey] ?? [])
|
||||||
|
.filter {
|
||||||
|
damus_state.contacts.is_in_friendosphere($0) && damus_state.profiles.is_validated($0) != nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
HStack {
|
||||||
|
if nip05_domain_favicon != nil {
|
||||||
|
Icon
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(model.domain)
|
||||||
|
.foregroundStyle(DamusLogoGradient.gradient)
|
||||||
|
.font(.title.bold())
|
||||||
|
.onTapGesture {
|
||||||
|
if let url = URL(string: "https://\(model.domain)") {
|
||||||
|
openURL(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let friendsOfFriends = friendsOfFriends
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
CondensedProfilePicturesView(state: damus_state, pubkeys: friendsOfFriends, maxPictures: 3)
|
||||||
|
let friendsOfFriendsString = friendsOfFriendsString(friendsOfFriends, ndb: damus_state.ndb)
|
||||||
|
Text(friendsOfFriendsString)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
if !friendsOfFriends.isEmpty {
|
||||||
|
damus_state.nav.push(route: Route.NIP05DomainPubkeys(domain: model.domain, nip05_domain_favicon: nip05_domain_favicon, pubkeys: friendsOfFriends))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func friendsOfFriendsString(_ friendsOfFriends: [Pubkey], ndb: Ndb, locale: Locale = Locale.current) -> String {
|
||||||
|
let bundle = bundleForLocale(locale: locale)
|
||||||
|
let names: [String] = friendsOfFriends.prefix(3).map { pk in
|
||||||
|
let profile = ndb.lookup_profile(pk)?.unsafeUnownedValue?.profile
|
||||||
|
return Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch friendsOfFriends.count {
|
||||||
|
case 0:
|
||||||
|
return "No one in your trusted network is associated with this domain."
|
||||||
|
case 1:
|
||||||
|
let format = NSLocalizedString("Notes from %@", bundle: bundle, comment: "Text to indicate that notes from one pubkey in our trusted network are shown below.")
|
||||||
|
return String(format: format, locale: locale, names[0])
|
||||||
|
case 2:
|
||||||
|
let format = NSLocalizedString("Notes from %@ & %@", bundle: bundle, comment: "Text to indicate that notes from two pubkeys in our trusted network are shown below.")
|
||||||
|
return String(format: format, locale: locale, names[0], names[1])
|
||||||
|
case 3:
|
||||||
|
let format = NSLocalizedString("Notes from %@, %@ & %@", bundle: bundle, comment: "Text to indicate that notes from three pubkeys in our trusted network are shown below.")
|
||||||
|
return String(format: format, locale: locale, names[0], names[1], names[2])
|
||||||
|
default:
|
||||||
|
let format = localizedStringFormat(key: "notes_from_three_and_others", locale: locale)
|
||||||
|
return String(format: format, locale: locale, friendsOfFriends.count - 3, names[0], names[1], names[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
let model = NIP05DomainEventsModel(state: test_damus_state, domain: "damus.io")
|
||||||
|
let nip05_domain_favicon = FaviconURL(source: URL(string: "https://damus.io/favicon.ico")!, format: .ico, sourceType: .ico)
|
||||||
|
NIP05DomainTimelineHeaderView(damus_state: test_damus_state, model: model, nip05_domain_favicon: nip05_domain_favicon)
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// NIP05DomainTimelineView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 4/11/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FaviconFinder
|
||||||
|
import Kingfisher
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct NIP05DomainTimelineView: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
@ObservedObject var model: NIP05DomainEventsModel
|
||||||
|
let nip05_domain_favicon: FaviconURL?
|
||||||
|
|
||||||
|
func nip05_filter(ev: NostrEvent) -> Bool {
|
||||||
|
damus_state.contacts.is_in_friendosphere(ev.pubkey) && damus_state.profiles.is_validated(ev.pubkey) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentFilters: ContentFilters {
|
||||||
|
var filters = Array<(NostrEvent) -> Bool>()
|
||||||
|
filters.append(contentsOf: ContentFilters.defaults(damus_state: damus_state))
|
||||||
|
filters.append(nip05_filter)
|
||||||
|
return ContentFilters(filters: filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let height: CGFloat = 250.0
|
||||||
|
|
||||||
|
TimelineView(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: contentFilters.filter(ev:)) {
|
||||||
|
ZStack(alignment: .leading) {
|
||||||
|
DamusBackground(maxHeight: height)
|
||||||
|
.mask(LinearGradient(gradient: Gradient(colors: [.black, .black, .black, .clear]), startPoint: .top, endPoint: .bottom))
|
||||||
|
NIP05DomainTimelineHeaderView(damus_state: damus_state, model: model, nip05_domain_favicon: nip05_domain_favicon)
|
||||||
|
.padding(.leading, 30)
|
||||||
|
.padding(.top, 30)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.padding(.bottom, tabHeight)
|
||||||
|
.onAppear {
|
||||||
|
guard model.events.all_events.isEmpty else { return }
|
||||||
|
|
||||||
|
model.subscribe()
|
||||||
|
|
||||||
|
if let pubkeys = model.filter.authors {
|
||||||
|
for pubkey in pubkeys {
|
||||||
|
check_nip05_validity(pubkey: pubkey, profiles: damus_state.profiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
model.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
let damus_state = test_damus_state
|
||||||
|
let model = NIP05DomainEventsModel(state: damus_state, domain: "damus.io")
|
||||||
|
let nip05_domain_favicon = FaviconURL(source: URL(string: "https://damus.io/favicon.ico")!, format: .ico, sourceType: .ico)
|
||||||
|
NIP05DomainTimelineView(damus_state: test_damus_state, model: model, nip05_domain_favicon: nip05_domain_favicon)
|
||||||
|
}
|
||||||
@@ -73,15 +73,40 @@ struct NoteContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var preview: LinkViewRepresentable? {
|
var preview: LinkViewRepresentable? {
|
||||||
guard !blur_images,
|
guard case .loaded(let preview) = preview_model.state,
|
||||||
case .loaded(let preview) = preview_model.state,
|
|
||||||
case .value(let cached) = preview else {
|
case .value(let cached) = preview else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If either
|
||||||
|
// (1) the blur images setting is enabled
|
||||||
|
// (2) the media previews setting is disabled
|
||||||
|
// (3) this note content view does not display media
|
||||||
|
// then do not show media in the link preview.
|
||||||
|
if blur_images || !damus_state.settings.media_previews || self.options.contains(.no_media) {
|
||||||
|
return linkPreviewWithNoMedia(cached)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If media is already being shown, do not show media in the link preview
|
||||||
|
// to avoid taking up additional screen space.
|
||||||
|
if case let .separated(separated) = note_artifacts, !separated.media.isEmpty && !self.options.contains(.no_media) {
|
||||||
|
return linkPreviewWithNoMedia(cached)
|
||||||
|
}
|
||||||
|
|
||||||
return LinkViewRepresentable(meta: .linkmeta(cached))
|
return LinkViewRepresentable(meta: .linkmeta(cached))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a LinkViewRepresentable without media previews.
|
||||||
|
func linkPreviewWithNoMedia(_ cached: CachedMetadata) -> LinkViewRepresentable? {
|
||||||
|
let linkMetadata = LPLinkMetadata()
|
||||||
|
|
||||||
|
linkMetadata.originalURL = cached.meta.originalURL
|
||||||
|
linkMetadata.title = cached.meta.title
|
||||||
|
linkMetadata.url = cached.meta.url
|
||||||
|
|
||||||
|
return LinkViewRepresentable(meta: .linkmeta(CachedMetadata(meta: linkMetadata)))
|
||||||
|
}
|
||||||
|
|
||||||
func truncatedText(content: CompatibleText) -> some View {
|
func truncatedText(content: CompatibleText) -> some View {
|
||||||
Group {
|
Group {
|
||||||
if truncate_very_short {
|
if truncate_very_short {
|
||||||
@@ -108,7 +133,7 @@ struct NoteContentView: View {
|
|||||||
|
|
||||||
func previewView(links: [URL]) -> some View {
|
func previewView(links: [URL]) -> some View {
|
||||||
Group {
|
Group {
|
||||||
if let preview = self.preview, !blur_images {
|
if let preview = self.preview {
|
||||||
if let preview_height {
|
if let preview_height {
|
||||||
preview
|
preview
|
||||||
.frame(height: preview_height)
|
.frame(height: preview_height)
|
||||||
@@ -181,7 +206,7 @@ struct NoteContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if damus_state.settings.media_previews, has_previews {
|
if has_previews {
|
||||||
if with_padding {
|
if with_padding {
|
||||||
previewView(links: artifacts.links).padding(.horizontal)
|
previewView(links: artifacts.links).padding(.horizontal)
|
||||||
} else {
|
} else {
|
||||||
@@ -401,7 +426,7 @@ struct BlurOverlayView: View {
|
|||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.bold()
|
.bold()
|
||||||
.padding(EdgeInsets(top: 5, leading: 10, bottom: 0, trailing: 10))
|
.padding(EdgeInsets(top: 5, leading: 10, bottom: 0, trailing: 10))
|
||||||
Text(NSLocalizedString("Media from someone you \n don't follow", comment: "Label on the image blur mask"))
|
Text(NSLocalizedString("Media from someone you don't follow", comment: "Label on the image blur mask"))
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.foregroundStyle(Color.white)
|
.foregroundStyle(Color.white)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import TipKit
|
||||||
|
|
||||||
class NotificationFilter: ObservableObject, Equatable {
|
class NotificationFilter: ObservableObject, Equatable {
|
||||||
@Published var state: NotificationFilterState
|
@Published var state: NotificationFilterState
|
||||||
@@ -79,6 +80,7 @@ struct NotificationsView: View {
|
|||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let showTrustedButton = would_filter_non_friends_from_notifications(contacts: state.contacts, state: filter_state, items: self.notifications.notifications)
|
||||||
TabView(selection: $filter_state) {
|
TabView(selection: $filter_state) {
|
||||||
NotificationTab(
|
NotificationTab(
|
||||||
NotificationFilter(
|
NotificationFilter(
|
||||||
@@ -115,14 +117,19 @@ struct NotificationsView: View {
|
|||||||
Button(
|
Button(
|
||||||
action: { state.nav.push(route: Route.NotificationSettings(settings: state.settings)) },
|
action: { state.nav.push(route: Route.NotificationSettings(settings: state.settings)) },
|
||||||
label: {
|
label: {
|
||||||
Image("settings")
|
Image(systemName: "gearshape")
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
if would_filter_non_friends_from_notifications(contacts: state.contacts, state: filter_state, items: self.notifications.notifications) {
|
if showTrustedButton {
|
||||||
FriendsButton(filter: $filter.friend_filter)
|
TrustedNetworkButton(filter: $filter.friend_filter) {
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
TrustedNetworkButtonTip.shared.invalidate(reason: .actionPerformed)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,6 +147,13 @@ struct NotificationsView: View {
|
|||||||
}
|
}
|
||||||
.safeAreaInset(edge: .top, spacing: 0) {
|
.safeAreaInset(edge: .top, spacing: 0) {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
if #available(iOS 17, *), showTrustedButton {
|
||||||
|
TipView(TrustedNetworkButtonTip.shared)
|
||||||
|
.tipBackground(.clear)
|
||||||
|
.tipViewStyle(TrustedNetworkButtonTipViewStyle())
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
|
||||||
CustomPicker(tabs: [
|
CustomPicker(tabs: [
|
||||||
(NSLocalizedString("All", comment: "Label for filter for all notifications."), NotificationFilterState.all),
|
(NSLocalizedString("All", comment: "Label for filter for all notifications."), NotificationFilterState.all),
|
||||||
(NSLocalizedString("Zaps", comment: "Label for filter for zap notifications."), NotificationFilterState.zaps),
|
(NSLocalizedString("Zaps", comment: "Label for filter for zap notifications."), NotificationFilterState.zaps),
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ struct SuggestedUserView: View {
|
|||||||
let target = FollowTarget.pubkey(user.pubkey)
|
let target = FollowTarget.pubkey(user.pubkey)
|
||||||
InnerProfilePicView(url: user.pfp,
|
InnerProfilePicView(url: user.pfp,
|
||||||
fallbackUrl: nil,
|
fallbackUrl: nil,
|
||||||
pubkey: target.pubkey,
|
|
||||||
size: 50,
|
size: 50,
|
||||||
highlight: .none,
|
highlight: .none,
|
||||||
disable_animation: false)
|
disable_animation: false)
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ struct PostView: View {
|
|||||||
var autoSaveModel: AutoSaveIndicatorView.AutoSaveViewModel
|
var autoSaveModel: AutoSaveIndicatorView.AutoSaveViewModel
|
||||||
|
|
||||||
@State var preUploadedMedia: [PreUploadedMedia] = []
|
@State var preUploadedMedia: [PreUploadedMedia] = []
|
||||||
|
@State var mediaUploadUnderProgress: MediaUpload? = nil
|
||||||
|
|
||||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
||||||
@StateObject var tagModel: TagModel = TagModel()
|
@StateObject var tagModel: TagModel = TagModel()
|
||||||
@@ -330,11 +331,6 @@ struct PostView: View {
|
|||||||
PostButton
|
PostButton
|
||||||
}
|
}
|
||||||
|
|
||||||
if let progress = image_upload.progress {
|
|
||||||
ProgressView(value: progress, total: 1.0)
|
|
||||||
.progressViewStyle(.linear)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
.foregroundColor(DamusColors.neutral3)
|
.foregroundColor(DamusColors.neutral3)
|
||||||
.padding(.top, 5)
|
.padding(.top, 5)
|
||||||
@@ -346,6 +342,7 @@ struct PostView: View {
|
|||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func handle_upload(media: MediaUpload) async -> Bool {
|
func handle_upload(media: MediaUpload) async -> Bool {
|
||||||
|
mediaUploadUnderProgress = media
|
||||||
let uploader = damus_state.settings.default_media_uploader
|
let uploader = damus_state.settings.default_media_uploader
|
||||||
|
|
||||||
let img = getImage(media: media)
|
let img = getImage(media: media)
|
||||||
@@ -354,6 +351,7 @@ struct PostView: View {
|
|||||||
async let blurhash = calculate_blurhash(img: img)
|
async let blurhash = calculate_blurhash(img: img)
|
||||||
let res = await image_upload.start(media: media, uploader: uploader, mediaType: .normal, keypair: damus_state.keypair)
|
let res = await image_upload.start(media: media, uploader: uploader, mediaType: .normal, keypair: damus_state.keypair)
|
||||||
|
|
||||||
|
mediaUploadUnderProgress = nil
|
||||||
switch res {
|
switch res {
|
||||||
case .success(let url):
|
case .success(let url):
|
||||||
guard let url = URL(string: url) else {
|
guard let url = URL(string: url) else {
|
||||||
@@ -401,7 +399,10 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
.id("post")
|
.id("post")
|
||||||
|
|
||||||
PVImageCarouselView(media: $uploadedMedias, deviceWidth: deviceSize.size.width)
|
PVImageCarouselView(media: $uploadedMedias,
|
||||||
|
mediaUnderProgress: $mediaUploadUnderProgress,
|
||||||
|
imageUploadModel: image_upload,
|
||||||
|
deviceWidth: deviceSize.size.width)
|
||||||
.onChange(of: uploadedMedias) { media in
|
.onChange(of: uploadedMedias) { media in
|
||||||
post_changed(post: post, media: media)
|
post_changed(post: post, media: media)
|
||||||
}
|
}
|
||||||
@@ -620,6 +621,8 @@ struct PostView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
struct PVImageCarouselView: View {
|
struct PVImageCarouselView: View {
|
||||||
@Binding var media: [UploadedMedia]
|
@Binding var media: [UploadedMedia]
|
||||||
|
@Binding var mediaUnderProgress: MediaUpload?
|
||||||
|
@ObservedObject var imageUploadModel: ImageUploadModel
|
||||||
|
|
||||||
let deviceWidth: CGFloat
|
let deviceWidth: CGFloat
|
||||||
|
|
||||||
@@ -667,6 +670,25 @@ struct PVImageCarouselView: View {
|
|||||||
.padding(.bottom, 35)
|
.padding(.bottom, 35)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let mediaUP = mediaUnderProgress, let progress = imageUploadModel.progress {
|
||||||
|
ZStack {
|
||||||
|
// Media under upload-progress
|
||||||
|
Image(uiImage: getImage(media: mediaUP))
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: media.count == 0 ? deviceWidth * 0.8 : 250, height: media.count == 0 ? 400 : 250)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.opacity(0.3)
|
||||||
|
.padding()
|
||||||
|
// Circle showing progress on top of media
|
||||||
|
Circle()
|
||||||
|
.trim(from: 0, to: CGFloat(progress))
|
||||||
|
.stroke(Color.damusPurple, lineWidth: 5.0)
|
||||||
|
.rotationEffect(.degrees(-90))
|
||||||
|
.frame(width: 30, height: 30)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// Created by William Casarin on 2022-04-16.
|
// Created by William Casarin on 2022-04-16.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import FaviconFinder
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum FriendType {
|
enum FriendType {
|
||||||
@@ -43,6 +44,7 @@ struct ProfileName: View {
|
|||||||
@State var nip05: NIP05?
|
@State var nip05: NIP05?
|
||||||
@State var donation: Int?
|
@State var donation: Int?
|
||||||
@State var purple_account: DamusPurple.Account?
|
@State var purple_account: DamusPurple.Account?
|
||||||
|
@State var nip05_domain_favicon: FaviconURL?
|
||||||
|
|
||||||
init(pubkey: Pubkey, prefix: String = "", damus: DamusState, show_nip5_domain: Bool = true, supporterBadgeStyle: SupporterBadge.Style = .compact) {
|
init(pubkey: Pubkey, prefix: String = "", damus: DamusState, show_nip5_domain: Bool = true, supporterBadgeStyle: SupporterBadge.Style = .compact) {
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
@@ -101,7 +103,7 @@ struct ProfileName: View {
|
|||||||
.fontWeight(prefix == "@" ? .none : .bold)
|
.fontWeight(prefix == "@" ? .none : .bold)
|
||||||
|
|
||||||
if let nip05 = current_nip05 {
|
if let nip05 = current_nip05 {
|
||||||
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: show_nip5_domain, profiles: damus_state.profiles)
|
NIP05Badge(nip05: nip05, pubkey: pubkey, damus_state: damus_state, show_domain: show_nip5_domain, nip05_domain_favicon: nip05_domain_favicon)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let friend = friend_type, current_nip05 == nil {
|
if let friend = friend_type, current_nip05 == nil {
|
||||||
@@ -122,6 +124,12 @@ struct ProfileName: View {
|
|||||||
self.purple_account = try? await damus_state.purple.get_maybe_cached_account(pubkey: pubkey)
|
self.purple_account = try? await damus_state.purple.get_maybe_cached_account(pubkey: pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.task {
|
||||||
|
if let domain = current_nip05?.host {
|
||||||
|
self.nip05_domain_favicon = try? await damus_state.favicon_cache.lookup(domain)
|
||||||
|
.largest()
|
||||||
|
}
|
||||||
|
}
|
||||||
.onReceive(handle_notify(.profile_updated)) { update in
|
.onReceive(handle_notify(.profile_updated)) { update in
|
||||||
if update.pubkey != pubkey {
|
if update.pubkey != pubkey {
|
||||||
return
|
return
|
||||||
@@ -151,6 +159,24 @@ struct ProfileName: View {
|
|||||||
let nip05 = damus_state.profiles.is_validated(pubkey)
|
let nip05 = damus_state.profiles.is_validated(pubkey)
|
||||||
if nip05 != self.nip05 {
|
if nip05 != self.nip05 {
|
||||||
self.nip05 = nip05
|
self.nip05 = nip05
|
||||||
|
|
||||||
|
if let domain = nip05?.host {
|
||||||
|
Task {
|
||||||
|
let favicon = try? await damus_state.favicon_cache.lookup(domain)
|
||||||
|
.filter {
|
||||||
|
if let size = $0.size {
|
||||||
|
return size.width <= 128 && size.height <= 128
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.largest()
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
self.nip05_domain_favicon = favicon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if donation != profile.damus_donation {
|
if donation != profile.damus_donation {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ func pfp_line_width(_ h: Highlight) -> CGFloat {
|
|||||||
struct InnerProfilePicView: View {
|
struct InnerProfilePicView: View {
|
||||||
let url: URL?
|
let url: URL?
|
||||||
let fallbackUrl: URL?
|
let fallbackUrl: URL?
|
||||||
let pubkey: Pubkey
|
|
||||||
let size: CGFloat
|
let size: CGFloat
|
||||||
let highlight: Highlight
|
let highlight: Highlight
|
||||||
let disable_animation: Bool
|
let disable_animation: Bool
|
||||||
@@ -65,16 +64,19 @@ struct InnerProfilePicView: View {
|
|||||||
|
|
||||||
|
|
||||||
struct ProfilePicView: View {
|
struct ProfilePicView: View {
|
||||||
|
@Environment(\.redactionReasons) var redactionReasons
|
||||||
|
|
||||||
let pubkey: Pubkey
|
let pubkey: Pubkey
|
||||||
let size: CGFloat
|
let size: CGFloat
|
||||||
let highlight: Highlight
|
let highlight: Highlight
|
||||||
let profiles: Profiles
|
let profiles: Profiles
|
||||||
let disable_animation: Bool
|
let disable_animation: Bool
|
||||||
let zappability_indicator: Bool
|
let zappability_indicator: Bool
|
||||||
|
let privacy_sensitive: Bool
|
||||||
|
|
||||||
@State var picture: String?
|
@State var picture: String?
|
||||||
|
|
||||||
init(pubkey: Pubkey, size: CGFloat, highlight: Highlight, profiles: Profiles, disable_animation: Bool, picture: String? = nil, show_zappability: Bool? = nil) {
|
init(pubkey: Pubkey, size: CGFloat, highlight: Highlight, profiles: Profiles, disable_animation: Bool, picture: String? = nil, show_zappability: Bool? = nil, privacy_sensitive: Bool = false) {
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.profiles = profiles
|
self.profiles = profiles
|
||||||
self.size = size
|
self.size = size
|
||||||
@@ -82,6 +84,15 @@ struct ProfilePicView: View {
|
|||||||
self._picture = State(initialValue: picture)
|
self._picture = State(initialValue: picture)
|
||||||
self.disable_animation = disable_animation
|
self.disable_animation = disable_animation
|
||||||
self.zappability_indicator = show_zappability ?? false
|
self.zappability_indicator = show_zappability ?? false
|
||||||
|
self.privacy_sensitive = privacy_sensitive
|
||||||
|
}
|
||||||
|
|
||||||
|
var privacy_sensitive_pubkey: Pubkey {
|
||||||
|
if privacy_sensitive && redactionReasons.contains(.privacy) {
|
||||||
|
ANON_PUBKEY
|
||||||
|
} else {
|
||||||
|
pubkey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_lnurl() -> String? {
|
func get_lnurl() -> String? {
|
||||||
@@ -90,7 +101,7 @@ struct ProfilePicView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack (alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
|
ZStack (alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
|
||||||
InnerProfilePicView(url: get_profile_url(picture: picture, pubkey: pubkey, profiles: profiles), fallbackUrl: URL(string: robohash(pubkey)), pubkey: pubkey, size: size, highlight: highlight, disable_animation: disable_animation)
|
InnerProfilePicView(url: get_profile_url(picture: picture, pubkey: privacy_sensitive_pubkey, profiles: profiles), fallbackUrl: URL(string: robohash(privacy_sensitive_pubkey)), size: size, highlight: highlight, disable_animation: disable_animation)
|
||||||
.onReceive(handle_notify(.profile_updated)) { updated in
|
.onReceive(handle_notify(.profile_updated)) { updated in
|
||||||
guard updated.pubkey == self.pubkey else {
|
guard updated.pubkey == self.pubkey else {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ struct ProfileView: View {
|
|||||||
var filters = ContentFilters.defaults(damus_state: damus_state)
|
var filters = ContentFilters.defaults(damus_state: damus_state)
|
||||||
filters.append(fstate.filter)
|
filters.append(fstate.filter)
|
||||||
switch fstate {
|
switch fstate {
|
||||||
case .posts, .posts_and_replies:
|
case .posts, .posts_and_replies, .follow_list:
|
||||||
filters.append({ profile.pubkey == $0.pubkey })
|
filters.append({ profile.pubkey == $0.pubkey })
|
||||||
case .conversations:
|
case .conversations:
|
||||||
filters.append({ profile.conversation_events.contains($0.id) } )
|
filters.append({ profile.conversation_events.contains($0.id) } )
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ struct PubkeyView: View {
|
|||||||
let bech32 = pubkey.npub
|
let bech32 = pubkey.npub
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text(verbatim: "\(abbrev_pubkey(bech32, amount: sidemenu ? 12 : 16))")
|
Text(verbatim: "\(abbrev_identifier(bech32, amount: sidemenu ? 12 : 16))")
|
||||||
.font(sidemenu ? .system(size: 10) : .footnote)
|
.font(sidemenu ? .system(size: 10) : .footnote)
|
||||||
.foregroundColor(keyColor())
|
.foregroundColor(keyColor())
|
||||||
.padding(5)
|
.padding(5)
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ struct RelayView: View {
|
|||||||
let state: DamusState
|
let state: DamusState
|
||||||
let relay: RelayURL
|
let relay: RelayURL
|
||||||
let recommended: Bool
|
let recommended: Bool
|
||||||
|
/// Disables navigation link
|
||||||
|
let disableNavLink: Bool
|
||||||
@ObservedObject private var model_cache: RelayModelCache
|
@ObservedObject private var model_cache: RelayModelCache
|
||||||
|
|
||||||
@State var relay_state: Bool
|
@State var relay_state: Bool
|
||||||
@Binding var showActionButtons: Bool
|
@Binding var showActionButtons: Bool
|
||||||
|
|
||||||
init(state: DamusState, relay: RelayURL, showActionButtons: Binding<Bool>, recommended: Bool) {
|
init(state: DamusState, relay: RelayURL, showActionButtons: Binding<Bool>, recommended: Bool, disableNavLink: Bool = false) {
|
||||||
self.state = state
|
self.state = state
|
||||||
self.relay = relay
|
self.relay = relay
|
||||||
self.recommended = recommended
|
self.recommended = recommended
|
||||||
@@ -24,6 +26,7 @@ struct RelayView: View {
|
|||||||
_showActionButtons = showActionButtons
|
_showActionButtons = showActionButtons
|
||||||
let relay_state = RelayView.get_relay_state(pool: state.nostrNetwork.pool, relay: relay)
|
let relay_state = RelayView.get_relay_state(pool: state.nostrNetwork.pool, relay: relay)
|
||||||
self._relay_state = State(initialValue: relay_state)
|
self._relay_state = State(initialValue: relay_state)
|
||||||
|
self.disableNavLink = disableNavLink
|
||||||
}
|
}
|
||||||
|
|
||||||
static func get_relay_state(pool: RelayPool, relay: RelayURL) -> Bool {
|
static func get_relay_state(pool: RelayPool, relay: RelayURL) -> Bool {
|
||||||
@@ -96,21 +99,25 @@ struct RelayView: View {
|
|||||||
RelayStatusView(connection: relay_connection)
|
RelayStatusView(connection: relay_connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !disableNavLink {
|
||||||
Image("chevron-large-right")
|
Image("chevron-large-right")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 15, height: 15)
|
.frame(width: 15, height: 15)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.relays_changed)) { _ in
|
.onReceive(handle_notify(.relays_changed)) { _ in
|
||||||
self.relay_state = RelayView.get_relay_state(pool: state.nostrNetwork.pool, relay: self.relay)
|
self.relay_state = RelayView.get_relay_state(pool: state.nostrNetwork.pool, relay: self.relay)
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
if !disableNavLink {
|
||||||
state.nav.push(route: Route.RelayDetail(relay: relay, metadata: model_cache.model(with_relay_id: relay)?.metadata))
|
state.nav.push(route: Route.RelayDetail(relay: relay, metadata: model_cache.model(with_relay_id: relay)?.metadata))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var relay_connection: RelayConnection? {
|
private var relay_connection: RelayConnection? {
|
||||||
state.nostrNetwork.pool.get_relay(relay)?.connection
|
state.nostrNetwork.pool.get_relay(relay)?.connection
|
||||||
|
|||||||
@@ -12,9 +12,19 @@ struct QuoteRepostsView: View {
|
|||||||
@ObservedObject var model: EventsModel
|
@ObservedObject var model: EventsModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineView<AnyView>(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: ContentFilters.default_filters(damus_state: damus_state).filter(ev:))
|
TimelineView(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: ContentFilters.default_filters(damus_state: damus_state).filter(ev:)) {
|
||||||
|
ZStack(alignment: .leading) {
|
||||||
|
DamusBackground(maxHeight: 250)
|
||||||
|
.mask(LinearGradient(gradient: Gradient(colors: [.black, .black, .black, .clear]), startPoint: .top, endPoint: .bottom))
|
||||||
|
Text("Quotes", comment: "Navigation bar title for Quote Reposts view.")
|
||||||
|
.foregroundStyle(DamusLogoGradient.gradient)
|
||||||
|
.font(.title.bold())
|
||||||
|
.padding(.leading, 30)
|
||||||
|
.padding(.top, 30)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
.padding(.bottom, tabHeight)
|
.padding(.bottom, tabHeight)
|
||||||
.navigationBarTitle(NSLocalizedString("Quotes", comment: "Navigation bar title for Quote Reposts view."))
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
model.subscribe()
|
model.subscribe()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ struct SearchHomeView: View {
|
|||||||
@State var search: String = ""
|
@State var search: String = ""
|
||||||
@FocusState private var isFocused: Bool
|
@FocusState private var isFocused: Bool
|
||||||
|
|
||||||
var content_filter: (NostrEvent) -> Bool {
|
func content_filter(_ fstate: FilterState) -> ((NostrEvent) -> Bool) {
|
||||||
let filters = ContentFilters.defaults(damus_state: self.damus_state)
|
var filters = ContentFilters.defaults(damus_state: damus_state)
|
||||||
|
filters.append(fstate.filter)
|
||||||
return ContentFilters(filters: filters).filter
|
return ContentFilters(filters: filters).filter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,21 +53,20 @@ struct SearchHomeView: View {
|
|||||||
loading: $model.loading,
|
loading: $model.loading,
|
||||||
damus: damus_state,
|
damus: damus_state,
|
||||||
show_friend_icon: true,
|
show_friend_icon: true,
|
||||||
filter: { ev in
|
filter:content_filter(FilterState.posts),
|
||||||
if !content_filter(ev) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let event_muted = damus_state.mutelist_manager.is_event_muted(ev)
|
|
||||||
if event_muted {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
content: {
|
content: {
|
||||||
AnyView(VStack {
|
AnyView(VStack(alignment: .leading) {
|
||||||
SuggestedHashtagsView(damus_state: damus_state, max_items: 5, events: model.events)
|
HStack {
|
||||||
|
Image(systemName: "sparkles")
|
||||||
|
.foregroundStyle(PinkGradient)
|
||||||
|
Text("Follow Packs", comment: "A label indicating that the items below it are follow packs")
|
||||||
|
.foregroundStyle(PinkGradient)
|
||||||
|
}
|
||||||
|
.padding(.top)
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
FollowPackTimelineView<AnyView>(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true,filter:content_filter(FilterState.follow_list)
|
||||||
|
).padding(.bottom)
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ struct AppearanceSettingsView: View {
|
|||||||
header: Text("Content filters", comment: "Section title for content filtering/moderation configuration."),
|
header: Text("Content filters", comment: "Section title for content filtering/moderation configuration."),
|
||||||
footer: Text("Notes with the #nsfw tag usually contains adult content or other \"Not safe for work\" content", comment: "Section footer clarifying what #nsfw (not safe for work) tags mean")
|
footer: Text("Notes with the #nsfw tag usually contains adult content or other \"Not safe for work\" content", comment: "Section footer clarifying what #nsfw (not safe for work) tags mean")
|
||||||
) {
|
) {
|
||||||
|
Toggle(NSLocalizedString("Show replies from your trusted network first", comment: "Setting to show replies in threads from the current user's trusted network first."), isOn: $settings.show_trusted_replies_first)
|
||||||
|
.toggleStyle(.switch)
|
||||||
Toggle(NSLocalizedString("Hide notes with #nsfw tags", comment: "Setting to hide notes with the #nsfw (not safe for work) tags"), isOn: $settings.hide_nsfw_tagged_content)
|
Toggle(NSLocalizedString("Hide notes with #nsfw tags", comment: "Setting to hide notes with the #nsfw (not safe for work) tags"), isOn: $settings.hide_nsfw_tagged_content)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,11 @@ struct DeveloperSettingsView: View {
|
|||||||
|
|
||||||
Toggle(NSLocalizedString("Enable experimental Purple In-app purchase support", comment: "Developer mode setting to enable experimental Purple In-app purchase support."), isOn: $settings.enable_experimental_purple_iap_support)
|
Toggle(NSLocalizedString("Enable experimental Purple In-app purchase support", comment: "Developer mode setting to enable experimental Purple In-app purchase support."), isOn: $settings.enable_experimental_purple_iap_support)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
|
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
Toggle(NSLocalizedString("Reset tips on launch", comment: "Developer mode setting to reset tips upon app first launch. Tips are visual contextual hints that highlight new, interesting, or unused features users have not discovered yet."), isOn: $settings.reset_tips_on_launch)
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ struct KeySettingsView: View {
|
|||||||
.disabled(true)
|
.disabled(true)
|
||||||
} else {
|
} else {
|
||||||
Text(sec)
|
Text(sec)
|
||||||
|
.privacySensitive()
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,13 @@ struct ZapSettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section(NSLocalizedString("NWC wallet", comment: "Title for section in zap settings that controls general NWC wallet settings.")) {
|
||||||
|
Toggle(NSLocalizedString("Disable high balance warning", comment: "Setting to disable high balance warnings on the user's wallet"), isOn: $settings.dismiss_wallet_high_balance_warning)
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
Toggle(NSLocalizedString("Hide balance", comment: "Setting to hide wallet balance."), isOn: $settings.hide_wallet_balance)
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(NSLocalizedString("Zaps", comment: "Navigation title for zap settings."))
|
.navigationTitle(NSLocalizedString("Zaps", comment: "Navigation title for zap settings."))
|
||||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// TrustedNetworkButtonTip.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 6/4/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import TipKit
|
||||||
|
|
||||||
|
@available(iOS 17, *)
|
||||||
|
struct TrustedNetworkButtonTip: Tip {
|
||||||
|
static let shared = TrustedNetworkButtonTip()
|
||||||
|
|
||||||
|
var title: Text {
|
||||||
|
Text("Toggle visibility of content from outside your trusted network", comment: "Title of tip that informs users what trusted network means and that they can toggle the visibility of content from outside their trusted network.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var message: Text? {
|
||||||
|
Text("Your trusted network is comprised of profiles you follow and profiles that they follow.", comment: "Description of the tip that informs users what trusted network means.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var image: Image? {
|
||||||
|
Image(systemName: "network.badge.shield.half.filled")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
//
|
||||||
|
// TrustedNetworkButtonTipViewStyle.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 6/7/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import TipKit
|
||||||
|
|
||||||
|
// (tyiu): Apple's native popover tips have a lot of rendering and race condition issues --
|
||||||
|
// text being rendered in the wrong locations or not at all, or the tip gets opened in full screen.
|
||||||
|
//
|
||||||
|
// Instead, we are introducing this custom popover tip view style to emulate a similar look and feel.
|
||||||
|
// The main thing needed from this view style is really just an arrow on the top right corner
|
||||||
|
// to point to the TrustedNetworkButton on the NotificationsView and DirectMessagesview.
|
||||||
|
@available(iOS 17, *)
|
||||||
|
struct TrustedNetworkButtonTipViewStyle: TipViewStyle {
|
||||||
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
// Arrow pointing up to the button (positioned at top right)
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Triangle()
|
||||||
|
.fill(Color(.secondarySystemBackground))
|
||||||
|
.frame(width: 24, height: 14)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(alignment: .top, spacing: 12) {
|
||||||
|
// Icon
|
||||||
|
configuration.image
|
||||||
|
.foregroundStyle(.tint)
|
||||||
|
.font(.title2)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
configuration.title
|
||||||
|
.font(.headline)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundStyle(.primary)
|
||||||
|
|
||||||
|
configuration.message
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: { configuration.tip.invalidate(reason: .tipClosed) }) {
|
||||||
|
Image(systemName: "xmark")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundStyle(Color(.tertiaryLabel))
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 14)
|
||||||
|
.padding(.vertical, 14)
|
||||||
|
.background(Color(.secondarySystemBackground))
|
||||||
|
.clipShape(
|
||||||
|
.rect(
|
||||||
|
topLeadingRadius: 20,
|
||||||
|
bottomLeadingRadius: 20,
|
||||||
|
bottomTrailingRadius: 20,
|
||||||
|
topTrailingRadius: 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom triangle shape for the popover arrow
|
||||||
|
struct Triangle: Shape {
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
var path = Path()
|
||||||
|
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
|
||||||
|
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
|
||||||
|
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
|
||||||
|
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// TrustedNetworkRepliesTip.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 6/7/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import TipKit
|
||||||
|
|
||||||
|
@available(iOS 17, *)
|
||||||
|
struct TrustedNetworkRepliesTip: Tip {
|
||||||
|
static let shared = TrustedNetworkRepliesTip()
|
||||||
|
|
||||||
|
var title: Text {
|
||||||
|
Text("Toggle visibility of replies from outside your trusted network", comment: "Title of tip that informs users what trusted network means and that they can toggle the visibility of threaded replies from outside their trusted network.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var message: Text? {
|
||||||
|
Text("Your trusted network is comprised of profiles you follow and profiles that they follow.", comment: "Description of the tip that informs users what trusted network means.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var image: Image? {
|
||||||
|
Image(systemName: "network.badge.shield.half.filled")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,10 +25,14 @@ import SwiftUI
|
|||||||
|
|
||||||
/// The URL of the video
|
/// The URL of the video
|
||||||
let url: URL
|
let url: URL
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Internal state
|
||||||
|
|
||||||
/// The underlying AVPlayer that we are wrapping.
|
/// The underlying AVPlayer that we are wrapping.
|
||||||
/// This is not public because we don't want any callers of this class controlling the `AVPlayer` directly, we want them to go through our interface
|
/// This is not public because we don't want any callers of this class controlling the `AVPlayer` directly, we want them to go through our interface
|
||||||
/// This measure helps avoid state inconsistencies and other flakiness. DO NOT USE THIS OUTSIDE `DamusVideoPlayer`
|
/// This measure helps avoid state inconsistencies and other flakiness. DO NOT USE THIS OUTSIDE `DamusVideoPlayer`
|
||||||
private let player: AVPlayer
|
private var player: AVPlayer
|
||||||
|
|
||||||
|
|
||||||
// MARK: SwiftUI-friendly interface
|
// MARK: SwiftUI-friendly interface
|
||||||
@@ -100,16 +104,39 @@ import SwiftUI
|
|||||||
private var videoIsPlayingObserver: NSKeyValueObservation?
|
private var videoIsPlayingObserver: NSKeyValueObservation?
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization, deinitialization and reinitialization
|
||||||
|
|
||||||
public init(url: URL) {
|
public init(url: URL) {
|
||||||
self.url = url
|
self.url = url
|
||||||
self.player = AVPlayer(playerItem: AVPlayerItem(url: url))
|
self.player = AVPlayer(playerItem: AVPlayerItem(url: url))
|
||||||
self.video_size = nil
|
self.video_size = nil
|
||||||
|
|
||||||
|
Task { await self.load() }
|
||||||
|
}
|
||||||
|
|
||||||
|
func reinitializePlayer() {
|
||||||
|
Log.info("DamusVideoPlayer: Reinitializing internal player…", for: .video_coordination)
|
||||||
|
|
||||||
|
// Tear down
|
||||||
|
videoSizeObserver?.invalidate()
|
||||||
|
videoDurationObserver?.invalidate()
|
||||||
|
videoIsPlayingObserver?.invalidate()
|
||||||
|
|
||||||
|
// Reset player
|
||||||
|
self.player = AVPlayer(playerItem: AVPlayerItem(url: url))
|
||||||
|
|
||||||
|
// Load once again
|
||||||
Task {
|
Task {
|
||||||
await load()
|
await load()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internally loads this class
|
||||||
|
private func load() async {
|
||||||
|
Task {
|
||||||
|
has_audio = await self.video_has_audio()
|
||||||
|
is_loading = false
|
||||||
|
}
|
||||||
|
|
||||||
player.isMuted = is_muted
|
player.isMuted = is_muted
|
||||||
|
|
||||||
@@ -126,6 +153,13 @@ import SwiftUI
|
|||||||
observeVideoIsPlaying()
|
observeVideoIsPlaying()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
// These cannot be moved into their own functions due to contraints on structured concurrency
|
||||||
|
videoSizeObserver?.invalidate()
|
||||||
|
videoDurationObserver?.invalidate()
|
||||||
|
videoIsPlayingObserver?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Observers
|
// MARK: - Observers
|
||||||
// Functions that allow us to observe certain variables and publish their changes for view updates
|
// Functions that allow us to observe certain variables and publish their changes for view updates
|
||||||
// These are all private because they are part of the internal logic
|
// These are all private because they are part of the internal logic
|
||||||
@@ -175,11 +209,6 @@ import SwiftUI
|
|||||||
|
|
||||||
// MARK: - Other internal logic functions
|
// MARK: - Other internal logic functions
|
||||||
|
|
||||||
private func load() async {
|
|
||||||
has_audio = await self.video_has_audio()
|
|
||||||
is_loading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private func video_has_audio() async -> Bool {
|
private func video_has_audio() async -> Bool {
|
||||||
do {
|
do {
|
||||||
let hasAudibleTracks = ((try await player.currentItem?.asset.loadMediaSelectionGroup(for: .audible)) != nil)
|
let hasAudibleTracks = ((try await player.currentItem?.asset.loadMediaSelectionGroup(for: .audible)) != nil)
|
||||||
@@ -196,17 +225,16 @@ import SwiftUI
|
|||||||
player.play()
|
player.play()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Deinit
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
videoSizeObserver?.invalidate()
|
|
||||||
videoDurationObserver?.invalidate()
|
|
||||||
videoIsPlayingObserver?.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Convenience interface functions
|
// MARK: - Convenience interface functions
|
||||||
|
|
||||||
func play() {
|
func play() {
|
||||||
|
switch self.player.status {
|
||||||
|
case .failed:
|
||||||
|
Log.error("DamusVideoPlayer: Failed to play video. Error: '%s'", for: .video_coordination, self.player.error?.localizedDescription ?? "no error")
|
||||||
|
self.reinitializePlayer()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
self.is_playing = true
|
self.is_playing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,10 +264,10 @@ extension DamusVideoPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
|
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
|
||||||
if uiViewController.player == nil {
|
/// - If `player.player` is changed (e.g. `DamusVideoPlayer` gets reinitialized), this will refresh the video player to the new working one.
|
||||||
|
/// - If `player.player` is unchanged, this is basically a very low cost no-op (Because `AVPlayer` is a class type, this assignment only copies a pointer, not a large structure)
|
||||||
uiViewController.player = player.player
|
uiViewController.player = player.player
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static func dismantleUIViewController(_ uiViewController: AVPlayerViewController, coordinator: ()) {
|
static func dismantleUIViewController(_ uiViewController: AVPlayerViewController, coordinator: ()) {
|
||||||
uiViewController.player = nil
|
uiViewController.player = nil
|
||||||
|
|||||||
@@ -10,24 +10,46 @@ import SwiftUI
|
|||||||
struct BalanceView: View {
|
struct BalanceView: View {
|
||||||
var balance: Int64?
|
var balance: Int64?
|
||||||
|
|
||||||
|
@Binding var hide_balance: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 5) {
|
VStack(spacing: 5) {
|
||||||
Text("Current balance", comment: "Label for displaying current wallet balance")
|
Text("Current balance", comment: "Label for displaying current wallet balance")
|
||||||
.foregroundStyle(DamusColors.neutral6)
|
.foregroundStyle(DamusColors.neutral6)
|
||||||
if let balance {
|
if let balance {
|
||||||
self.numericalBalanceView(text: NumberFormatter.localizedString(from: NSNumber(integerLiteral: Int(balance)), number: .decimal))
|
NumericalBalanceView(text: NumberFormatter.localizedString(from: NSNumber(integerLiteral: Int(balance)), number: .decimal), hide_balance: $hide_balance)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Make sure we do not show any numeric value to the user when still loading (or when failed to load)
|
// Make sure we do not show any numeric value to the user when still loading (or when failed to load)
|
||||||
// This is important because if we show a numeric value like "zero" when things are not loaded properly, we risk scaring the user into thinking that they have lost funds.
|
// This is important because if we show a numeric value like "zero" when things are not loaded properly, we risk scaring the user into thinking that they have lost funds.
|
||||||
self.numericalBalanceView(text: "??")
|
Text(verbatim: "??")
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.70)
|
||||||
|
.font(.veryVeryLargeTitle)
|
||||||
|
.fontWeight(.heavy)
|
||||||
|
.foregroundStyle(PinkGradient)
|
||||||
.redacted(reason: .placeholder)
|
.redacted(reason: .placeholder)
|
||||||
.shimmer(true)
|
.shimmer(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func numericalBalanceView(text: String) -> some View {
|
struct NumericalBalanceView: View {
|
||||||
|
let text: String
|
||||||
|
@Binding var hide_balance: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if hide_balance {
|
||||||
|
Text(verbatim: "*****")
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.70)
|
||||||
|
.font(.veryVeryLargeTitle)
|
||||||
|
.fontWeight(.heavy)
|
||||||
|
.foregroundStyle(PinkGradient)
|
||||||
|
|
||||||
|
} else {
|
||||||
HStack {
|
HStack {
|
||||||
Text(verbatim: text)
|
Text(verbatim: text)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
@@ -43,14 +65,21 @@ struct BalanceView: View {
|
|||||||
.foregroundStyle(PinkGradient)
|
.foregroundStyle(PinkGradient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.privacySensitive()
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
|
.onTapGesture {
|
||||||
|
hide_balance.toggle()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BalanceView_Previews: PreviewProvider {
|
struct BalanceView_Previews: PreviewProvider {
|
||||||
|
@State private static var hide_balance: Bool = false
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
BalanceView(balance: 100000000)
|
BalanceView(balance: 100000000, hide_balance: $hide_balance)
|
||||||
BalanceView(balance: nil)
|
BalanceView(balance: nil, hide_balance: $hide_balance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ struct ConnectWalletView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
NWCSettings.AccountDetailsView(nwc: nwc)
|
NWCSettings.AccountDetailsView(nwc: nwc, damus_state: nil)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ struct ConnectWalletView: View {
|
|||||||
CoinosButton() {
|
CoinosButton() {
|
||||||
self.show_coinos_options = true
|
self.show_coinos_options = true
|
||||||
}
|
}
|
||||||
Text("Coinos is a service operated by a third-party. We have no access to your Coinos wallet.", comment: "Small caption with a disclaimer that Damus does not own or have access to Coinos wallets, Coinos is a third-party service.")
|
Text("Coinos is a service operated by a third-party. The Damus team has no access to your wallet.", comment: "Small caption with a disclaimer that Damus does not own or have access to Coinos wallets, Coinos is a third-party service.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
//
|
||||||
|
// LnurlAmountView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2025-06-18
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
class LnurlAmountModel: ObservableObject {
|
||||||
|
@Published var custom_amount: String = "0"
|
||||||
|
@Published var custom_amount_sats: Int? = 0
|
||||||
|
@Published var processing: Bool = false
|
||||||
|
@Published var error: String? = nil
|
||||||
|
@Published var invoice: String? = nil
|
||||||
|
@Published var zap_amounts: [ZapAmountItem] = []
|
||||||
|
|
||||||
|
func set_defaults(settings: UserSettingsStore) {
|
||||||
|
let default_amount = settings.default_zap_amount
|
||||||
|
custom_amount = String(default_amount)
|
||||||
|
custom_amount_sats = default_amount
|
||||||
|
zap_amounts = get_zap_amount_items(default_amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables the user to enter a Bitcoin amount to be sent. Based on `CustomizeZapView`.
|
||||||
|
struct LnurlAmountView: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
let lnurlString: String
|
||||||
|
let onInvoiceFetched: (Invoice) -> Void
|
||||||
|
let onCancel: () -> Void
|
||||||
|
|
||||||
|
@StateObject var model: LnurlAmountModel = LnurlAmountModel()
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
@FocusState var isAmountFocused: Bool
|
||||||
|
|
||||||
|
init(damus_state: DamusState, lnurlString: String, onInvoiceFetched: @escaping (Invoice) -> Void, onCancel: @escaping () -> Void) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
self.lnurlString = lnurlString
|
||||||
|
self.onInvoiceFetched = onInvoiceFetched
|
||||||
|
self.onCancel = onCancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func AmountButton(zapAmountItem: ZapAmountItem) -> some View {
|
||||||
|
let isSelected = model.custom_amount_sats == zapAmountItem.amount
|
||||||
|
|
||||||
|
return Button(action: {
|
||||||
|
model.custom_amount_sats = zapAmountItem.amount
|
||||||
|
model.custom_amount = String(zapAmountItem.amount)
|
||||||
|
}) {
|
||||||
|
let fmt = format_msats_abbrev(Int64(zapAmountItem.amount) * 1000)
|
||||||
|
Text(verbatim: "\(zapAmountItem.icon)\n\(fmt)")
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.font(.headline)
|
||||||
|
.frame(width: 70, height: 70)
|
||||||
|
.foregroundColor(DamusColors.adaptableBlack)
|
||||||
|
.background(isSelected ? DamusColors.adaptableWhite : DamusColors.adaptableGrey)
|
||||||
|
.cornerRadius(15)
|
||||||
|
.overlay(RoundedRectangle(cornerRadius: 15)
|
||||||
|
.stroke(DamusColors.purple.opacity(isSelected ? 1.0 : 0.0), lineWidth: 2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func amount_parts(_ n: Int) -> [ZapAmountItem] {
|
||||||
|
var i: Int = -1
|
||||||
|
let start = n * 4
|
||||||
|
let end = start + 4
|
||||||
|
|
||||||
|
return model.zap_amounts.filter { _ in
|
||||||
|
i += 1
|
||||||
|
return i >= start && i < end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AmountsPart(n: Int) -> some View {
|
||||||
|
HStack(alignment: .center, spacing: 15) {
|
||||||
|
ForEach(amount_parts(n)) { entry in
|
||||||
|
AmountButton(zapAmountItem: entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var AmountGrid: some View {
|
||||||
|
VStack {
|
||||||
|
AmountsPart(n: 0)
|
||||||
|
|
||||||
|
AmountsPart(n: 1)
|
||||||
|
}
|
||||||
|
.padding(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
var CustomAmountTextField: some View {
|
||||||
|
VStack(alignment: .center, spacing: 0) {
|
||||||
|
TextField("", text: $model.custom_amount)
|
||||||
|
.focused($isAmountFocused)
|
||||||
|
.task {
|
||||||
|
self.isAmountFocused = true
|
||||||
|
}
|
||||||
|
.font(.system(size: 72, weight: .heavy))
|
||||||
|
.minimumScaleFactor(0.01)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.onChange(of: model.custom_amount) { newValue in
|
||||||
|
if let parsed = handle_string_amount(new_value: newValue) {
|
||||||
|
model.custom_amount = parsed.formatted()
|
||||||
|
model.custom_amount_sats = parsed
|
||||||
|
} else {
|
||||||
|
model.custom_amount = "0"
|
||||||
|
model.custom_amount_sats = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let noun = pluralizedString(key: "sats", count: model.custom_amount_sats ?? 0)
|
||||||
|
Text(noun)
|
||||||
|
.font(.system(size: 18, weight: .heavy))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchInvoice() {
|
||||||
|
guard let amount = model.custom_amount_sats, amount > 0 else {
|
||||||
|
model.error = NSLocalizedString("Please enter a valid amount", comment: "Error message when no valid amount is entered for LNURL payment")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
model.processing = true
|
||||||
|
model.error = nil
|
||||||
|
|
||||||
|
Task { @MainActor in
|
||||||
|
// For LNURL payments without zaps, we use nil for zapreq and comment
|
||||||
|
// We just need the invoice for payment
|
||||||
|
let msats = Int64(amount) * 1000
|
||||||
|
|
||||||
|
// First get the payment request from the LNURL
|
||||||
|
guard let payreq = await fetch_static_payreq(lnurlString) else {
|
||||||
|
model.processing = false
|
||||||
|
model.error = NSLocalizedString("Error fetching LNURL payment information", comment: "Error message when LNURL fetch fails")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then fetch the invoice with the amount
|
||||||
|
guard let invoiceStr = await fetch_zap_invoice(payreq, zapreq: nil, msats: msats, zap_type: .non_zap, comment: nil) else {
|
||||||
|
model.processing = false
|
||||||
|
model.error = NSLocalizedString("Error fetching lightning invoice", comment: "Error message when there was an error fetching a lightning invoice")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the invoice to validate it
|
||||||
|
guard let invoice = decode_bolt11(invoiceStr) else {
|
||||||
|
model.processing = false
|
||||||
|
model.error = NSLocalizedString("Invalid lightning invoice received", comment: "Error message when the lightning invoice received from LNURL is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// All good, pass the invoice back to the parent view
|
||||||
|
model.processing = false
|
||||||
|
onInvoiceFetched(invoice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var PayButton: some View {
|
||||||
|
VStack {
|
||||||
|
if model.processing {
|
||||||
|
Text("Processing...", comment: "Text to indicate that the app is in the process of fetching an invoice.")
|
||||||
|
.padding()
|
||||||
|
ProgressView()
|
||||||
|
} else {
|
||||||
|
Button(action: {
|
||||||
|
fetchInvoice()
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Text("Continue", comment: "Button to proceed with LNURL payment process.")
|
||||||
|
.font(.system(size: 20, weight: .bold))
|
||||||
|
}
|
||||||
|
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
||||||
|
}
|
||||||
|
.buttonStyle(GradientButtonStyle())
|
||||||
|
.disabled(model.custom_amount_sats == 0 || model.custom_amount == "0")
|
||||||
|
.opacity(model.custom_amount_sats == 0 || model.custom_amount == "0" ? 0.5 : 1.0)
|
||||||
|
.padding(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let error = model.error {
|
||||||
|
Text(error)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var CancelButton: some View {
|
||||||
|
Button(action: onCancel) {
|
||||||
|
HStack {
|
||||||
|
Text("Cancel", comment: "Button to cancel the LNURL payment process.")
|
||||||
|
.font(.headline)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
||||||
|
}
|
||||||
|
.buttonStyle(NeutralButtonStyle())
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .center, spacing: 20) {
|
||||||
|
ScrollView {
|
||||||
|
VStack {
|
||||||
|
Text("Enter Amount", comment: "Header text for LNURL payment amount entry screen")
|
||||||
|
.font(.title)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Text("How much would you like to send?", comment: "Instruction text for LNURL payment amount")
|
||||||
|
.font(.headline)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.padding(.bottom)
|
||||||
|
|
||||||
|
CustomAmountTextField
|
||||||
|
|
||||||
|
AmountGrid
|
||||||
|
|
||||||
|
PayButton
|
||||||
|
|
||||||
|
CancelButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
model.set_defaults(settings: damus_state.settings)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
hideKeyboard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LnurlAmountView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
LnurlAmountView(
|
||||||
|
damus_state: test_damus_state,
|
||||||
|
lnurlString: "lnurl1dp68gurn8ghj7um9wfmxjcm99e3k7mf0v9cxj0m385ekvcenxc6r2c35xvukxefcv5mkvv34x5ekzd3ev56nyd3hxqurzepexejxxepnxscrvwfnv9nxzcn9xq6xyefhvgcxxcmyxymnserxfq5fns",
|
||||||
|
onInvoiceFetched: { _ in },
|
||||||
|
onCancel: {}
|
||||||
|
)
|
||||||
|
.frame(width: 400, height: 600)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -134,7 +134,13 @@ struct NWCSettings: View {
|
|||||||
SupportDamus
|
SupportDamus
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
|
|
||||||
AccountDetailsView(nwc: nwc)
|
AccountDetailsView(nwc: nwc, damus_state: damus_state)
|
||||||
|
|
||||||
|
Toggle(NSLocalizedString("Disable high balance warning", comment: "Setting to disable high balance warnings on the user's wallet"), isOn: $settings.dismiss_wallet_high_balance_warning)
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
|
||||||
|
Toggle(NSLocalizedString("Hide balance", comment: "Setting to hide wallet balance."), isOn: $settings.hide_wallet_balance)
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
self.model.disconnect()
|
self.model.disconnect()
|
||||||
@@ -182,6 +188,7 @@ struct NWCSettings: View {
|
|||||||
|
|
||||||
struct AccountDetailsView: View {
|
struct AccountDetailsView: View {
|
||||||
let nwc: WalletConnect.ConnectURL
|
let nwc: WalletConnect.ConnectURL
|
||||||
|
let damus_state: DamusState?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
@@ -195,11 +202,17 @@ struct NWCSettings: View {
|
|||||||
Text("Routing", comment: "Label indicating the routing address for Nostr Wallet Connect payments. In other words, the relay used by the NWC wallet provider")
|
Text("Routing", comment: "Label indicating the routing address for Nostr Wallet Connect payments. In other words, the relay used by the NWC wallet provider")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
|
|
||||||
|
if let damus_state {
|
||||||
|
RelayView(state: damus_state, relay: nwc.relay, showActionButtons: .constant(false), recommended: false, disableNavLink: true)
|
||||||
|
.padding(.bottom)
|
||||||
|
}
|
||||||
|
else {
|
||||||
Text(nwc.relay.absoluteString)
|
Text(nwc.relay.absoluteString)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
|
}
|
||||||
|
|
||||||
if let lud16 = nwc.lud16 {
|
if let lud16 = nwc.lud16 {
|
||||||
Text("Account", comment: "Label for the user account information with the Nostr Wallet Connect wallet provider.")
|
Text("Account", comment: "Label for the user account information with the Nostr Wallet Connect wallet provider.")
|
||||||
|
|||||||
@@ -0,0 +1,375 @@
|
|||||||
|
//
|
||||||
|
// SendPaymentView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2025-06-13.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import CodeScanner
|
||||||
|
|
||||||
|
fileprivate let SEND_PAYMENT_TIMEOUT: Duration = .seconds(10)
|
||||||
|
|
||||||
|
/// A view that allows a user to pay a lightning invoice
|
||||||
|
struct SendPaymentView: View {
|
||||||
|
|
||||||
|
// MARK: - Helper structures
|
||||||
|
|
||||||
|
/// Represents the state of the invoice payment process
|
||||||
|
enum SendState {
|
||||||
|
case enterInvoice(scannerMessage: String?)
|
||||||
|
case confirmPayment(invoice: Invoice)
|
||||||
|
case enterLnurlAmount(lnurl: String)
|
||||||
|
case processing
|
||||||
|
case completed
|
||||||
|
case failed(error: HumanReadableError)
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias HumanReadableError = ErrorView.UserPresentableError
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Immutable members
|
||||||
|
|
||||||
|
let damus_state: DamusState
|
||||||
|
let model: WalletModel
|
||||||
|
let nwc: WalletConnectURL
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - State management
|
||||||
|
|
||||||
|
@State private var sendState: SendState = .enterInvoice(scannerMessage: nil) {
|
||||||
|
didSet {
|
||||||
|
switch sendState {
|
||||||
|
case .enterInvoice, .confirmPayment, .processing, .enterLnurlAmount:
|
||||||
|
break
|
||||||
|
case .completed:
|
||||||
|
// Refresh wallet to reflect new balance after payment
|
||||||
|
Task { await WalletConnect.refresh_wallet_information(damus_state: damus_state) }
|
||||||
|
case .failed:
|
||||||
|
// Even when a wallet says it has failed, update balance just in case it is a false negative,
|
||||||
|
// This might prevent the user from accidentally sending a payment twice in case of a bug.
|
||||||
|
Task { await WalletConnect.refresh_wallet_information(damus_state: damus_state) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var isShowingScanner: Bool {
|
||||||
|
if case .enterInvoice = sendState { true } else { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Views
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .center) {
|
||||||
|
switch sendState {
|
||||||
|
case .enterInvoice(let scannerMessage):
|
||||||
|
invoiceInputView(scannerMessage: scannerMessage)
|
||||||
|
.padding(40)
|
||||||
|
case .confirmPayment(let invoice):
|
||||||
|
confirmationView(invoice: invoice)
|
||||||
|
.padding(40)
|
||||||
|
case .enterLnurlAmount(let lnurl):
|
||||||
|
LnurlAmountView(
|
||||||
|
damus_state: damus_state,
|
||||||
|
lnurlString: lnurl,
|
||||||
|
onInvoiceFetched: { invoice in
|
||||||
|
sendState = .confirmPayment(invoice: invoice)
|
||||||
|
},
|
||||||
|
onCancel: {
|
||||||
|
sendState = .enterInvoice(scannerMessage: nil)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
case .processing:
|
||||||
|
processingView
|
||||||
|
.padding(40)
|
||||||
|
case .completed:
|
||||||
|
completedView
|
||||||
|
.padding(40)
|
||||||
|
case .failed(error: let error):
|
||||||
|
failedView(error: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func invoiceInputView(scannerMessage: String?) -> some View {
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
Text("Scan Lightning Invoice", comment: "Title for the invoice scanning screen")
|
||||||
|
.font(.title2)
|
||||||
|
.bold()
|
||||||
|
|
||||||
|
CodeScannerView(
|
||||||
|
codeTypes: [.qr],
|
||||||
|
scanMode: .continuous,
|
||||||
|
showViewfinder: true, // The scan only seems to work if it fits the bounding box, so let's make this visible to hint that to the users
|
||||||
|
simulatedData: "lightning:lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r",
|
||||||
|
completion: handleScan
|
||||||
|
)
|
||||||
|
.frame(height: 300)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 20))
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 20)
|
||||||
|
.stroke(Color.accentColor, lineWidth: 2)
|
||||||
|
)
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
VStack(spacing: 15) {
|
||||||
|
Button(action: {
|
||||||
|
if let pastedInvoice = getPasteboardContent() {
|
||||||
|
processUserInput(pastedInvoice)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "doc.on.clipboard")
|
||||||
|
Text("Paste from Clipboard", comment: "Button to paste invoice from clipboard")
|
||||||
|
}
|
||||||
|
.frame(minWidth: 250, maxWidth: .infinity, alignment: .center)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.buttonStyle(NeutralButtonStyle())
|
||||||
|
.accessibilityLabel(NSLocalizedString("Paste invoice from clipboard", comment: "Accessibility label for the invoice paste button"))
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
if let scannerMessage {
|
||||||
|
Text(scannerMessage)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.top, 10)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func confirmationView(invoice: Invoice) -> some View {
|
||||||
|
let insufficientFunds: Bool = (invoice.amount.amount_sats() ?? 0) > (model.balance ?? 0)
|
||||||
|
return VStack(spacing: 20) {
|
||||||
|
Text("Confirm Payment", comment: "Title for payment confirmation screen")
|
||||||
|
.font(.title2)
|
||||||
|
.bold()
|
||||||
|
|
||||||
|
VStack(spacing: 15) {
|
||||||
|
Text("Amount", comment: "Label for invoice payment amount in confirmation screen")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
if case .specific(let amount) = invoice.amount {
|
||||||
|
NumericalBalanceView(text: NumberFormatter.localizedString(from: NSNumber(value: (Double(amount) / 1000.0)), number: .decimal), hide_balance: .constant(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("Bolt11 Invoice", comment: "Label for the bolt11 invoice string in confirmation screen")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
Text(verbatim: invoice.abbreviated)
|
||||||
|
.font(.system(.body, design: .monospaced))
|
||||||
|
.padding()
|
||||||
|
.background(DamusColors.adaptableGrey)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 15) {
|
||||||
|
Button(action: {
|
||||||
|
sendState = .enterInvoice(scannerMessage: nil)
|
||||||
|
}) {
|
||||||
|
Text("Back", comment: "Button to go back to invoice input")
|
||||||
|
.font(.headline)
|
||||||
|
.frame(minWidth: 140)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.buttonStyle(NeutralButtonStyle())
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
sendState = .processing
|
||||||
|
|
||||||
|
// Process payment
|
||||||
|
guard let payRequestEv = WalletConnect.pay(url: nwc, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, invoice: invoice.string, zap_request: nil, delay: nil) else {
|
||||||
|
sendState = .failed(error: .init(
|
||||||
|
user_visible_description: NSLocalizedString("The payment request could not be made to your wallet provider.", comment: "A human-readable error message"),
|
||||||
|
tip: NSLocalizedString("Check if your wallet looks configured correctly and try again. If the error persists, please contact support.", comment: "A human-readable tip for an error when a payment request cannot be made to a wallet."),
|
||||||
|
technical_info: "Cannot form Nostr Event to send to the NWC provider when calling `pay` from the \"send payment\" feature. Wallet provider relay: \"\(nwc.relay)\""
|
||||||
|
))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
let result = try await model.waitForResponse(for: payRequestEv.id, timeout: SEND_PAYMENT_TIMEOUT)
|
||||||
|
guard case .pay_invoice(_) = result else {
|
||||||
|
sendState = .failed(error: .init(
|
||||||
|
user_visible_description: NSLocalizedString("Received an incorrect or unexpected response from the wallet provider. This looks like an issue with your wallet provider.", comment: "A human-readable error message"),
|
||||||
|
tip: NSLocalizedString("Try again. If the error persists, please contact your wallet provider and/or our support team.", comment: "A human-readable tip for an error when a payment request cannot be made to a wallet."),
|
||||||
|
technical_info: "Expected a `pay_invoice` response for the request, but received a different type of response from the NWC wallet provider. Wallet provider relay: \"\(nwc.relay)\""
|
||||||
|
))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sendState = .completed
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
if let error = error as? WalletModel.WaitError {
|
||||||
|
switch error {
|
||||||
|
case .timeout:
|
||||||
|
sendState = .failed(error: .init(
|
||||||
|
user_visible_description: NSLocalizedString("The payment request did not receive a response and the request timed-out.", comment: "A human-readable error message"),
|
||||||
|
tip: NSLocalizedString("Check if the invoice is valid, your wallet is online, configured correctly, and try again. If the error persists, please contact support and/or your wallet provider.", comment: "A human-readable tip guiding the user on what to do when seeing a timeout error while sending a wallet payment."),
|
||||||
|
technical_info: "Payment request timed-out. Wallet provider relay: \"\(nwc.relay)\""
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if let error = error as? WalletConnect.WalletResponseErr,
|
||||||
|
let humanReadableError = error.humanReadableError {
|
||||||
|
sendState = .failed(error: humanReadableError)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendState = .failed(error: .init(
|
||||||
|
user_visible_description: NSLocalizedString("An unexpected error occurred.", comment: "A human-readable error message"),
|
||||||
|
tip: NSLocalizedString("Please try again. If the error persists, please contact support.", comment: "A human-readable tip guiding the user on what to do when seeing an unexpected error while sending a wallet payment."),
|
||||||
|
technical_info: "Unexpected error thrown while waiting for payment request response. Wallet provider relay: \"\(nwc.relay)\"; Error: \(error)"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Confirm", comment: "Button to confirm payment")
|
||||||
|
.font(.headline)
|
||||||
|
.frame(minWidth: 140)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.buttonStyle(GradientButtonStyle(padding: 0))
|
||||||
|
.disabled(insufficientFunds)
|
||||||
|
.opacity(insufficientFunds ? 0.5 : 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if insufficientFunds {
|
||||||
|
Text("You do not have enough funds to pay for this invoice.", comment: "Label on invoice payment screen, indicating user has insufficient funds")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.top, 10)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var processingView: some View {
|
||||||
|
VStack(spacing: 30) {
|
||||||
|
Text("Processing Payment", comment: "Title for payment processing screen")
|
||||||
|
.font(.title2)
|
||||||
|
.bold()
|
||||||
|
|
||||||
|
ProgressView()
|
||||||
|
.scaleEffect(1.5)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Text("Please wait while your payment is being processed…", comment: "Message while payment is being processed")
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var completedView: some View {
|
||||||
|
VStack(spacing: 30) {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 80, height: 80)
|
||||||
|
.foregroundColor(.green)
|
||||||
|
|
||||||
|
Text("Payment Sent!", comment: "Title for successful payment screen")
|
||||||
|
.font(.title2)
|
||||||
|
.bold()
|
||||||
|
|
||||||
|
Text("Your payment has been successfully sent.", comment: "Message for successful payment")
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
dismiss()
|
||||||
|
}) {
|
||||||
|
Text("Done", comment: "Button to dismiss successful payment screen")
|
||||||
|
.font(.headline)
|
||||||
|
.frame(minWidth: 200)
|
||||||
|
}
|
||||||
|
.buttonStyle(GradientButtonStyle())
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func failedView(error: HumanReadableError) -> some View {
|
||||||
|
ScrollView {
|
||||||
|
VStack {
|
||||||
|
ErrorView(damus_state: damus_state, error: error)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
sendState = .enterInvoice(scannerMessage: nil)
|
||||||
|
}) {
|
||||||
|
Text("Try Again", comment: "Button to retry payment")
|
||||||
|
.font(.headline)
|
||||||
|
.frame(minWidth: 200)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.buttonStyle(GradientButtonStyle(padding: 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleScan(result: Result<ScanResult, ScanError>) {
|
||||||
|
switch result {
|
||||||
|
case .success(let result):
|
||||||
|
processUserInput(result.string)
|
||||||
|
case .failure(let error):
|
||||||
|
sendState = .enterInvoice(scannerMessage: NSLocalizedString("Failed to scan QR code, please try again.", comment: "Error message for failed QR scan"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func processUserInput(_ text: String) {
|
||||||
|
if let result = parseScanData(text) {
|
||||||
|
switch result {
|
||||||
|
case .invoice(let invoice):
|
||||||
|
if invoice.amount == .any {
|
||||||
|
sendState = .enterInvoice(scannerMessage: NSLocalizedString("Sorry, we do not support paying invoices without amount yet. Please scan an invoice with an amount.", comment: "A user-readable message indicating that the lightning invoice they scanned or pasted is not supported and is missing an amount."))
|
||||||
|
} else {
|
||||||
|
sendState = .confirmPayment(invoice: invoice)
|
||||||
|
}
|
||||||
|
case .lnurl(let lnurlString):
|
||||||
|
sendState = .enterLnurlAmount(lnurl: lnurlString)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendState = .enterInvoice(scannerMessage: NSLocalizedString("This does not appear to be a valid Lightning invoice or LNURL.", comment: "A user-readable message indicating that the scanned or pasted content was not a valid lightning invoice or LNURL."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseScanData(_ text: String) -> ScanData? {
|
||||||
|
let processedString = text.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||||
|
|
||||||
|
if let invoice = Invoice.from(string: processedString) {
|
||||||
|
return .invoice(invoice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = processedString.range(of: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", options: .regularExpression) {
|
||||||
|
guard let lnurl = lnaddress_to_lnurl(processedString) else { return nil }
|
||||||
|
return .lnurl(lnurl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if processedString.hasPrefix("lnurl") {
|
||||||
|
return .lnurl(processedString)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ScanData {
|
||||||
|
case invoice(Invoice)
|
||||||
|
case lnurl(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get pasteboard content
|
||||||
|
func getPasteboardContent() -> String? {
|
||||||
|
return UIPasteboard.general.string
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,26 +8,36 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TransactionView: View {
|
struct TransactionView: View {
|
||||||
|
@Environment(\.redactionReasons) var redactionReasons
|
||||||
|
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
var transaction: WalletConnect.Transaction
|
var transaction: WalletConnect.Transaction
|
||||||
|
|
||||||
|
@Binding var hide_balance: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let redactedForPrivacy = redactionReasons.contains(.privacy)
|
||||||
let isIncomingTransaction = transaction.type == "incoming"
|
let isIncomingTransaction = transaction.type == "incoming"
|
||||||
let txType = isIncomingTransaction ? "arrow-bottom-left" : "arrow-top-right"
|
let txType = isIncomingTransaction ? "arrow-bottom-left" : "arrow-top-right"
|
||||||
let txColor = isIncomingTransaction ? DamusColors.success : Color.gray
|
let txColor = (isIncomingTransaction && !hide_balance && !redactedForPrivacy) ? DamusColors.success : Color.gray
|
||||||
let txOp = isIncomingTransaction ? "+" : "-"
|
let txOp = isIncomingTransaction ? "+" : "-"
|
||||||
let created_at = Date.init(timeIntervalSince1970: TimeInterval(transaction.created_at))
|
let created_at = Date.init(timeIntervalSince1970: TimeInterval(transaction.created_at))
|
||||||
let formatter = RelativeDateTimeFormatter()
|
let formatter = RelativeDateTimeFormatter()
|
||||||
let relativeDate = formatter.localizedString(for: created_at, relativeTo: Date.now)
|
let relativeDate = formatter.localizedString(for: created_at, relativeTo: Date.now)
|
||||||
let event = decode_nostr_event_json(transaction.description ?? "")
|
let event = decode_nostr_event_json(transaction.description ?? "") ?? transaction.metadata?.nostr
|
||||||
let pubkey = (event?.pubkey ?? ANON_PUBKEY)
|
let pubkey = self.pubkeyToDisplay(for: event, isIncomingTransaction: isIncomingTransaction)
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ZStack {
|
ZStack {
|
||||||
ProfilePicView(pubkey: pubkey, size: 45, highlight: .custom(.damusAdaptableBlack, 0.1), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey ?? ANON_PUBKEY, size: 45, highlight: .custom(.damusAdaptableBlack, 0.1), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, privacy_sensitive: true)
|
||||||
|
.onTapGesture {
|
||||||
|
if let pubkey {
|
||||||
|
damus_state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hide_balance && !redactedForPrivacy {
|
||||||
Image(txType)
|
Image(txType)
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 18, height: 18)
|
.frame(width: 18, height: 18)
|
||||||
@@ -39,6 +49,7 @@ struct TransactionView: View {
|
|||||||
.padding(.top, 25)
|
.padding(.top, 25)
|
||||||
.padding(.leading, 35)
|
.padding(.leading, 35)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
|
||||||
@@ -55,11 +66,18 @@ struct TransactionView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
if hide_balance {
|
||||||
|
Text(verbatim: "*****")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(txColor)
|
||||||
|
.bold()
|
||||||
|
} else {
|
||||||
Text(verbatim: "\(txOp) \(format_msats(transaction.amount))")
|
Text(verbatim: "\(txOp) \(format_msats(transaction.amount))")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(txColor)
|
.foregroundColor(txColor)
|
||||||
.bold()
|
.bold()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.frame(maxWidth: .infinity, minHeight: 75, alignment: .center)
|
.frame(maxWidth: .infinity, minHeight: 75, alignment: .center)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.background(DamusColors.neutral1)
|
.background(DamusColors.neutral1)
|
||||||
@@ -71,17 +89,25 @@ struct TransactionView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func userDisplayName(pubkey: Pubkey) -> String {
|
func pubkeyToDisplay(for zapRequest: NostrEvent?, isIncomingTransaction: Bool) -> Pubkey? {
|
||||||
|
guard let zapRequest else { return nil }
|
||||||
|
if isIncomingTransaction {
|
||||||
|
return zapRequest.pubkey // We want to know who sent it to us
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return zapRequest.referenced_pubkeys.first // We want to know who we sent it to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userDisplayName(pubkey: Pubkey?) -> String {
|
||||||
|
guard let pubkey else {
|
||||||
|
return NSLocalizedString("Unknown", comment: "A name label for an unknown user")
|
||||||
|
}
|
||||||
|
|
||||||
let profile_txn = damus_state.profiles.lookup(id: pubkey, txn_name: "txview-profile")
|
let profile_txn = damus_state.profiles.lookup(id: pubkey, txn_name: "txview-profile")
|
||||||
let profile = profile_txn?.unsafeUnownedValue
|
let profile = profile_txn?.unsafeUnownedValue
|
||||||
|
|
||||||
if let display_name = profile?.display_name {
|
return Profile.displayName(profile: profile, pubkey: pubkey).displayName
|
||||||
return display_name
|
|
||||||
} else if let name = profile?.name {
|
|
||||||
return "@" + name
|
|
||||||
} else {
|
|
||||||
return NSLocalizedString("Unknown", comment: "A name label for an unknown user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -94,17 +120,20 @@ struct TransactionsView: View {
|
|||||||
transactions?.sorted(by: { $0.created_at > $1.created_at })
|
transactions?.sorted(by: { $0.created_at > $1.created_at })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Binding var hide_balance: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
Text("Latest transactions", comment: "Heading for latest wallet transactions list")
|
Text("Latest transactions", comment: "Heading for latest wallet transactions list")
|
||||||
.foregroundStyle(DamusColors.neutral6)
|
.foregroundStyle(DamusColors.neutral6)
|
||||||
|
|
||||||
|
Group {
|
||||||
if let sortedTransactions {
|
if let sortedTransactions {
|
||||||
if sortedTransactions.isEmpty {
|
if sortedTransactions.isEmpty {
|
||||||
emptyTransactions
|
emptyTransactions
|
||||||
} else {
|
} else {
|
||||||
ForEach(sortedTransactions, id: \.self) { transaction in
|
ForEach(sortedTransactions, id: \.self) { transaction in
|
||||||
TransactionView(damus_state: damus_state, transaction: transaction)
|
TransactionView(damus_state: damus_state, transaction: transaction, hide_balance: $hide_balance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,6 +145,8 @@ struct TransactionsView: View {
|
|||||||
.shimmer(true)
|
.shimmer(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.privacySensitive()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var emptyTransactions: some View {
|
var emptyTransactions: some View {
|
||||||
@@ -136,13 +167,15 @@ struct TransactionsView: View {
|
|||||||
|
|
||||||
struct TransactionsView_Previews: PreviewProvider {
|
struct TransactionsView_Previews: PreviewProvider {
|
||||||
static let tds = test_damus_state
|
static let tds = test_damus_state
|
||||||
static let transaction1: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "{\"id\":\"7c0999a5870ca3ba0186a29a8650152b555cee29b53b5b8747d8a3798042d01c\",\"pubkey\":\"b8851a06dfd79d48fc325234a15e9a46a32a0982a823b54cdf82514b9b120ba1\",\"created_at\":1736383715,\"kind\":9734,\"tags\":[[\"p\",\"520830c334a3f79f88cac934580d26f91a7832c6b21fb9625690ea2ed81b5626\"],[\"amount\",\"21000\"],[\"e\",\"a25e152a4cd1b3bbc3d22e8e9315d8ea1f35c227b2f212c7cff18abff36fa208\"],[\"relays\",\"wss://nos.lol\",\"wss://nostr.wine\",\"wss://premium.primal.net\",\"wss://relay.damus.io\",\"wss://relay.nostr.band\",\"wss://relay.nostrarabia.com\"]],\"content\":\"🫡 Onward!\",\"sig\":\"e77d16822fa21b9c2e6b580b51c470588052c14aeb222f08f0e735027e366157c8742a6d5cb850780c2bf44ac63d89b048e5cc56dd47a1bfc740a3173e578f4e\"}", description_hash: "", preimage: "", payment_hash: "1234567890", amount: 21000, fees_paid: 0, created_at: 1737736866, expires_at: 0, settled_at: 0)
|
static let transaction1: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "{\"id\":\"7c0999a5870ca3ba0186a29a8650152b555cee29b53b5b8747d8a3798042d01c\",\"pubkey\":\"b8851a06dfd79d48fc325234a15e9a46a32a0982a823b54cdf82514b9b120ba1\",\"created_at\":1736383715,\"kind\":9734,\"tags\":[[\"p\",\"520830c334a3f79f88cac934580d26f91a7832c6b21fb9625690ea2ed81b5626\"],[\"amount\",\"21000\"],[\"e\",\"a25e152a4cd1b3bbc3d22e8e9315d8ea1f35c227b2f212c7cff18abff36fa208\"],[\"relays\",\"wss://nos.lol\",\"wss://nostr.wine\",\"wss://premium.primal.net\",\"wss://relay.damus.io\",\"wss://relay.nostr.band\",\"wss://relay.nostrarabia.com\"]],\"content\":\"🫡 Onward!\",\"sig\":\"e77d16822fa21b9c2e6b580b51c470588052c14aeb222f08f0e735027e366157c8742a6d5cb850780c2bf44ac63d89b048e5cc56dd47a1bfc740a3173e578f4e\"}", description_hash: "", preimage: "", payment_hash: "1234567890", amount: 21000, fees_paid: 0, created_at: 1737736866, expires_at: 0, settled_at: 0, metadata: nil)
|
||||||
static let transaction2: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789033", amount: 100000000, fees_paid: 0, created_at: 1737690090, expires_at: 0, settled_at: 0)
|
static let transaction2: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789033", amount: 100000000, fees_paid: 0, created_at: 1737690090, expires_at: 0, settled_at: 0, metadata: nil)
|
||||||
static let transaction3: WalletConnect.Transaction = WalletConnect.Transaction(type: "outgoing", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789042", amount: 303000, fees_paid: 0, created_at: 1737590101, expires_at: 0, settled_at: 0)
|
static let transaction3: WalletConnect.Transaction = WalletConnect.Transaction(type: "outgoing", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789042", amount: 303000, fees_paid: 0, created_at: 1737590101, expires_at: 0, settled_at: 0, metadata: nil)
|
||||||
static let transaction4: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "1234567890662", amount: 720000, fees_paid: 0, created_at: 1737090300, expires_at: 0, settled_at: 0)
|
static let transaction4: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "1234567890662", amount: 720000, fees_paid: 0, created_at: 1737090300, expires_at: 0, settled_at: 0, metadata: nil)
|
||||||
static var test_transactions: [WalletConnect.Transaction] = [transaction1, transaction2, transaction3, transaction4]
|
static var test_transactions: [WalletConnect.Transaction] = [transaction1, transaction2, transaction3, transaction4]
|
||||||
|
|
||||||
|
@State private static var hide_balance: Bool = false
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
TransactionsView(damus_state: tds, transactions: test_transactions)
|
TransactionsView(damus_state: tds, transactions: test_transactions, hide_balance: $hide_balance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ let WALLET_WARNING_THRESHOLD: UInt64 = 100000
|
|||||||
struct WalletView: View {
|
struct WalletView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
@State var show_settings: Bool = false
|
@State var show_settings: Bool = false
|
||||||
|
@State var show_send_sheet: Bool = false
|
||||||
@ObservedObject var model: WalletModel
|
@ObservedObject var model: WalletModel
|
||||||
@ObservedObject var settings: UserSettingsStore
|
@ObservedObject var settings: UserSettingsStore
|
||||||
|
@State private var showBalance: Bool = false
|
||||||
|
|
||||||
init(damus_state: DamusState, model: WalletModel? = nil) {
|
init(damus_state: DamusState, model: WalletModel? = nil) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
@@ -24,7 +26,7 @@ struct WalletView: View {
|
|||||||
func MainWalletView(nwc: WalletConnectURL) -> some View {
|
func MainWalletView(nwc: WalletConnectURL) -> some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 35) {
|
VStack(spacing: 35) {
|
||||||
if let balance = model.balance, balance > WALLET_WARNING_THRESHOLD {
|
if let balance = model.balance, balance > WALLET_WARNING_THRESHOLD && !settings.dismiss_wallet_high_balance_warning {
|
||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "exclamationmark.circle")
|
Image(systemName: "exclamationmark.circle")
|
||||||
@@ -36,8 +38,18 @@ struct WalletView: View {
|
|||||||
|
|
||||||
Text("If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more.", comment: "Text reminding the user has a high balance, recommending them to learn about self-custody")
|
Text("If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more.", comment: "Text reminding the user has a high balance, recommending them to learn about self-custody")
|
||||||
.foregroundStyle(.damusWarningSecondary)
|
.foregroundStyle(.damusWarningSecondary)
|
||||||
|
.accentColor(.damusWarningTertiary)
|
||||||
.opacity(0.8)
|
.opacity(0.8)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
settings.dismiss_wallet_high_balance_warning = true
|
||||||
|
}, label: {
|
||||||
|
Text("Dismiss", comment: "Button label to dismiss the safety reminder that the user's wallet has a high balance")
|
||||||
|
})
|
||||||
|
.bold()
|
||||||
|
.foregroundStyle(.damusWarningTertiary)
|
||||||
}
|
}
|
||||||
|
.privacySensitive()
|
||||||
.padding()
|
.padding()
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 20)
|
RoundedRectangle(cornerRadius: 20)
|
||||||
@@ -47,9 +59,22 @@ struct WalletView: View {
|
|||||||
|
|
||||||
VStack(spacing: 5) {
|
VStack(spacing: 5) {
|
||||||
|
|
||||||
BalanceView(balance: model.balance)
|
BalanceView(balance: model.balance, hide_balance: $settings.hide_wallet_balance)
|
||||||
|
|
||||||
TransactionsView(damus_state: damus_state, transactions: model.transactions)
|
Button(action: {
|
||||||
|
show_send_sheet = true
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "paperplane.fill")
|
||||||
|
Text("Send", comment: "Button label to send bitcoin payment from wallet")
|
||||||
|
.font(.headline)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
}
|
||||||
|
.buttonStyle(GradientButtonStyle())
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
|
||||||
|
TransactionsView(damus_state: damus_state, transactions: model.transactions, hide_balance: $settings.hide_wallet_balance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(NSLocalizedString("Wallet", comment: "Navigation title for Wallet view"))
|
.navigationTitle(NSLocalizedString("Wallet", comment: "Navigation title for Wallet view"))
|
||||||
@@ -93,23 +118,17 @@ struct WalletView: View {
|
|||||||
.presentationDragIndicator(.visible)
|
.presentationDragIndicator(.visible)
|
||||||
.presentationDetents([.large])
|
.presentationDetents([.large])
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $show_send_sheet) {
|
||||||
|
SendPaymentView(damus_state: damus_state, model: model, nwc: nwc)
|
||||||
|
.presentationDragIndicator(.visible)
|
||||||
|
.presentationDetents([.large])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func updateWalletInformation() async {
|
func updateWalletInformation() async {
|
||||||
guard let url = damus_state.settings.nostr_wallet_connect,
|
await WalletConnect.update_wallet_information(damus_state: damus_state)
|
||||||
let nwc = WalletConnectURL(str: url) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let flusher: OnFlush? = nil
|
|
||||||
|
|
||||||
let delay = 0.0 // We don't need a delay when fetching a transaction list or balance
|
|
||||||
|
|
||||||
WalletConnect.request_transaction_list(url: nwc, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, delay: delay, on_flush: flusher)
|
|
||||||
WalletConnect.request_balance_information(url: nwc, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, delay: delay, on_flush: flusher)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-44
@@ -80,7 +80,7 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
UNUserNotificationCenter.current().delegate = self
|
UNUserNotificationCenter.current().delegate = self
|
||||||
SKPaymentQueue.default().add(StoreObserver.standard)
|
SKPaymentQueue.default().add(StoreObserver.standard)
|
||||||
registerNotificationCategories()
|
registerNotificationCategories()
|
||||||
migrateKingfisherCacheIfNeeded()
|
ImageCacheMigrations.migrateKingfisherCacheIfNeeded()
|
||||||
configureKingfisherCache()
|
configureKingfisherCache()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -113,50 +113,8 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
completionHandler()
|
completionHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func migrateKingfisherCacheIfNeeded() {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
let defaults = UserDefaults.standard
|
|
||||||
let migrationKey = "KingfisherCacheMigrated"
|
|
||||||
|
|
||||||
// Check if migration has already been done
|
|
||||||
guard !defaults.bool(forKey: migrationKey) else { return }
|
|
||||||
|
|
||||||
// Get the default Kingfisher cache (before we override it)
|
|
||||||
let defaultCache = ImageCache.default
|
|
||||||
let oldCachePath = defaultCache.diskStorage.directoryURL.path
|
|
||||||
|
|
||||||
// New shared cache location
|
|
||||||
guard let groupURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: Constants.DAMUS_APP_GROUP_IDENTIFIER) else { return }
|
|
||||||
let newCachePath = groupURL.appendingPathComponent(Constants.IMAGE_CACHE_DIRNAME).path
|
|
||||||
|
|
||||||
// Check if the old cache exists
|
|
||||||
if fileManager.fileExists(atPath: oldCachePath) {
|
|
||||||
do {
|
|
||||||
// Move the old cache to the new location
|
|
||||||
try fileManager.moveItem(atPath: oldCachePath, toPath: newCachePath)
|
|
||||||
print("Successfully migrated Kingfisher cache to \(newCachePath)")
|
|
||||||
} catch {
|
|
||||||
print("Failed to migrate cache: \(error)")
|
|
||||||
// Optionally, copy instead of move if you want to preserve the old cache as a fallback
|
|
||||||
do {
|
|
||||||
try fileManager.copyItem(atPath: oldCachePath, toPath: newCachePath)
|
|
||||||
print("Copied cache instead due to error")
|
|
||||||
} catch {
|
|
||||||
print("Failed to copy cache: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark migration as complete
|
|
||||||
defaults.set(true, forKey: migrationKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func configureKingfisherCache() {
|
private func configureKingfisherCache() {
|
||||||
guard let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.DAMUS_APP_GROUP_IDENTIFIER) else {
|
let cachePath = ImageCacheMigrations.kingfisherCachePath()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let cachePath = groupURL.appendingPathComponent(Constants.IMAGE_CACHE_DIRNAME)
|
|
||||||
if let cache = try? ImageCache(name: "sharedCache", cacheDirectoryURL: cachePath) {
|
if let cache = try? ImageCache(name: "sharedCache", cacheDirectoryURL: cachePath) {
|
||||||
KingfisherManager.shared.cache = cache
|
KingfisherManager.shared.cache = cache
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -50,6 +50,22 @@
|
|||||||
<string>Folge ich</string>
|
<string>Folge ich</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>hellthread_notifications_disabled</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@HELLTHREAD_PROFILES@</string>
|
||||||
|
<key>HELLTHREAD_PROFILES</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>Benachrichtigungen ausblenden, die mehr als %d Profil markieren</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>Benachrichtigungen ausblenden, die mehr als %d Profile markieren.</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>imports_count</key>
|
<key>imports_count</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -82,6 +98,22 @@
|
|||||||
<string>%2$@ und %1$d weitere teilten</string>
|
<string>%2$@ und %1$d weitere teilten</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>quoted_reposts_count</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@QUOTE_REPOSTS@</string>
|
||||||
|
<key>QUOTE_REPOSTS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>Zitate</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>Zitat</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>reacted_tagged_in_3</key>
|
<key>reacted_tagged_in_3</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -242,22 +274,6 @@
|
|||||||
<string>geteilte Beiträge</string>
|
<string>geteilte Beiträge</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>quoted_reposts_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@QUOTE_REPOSTS@</string>
|
|
||||||
<key>QUOTE_REPOSTS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Zitate</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Zitat</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>sats</key>
|
<key>sats</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
|||||||
@@ -2,6 +2,22 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>follow_pack_user_count</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@FOLLOW_PACK_USERS@</string>
|
||||||
|
<key>FOLLOW_PACK_USERS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>user</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>users</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>followed_by_three_and_others</key>
|
<key>followed_by_three_and_others</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -82,6 +98,22 @@
|
|||||||
<string>Imports</string>
|
<string>Imports</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>notes_from_three_and_others</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@OTHERS@</string>
|
||||||
|
<key>OTHERS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>Notes from %2$@, %3$@, %4$@ & %1$d other in your trusted network</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>Notes from %2$@, %3$@, %4$@ & %1$d others in your trusted network</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>people_reposted_count</key>
|
<key>people_reposted_count</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@
|
|||||||
"comment" : "Amount of money required to publish to the Nostr relay. In English, this would look something like '10 sats / event', meaning it costs 10 sats to publish one event."
|
"comment" : "Amount of money required to publish to the Nostr relay. In English, this would look something like '10 sats / event', meaning it costs 10 sats to publish one event."
|
||||||
},
|
},
|
||||||
"%@ %@" : {
|
"%@ %@" : {
|
||||||
"comment" : "Sentence composed of 2 variables to describe how many imports were performed from loading a NostrScript. In source English, the first variable is the number of imports, and the second variable is 'Import' or 'Imports'.\nSentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.\nSentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.\nSentence composed of 2 variables to describe how many quoted reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.\nSentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.\nSentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.\nSentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.\nSentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.",
|
"comment" : "Sentence composed of 2 variables to describe how many imports were performed from loading a NostrScript. In source English, the first variable is the number of imports, and the second variable is 'Import' or 'Imports'.\nSentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.\nSentence composed of 2 variables to describe how many people are in the follow pack. In source English, the first variable is the number of users, and the second variable is 'user' or 'users'.\nSentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.\nSentence composed of 2 variables to describe how many quoted reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.\nSentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.\nSentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.\nSentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.\nSentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en-US" : {
|
"en-US" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
"comment" : "Heading for some advice text to help the user with an error"
|
"comment" : "Heading for some advice text to help the user with an error"
|
||||||
},
|
},
|
||||||
"All" : {
|
"All" : {
|
||||||
"comment" : "Human-readable short description of the 'friends filter' when it is set to 'all'\nLabel for filter for all notifications."
|
"comment" : "Human-readable short description of the 'trusted network filter' when it is disabled, and therefore is showing all content.\nLabel for filter for all notifications."
|
||||||
},
|
},
|
||||||
"All recent notes" : {
|
"All recent notes" : {
|
||||||
"comment" : "A label indicating that the notes being displayed below it are all recent notes"
|
"comment" : "A label indicating that the notes being displayed below it are all recent notes"
|
||||||
@@ -168,18 +168,30 @@
|
|||||||
"Already on Nostr?" : {
|
"Already on Nostr?" : {
|
||||||
"comment" : "Ask the user if they already have an account on Nostr"
|
"comment" : "Ask the user if they already have an account on Nostr"
|
||||||
},
|
},
|
||||||
|
"Also click here if you had a one-click setup before." : {
|
||||||
|
"comment" : "Button description hint for users who may want to do a one-click setup."
|
||||||
|
},
|
||||||
"Always show onboarding" : {
|
"Always show onboarding" : {
|
||||||
"comment" : "Developer mode setting to always show onboarding suggestions."
|
"comment" : "Developer mode setting to always show onboarding suggestions."
|
||||||
},
|
},
|
||||||
"An additional percentage of each zap will be sent to support Damus development" : {
|
"An additional percentage of each zap will be sent to support Damus development" : {
|
||||||
"comment" : "Text indicating that they can contribute zaps to support Damus development."
|
"comment" : "Text indicating that they can contribute zaps to support Damus development."
|
||||||
},
|
},
|
||||||
"An unexpected error happened while trying to create the new contact list. Please contact support." : {
|
"An internal error occurred in your wallet." : {
|
||||||
"comment" : "Error message for a failed contact list reset operation"
|
"comment" : "Error description for an internal error"
|
||||||
|
},
|
||||||
|
"An unexpected error happened while trying to perform this action. Please contact support." : {
|
||||||
|
"comment" : "Error message for a failed reset/repair operation"
|
||||||
},
|
},
|
||||||
"An unexpected error occurred. Please contact Damus support via [Nostr](damus:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955) or [email](support@damus.io) with the error message below." : {
|
"An unexpected error occurred. Please contact Damus support via [Nostr](damus:npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955) or [email](support@damus.io) with the error message below." : {
|
||||||
"comment" : "Label explaining there was an error, and suggesting next steps"
|
"comment" : "Label explaining there was an error, and suggesting next steps"
|
||||||
},
|
},
|
||||||
|
"An unknown error occurred while adding a relay." : {
|
||||||
|
"comment" : "Title of an unknown relay error message."
|
||||||
|
},
|
||||||
|
"An unspecified error occurred in your wallet." : {
|
||||||
|
"comment" : "Error description for an unspecified error"
|
||||||
|
},
|
||||||
"Animations" : {
|
"Animations" : {
|
||||||
"comment" : "Toggle to enable or disable image animation"
|
"comment" : "Toggle to enable or disable image animation"
|
||||||
},
|
},
|
||||||
@@ -286,7 +298,7 @@
|
|||||||
"comment" : "User-visible heading for an error message indicating a note has an unknown kind or is unsupported for viewing."
|
"comment" : "User-visible heading for an error message indicating a note has an unknown kind or is unsupported for viewing."
|
||||||
},
|
},
|
||||||
"Cancel" : {
|
"Cancel" : {
|
||||||
"comment" : "Alert button to cancel out of alert for muting a user.\nButton to cancel a repost.\nButton to cancel any interaction with the QRCode link.\nButton to cancel out of alert that creates a new mutelist.\nButton to cancel out of posting a note.\nButton to cancel out of search text entry mode.\nButton to cancel the upload.\nCancel button text for dismissing profile status settings view.\nCancel button text for dismissing updating image url.\nCancel deleting bookmarks.\nCancel deleting the user.\nCancel out of logging out the user.\nCancel out of search view.\nCancel resetting the contact list.\nText for button to cancel out of connecting Nostr Wallet Connect lightning wallet."
|
"comment" : "Alert button to cancel out of alert for muting a user.\nButton to cancel a repost.\nButton to cancel any interaction with the QRCode link.\nButton to cancel out of alert that creates a new mutelist.\nButton to cancel out of posting a note.\nButton to cancel out of search text entry mode.\nButton to cancel the upload.\nCancel button text for dismissing profile status settings view.\nCancel button text for dismissing updating image url.\nCancel deleting bookmarks.\nCancel deleting the user.\nCancel out of logging out the user.\nCancel out of search view.\nCancel the user-requested operation.\nText for button to cancel out of connecting Nostr Wallet Connect lightning wallet."
|
||||||
},
|
},
|
||||||
"Cancelled" : {
|
"Cancelled" : {
|
||||||
"comment" : "Title indicating that the user has cancelled."
|
"comment" : "Title indicating that the user has cancelled."
|
||||||
@@ -294,6 +306,15 @@
|
|||||||
"Changing this setting will cause the cache to be cleared. This will free space, but images may take longer to load again. Are you sure you want to proceed?" : {
|
"Changing this setting will cause the cache to be cleared. This will free space, but images may take longer to load again. Are you sure you want to proceed?" : {
|
||||||
"comment" : "Message explaining consequences of changing the 'enable animation' setting"
|
"comment" : "Message explaining consequences of changing the 'enable animation' setting"
|
||||||
},
|
},
|
||||||
|
"Check the address and/or the relay list." : {
|
||||||
|
"comment" : "Human readable tip for error"
|
||||||
|
},
|
||||||
|
"Check your account permissions or contact support." : {
|
||||||
|
"comment" : "Tip for restricted operation"
|
||||||
|
},
|
||||||
|
"Check your internet connection and try again. If the error persists, contact support." : {
|
||||||
|
"comment" : "Error tip when user tries to create the one-click Coinos wallet setup but fails for a generic reason."
|
||||||
|
},
|
||||||
"Choose from Library" : {
|
"Choose from Library" : {
|
||||||
"comment" : "Option to select photo from library"
|
"comment" : "Option to select photo from library"
|
||||||
},
|
},
|
||||||
@@ -312,9 +333,15 @@
|
|||||||
"Clearing Cache" : {
|
"Clearing Cache" : {
|
||||||
"comment" : "Loading message indicating that the cache is being cleared."
|
"comment" : "Loading message indicating that the cache is being cleared."
|
||||||
},
|
},
|
||||||
|
"Click here if you have a Coinos username and password." : {
|
||||||
|
"comment" : "Button description hint for users who may want to connect via the website."
|
||||||
|
},
|
||||||
"Close" : {
|
"Close" : {
|
||||||
"comment" : "Button label giving the user the option to close the sheet due to not being logged in.\nButton label giving the user the option to close the sheet from which they shared content\nButton label giving the user the option to close the sheet from which they were trying share.\nButton label giving the user the option to close the sheet from which they were trying to share.\nButton label giving the user the option to close the view when no content is available to share"
|
"comment" : "Button label giving the user the option to close the sheet due to not being logged in.\nButton label giving the user the option to close the sheet from which they shared content\nButton label giving the user the option to close the sheet from which they were trying share.\nButton label giving the user the option to close the sheet from which they were trying to share.\nButton label giving the user the option to close the view when no content is available to share"
|
||||||
},
|
},
|
||||||
|
"Coinos is a service operated by a third-party. The Damus team has no access to your wallet." : {
|
||||||
|
"comment" : "Small caption with a disclaimer that Damus does not own or have access to Coinos wallets, Coinos is a third-party service."
|
||||||
|
},
|
||||||
"Coming soon" : {
|
"Coming soon" : {
|
||||||
"comment" : "Feature is still in development and will be available soon"
|
"comment" : "Feature is still in development and will be available soon"
|
||||||
},
|
},
|
||||||
@@ -336,13 +363,16 @@
|
|||||||
"Connect to Coinos" : {
|
"Connect to Coinos" : {
|
||||||
"comment" : "Button to attach a Coinos Wallet, a service that provides a Lightning wallet for zapping sats. Coinos is the name of the service and should not be translated."
|
"comment" : "Button to attach a Coinos Wallet, a service that provides a Lightning wallet for zapping sats. Coinos is the name of the service and should not be translated."
|
||||||
},
|
},
|
||||||
|
"Connect via the website" : {
|
||||||
|
"comment" : "Button label for users who are setting up a Coinos wallet and would like to connect via the website"
|
||||||
|
},
|
||||||
"Connecting" : {
|
"Connecting" : {
|
||||||
"comment" : "Relay status label that indicates a relay is connecting."
|
"comment" : "Relay status label that indicates a relay is connecting."
|
||||||
},
|
},
|
||||||
"CONTACT" : {
|
"CONTACT" : {
|
||||||
"comment" : "Text label indicating that the information below is the contact information of the admin of the Nostr relay."
|
"comment" : "Text label indicating that the information below is the contact information of the admin of the Nostr relay."
|
||||||
},
|
},
|
||||||
"Contact list (Follows + Relay list)" : {
|
"Contact list" : {
|
||||||
"comment" : "Section title for Contact list first aid tools"
|
"comment" : "Section title for Contact list first aid tools"
|
||||||
},
|
},
|
||||||
"Contact list has been reset" : {
|
"Contact list has been reset" : {
|
||||||
@@ -358,7 +388,7 @@
|
|||||||
"comment" : "Section title for content filtering/moderation configuration."
|
"comment" : "Section title for content filtering/moderation configuration."
|
||||||
},
|
},
|
||||||
"Continue" : {
|
"Continue" : {
|
||||||
"comment" : "Button to dismiss suggested users view and continue to the main app\nContinue with bookmarks.\nContinue with deleting the user.\nContinue with resetting the contact list.\nPrompt to user to continue"
|
"comment" : "Button to dismiss suggested users view and continue to the main app\nContinue with bookmarks.\nContinue with deleting the user.\nContinue with the user-requested operation.\nPrompt to user to continue"
|
||||||
},
|
},
|
||||||
"Conversations" : {
|
"Conversations" : {
|
||||||
"comment" : "Label for filter for seeing notes and replies that involve conversations between the signed in user and the current profile."
|
"comment" : "Label for filter for seeing notes and replies that involve conversations between the signed in user and the current profile."
|
||||||
@@ -366,6 +396,9 @@
|
|||||||
"Copied" : {
|
"Copied" : {
|
||||||
"comment" : "Label indicating that a user's key was copied."
|
"comment" : "Label indicating that a user's key was copied."
|
||||||
},
|
},
|
||||||
|
"Copied!" : {
|
||||||
|
"comment" : "Label indicating that the error technical information was successfully copied to the clipboard, which shows up as soon as the user clicks the copy button."
|
||||||
|
},
|
||||||
"Copy" : {
|
"Copy" : {
|
||||||
"comment" : "Button to copy a relay server address.\nButton to copy the value found.\nContext menu option for copying the version of damus."
|
"comment" : "Button to copy a relay server address.\nButton to copy the value found.\nContext menu option for copying the version of damus."
|
||||||
},
|
},
|
||||||
@@ -396,6 +429,9 @@
|
|||||||
"Copy Report ID" : {
|
"Copy Report ID" : {
|
||||||
"comment" : "Button to copy report ID."
|
"comment" : "Button to copy report ID."
|
||||||
},
|
},
|
||||||
|
"Copy technical information" : {
|
||||||
|
"comment" : "Button label to allow user to copy technical information from an error screen (usually to provide our support team for further troubleshooting)"
|
||||||
|
},
|
||||||
"Copy text" : {
|
"Copy text" : {
|
||||||
"comment" : "Context menu option for copying the text from an note."
|
"comment" : "Context menu option for copying the text from an note."
|
||||||
},
|
},
|
||||||
@@ -408,6 +444,9 @@
|
|||||||
"Could not create your initial contact list event. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help." : {
|
"Could not create your initial contact list event. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help." : {
|
||||||
"comment" : "Error message to the user indicating that the initial contact list failed to be created."
|
"comment" : "Error message to the user indicating that the initial contact list failed to be created."
|
||||||
},
|
},
|
||||||
|
"Could not create your initial relay list. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help." : {
|
||||||
|
"comment" : "Error message to the user indicating that the initial relay list failed to be created."
|
||||||
|
},
|
||||||
"Could not find the user you're looking for" : {
|
"Could not find the user you're looking for" : {
|
||||||
"comment" : "Indicates that there are no users found."
|
"comment" : "Indicates that there are no users found."
|
||||||
},
|
},
|
||||||
@@ -429,6 +468,9 @@
|
|||||||
"Create new wallet" : {
|
"Create new wallet" : {
|
||||||
"comment" : "Button text for creating a new wallet."
|
"comment" : "Button text for creating a new wallet."
|
||||||
},
|
},
|
||||||
|
"Created by %@" : {
|
||||||
|
"comment" : "Lets the user know who created this follow pack."
|
||||||
|
},
|
||||||
"Current balance" : {
|
"Current balance" : {
|
||||||
"comment" : "Label for displaying current wallet balance"
|
"comment" : "Label for displaying current wallet balance"
|
||||||
},
|
},
|
||||||
@@ -477,6 +519,9 @@
|
|||||||
"Developer Mode enables features and options that may help developers diagnose issues and improve this app. Most users will not need Developer Mode." : {
|
"Developer Mode enables features and options that may help developers diagnose issues and improve this app. Most users will not need Developer Mode." : {
|
||||||
"comment" : "Section header for Developer Settings view"
|
"comment" : "Section header for Developer Settings view"
|
||||||
},
|
},
|
||||||
|
"Disable high balance warning" : {
|
||||||
|
"comment" : "Setting to disable high balance warnings on the user's wallet"
|
||||||
|
},
|
||||||
"Discard changes?" : {
|
"Discard changes?" : {
|
||||||
"comment" : "Alert user that changes have been made."
|
"comment" : "Alert user that changes have been made."
|
||||||
},
|
},
|
||||||
@@ -487,7 +532,7 @@
|
|||||||
"comment" : "Text for button to disconnect from Nostr Wallet Connect lightning wallet."
|
"comment" : "Text for button to disconnect from Nostr Wallet Connect lightning wallet."
|
||||||
},
|
},
|
||||||
"Dismiss" : {
|
"Dismiss" : {
|
||||||
"comment" : "Button to dismiss alert\nButton to dismiss error"
|
"comment" : "Button label to dismiss the safety reminder that the user's wallet has a high balance\nButton to dismiss alert\nButton to dismiss error"
|
||||||
},
|
},
|
||||||
"DMs" : {
|
"DMs" : {
|
||||||
"comment" : "Navigation title for DMs view, where DM is the English abbreviation for Direct Message.\nNavigation title for view of DMs, where DM is an English abbreviation for Direct Message.\nPicker option for DM selector for seeing only DMs that have been responded to. DM is the English abbreviation for Direct Message.\nSetting to enable DM Local Notification\nToolbar label for DMs view, where DM is the English abbreviation for Direct Message."
|
"comment" : "Navigation title for DMs view, where DM is the English abbreviation for Direct Message.\nNavigation title for view of DMs, where DM is an English abbreviation for Direct Message.\nPicker option for DM selector for seeing only DMs that have been responded to. DM is the English abbreviation for Direct Message.\nSetting to enable DM Local Notification\nToolbar label for DMs view, where DM is the English abbreviation for Direct Message."
|
||||||
@@ -495,9 +540,6 @@
|
|||||||
"Done" : {
|
"Done" : {
|
||||||
"comment" : "Button to dismiss wallet selection view for paying Lightning invoice.\nButton to leave edit mode for modifying the list of relays."
|
"comment" : "Button to dismiss wallet selection view for paying Lightning invoice.\nButton to leave edit mode for modifying the list of relays."
|
||||||
},
|
},
|
||||||
"Duplicate relay" : {
|
|
||||||
"comment" : "Title of the duplicate relay error message."
|
|
||||||
},
|
|
||||||
"Duration" : {
|
"Duration" : {
|
||||||
"comment" : "Label for profile status expiration duration picker.\nThe duration in which to mute the given item."
|
"comment" : "Label for profile status expiration duration picker.\nThe duration in which to mute the given item."
|
||||||
},
|
},
|
||||||
@@ -519,6 +561,9 @@
|
|||||||
"Edit profile picture" : {
|
"Edit profile picture" : {
|
||||||
"comment" : "Accessibility label for a button that edits a profile picture"
|
"comment" : "Accessibility label for a button that edits a profile picture"
|
||||||
},
|
},
|
||||||
|
"Empty error message" : {
|
||||||
|
"comment" : "A human readable placeholder to indicate that the error message is empty"
|
||||||
|
},
|
||||||
"Enable experimental Purple API support" : {
|
"Enable experimental Purple API support" : {
|
||||||
"comment" : "Developer mode setting to enable experimental Purple API support."
|
"comment" : "Developer mode setting to enable experimental Purple API support."
|
||||||
},
|
},
|
||||||
@@ -618,6 +663,9 @@
|
|||||||
"Follow me on Nostr" : {
|
"Follow me on Nostr" : {
|
||||||
"comment" : "Text on QR code view to prompt viewer looking at screen to follow the user."
|
"comment" : "Text on QR code view to prompt viewer looking at screen to follow the user."
|
||||||
},
|
},
|
||||||
|
"Follow Packs" : {
|
||||||
|
"comment" : "A label indicating that the items below it are follow packs"
|
||||||
|
},
|
||||||
"Followed by %@" : {
|
"Followed by %@" : {
|
||||||
"comment" : "Text to indicate that the user is followed by one of our follows."
|
"comment" : "Text to indicate that the user is followed by one of our follows."
|
||||||
},
|
},
|
||||||
@@ -673,9 +721,6 @@
|
|||||||
"Free" : {
|
"Free" : {
|
||||||
"comment" : "Dropdown option for selecting Free plan for DeepL translation service."
|
"comment" : "Dropdown option for selecting Free plan for DeepL translation service."
|
||||||
},
|
},
|
||||||
"Friends of friends" : {
|
|
||||||
"comment" : "Human-readable short description of the 'friends filter' when it is set to 'friends-of-friends'"
|
|
||||||
},
|
|
||||||
"General" : {
|
"General" : {
|
||||||
"comment" : "Section header for general damus notifications user configuration"
|
"comment" : "Section header for general damus notifications user configuration"
|
||||||
},
|
},
|
||||||
@@ -721,9 +766,18 @@
|
|||||||
"Hide all 🤙's" : {
|
"Hide all 🤙's" : {
|
||||||
"comment" : "Section footer describing OnlyZaps mode"
|
"comment" : "Section footer describing OnlyZaps mode"
|
||||||
},
|
},
|
||||||
|
"Hide balance" : {
|
||||||
|
"comment" : "Setting to hide wallet balance."
|
||||||
|
},
|
||||||
"Hide notes with #nsfw tags" : {
|
"Hide notes with #nsfw tags" : {
|
||||||
"comment" : "Setting to hide notes with the #nsfw (not safe for work) tags"
|
"comment" : "Setting to hide notes with the #nsfw (not safe for work) tags"
|
||||||
},
|
},
|
||||||
|
"Hide notifications that tag many profiles" : {
|
||||||
|
"comment" : "Label for notification settings toggle that hides notifications that tag many people."
|
||||||
|
},
|
||||||
|
"Highlight" : {
|
||||||
|
"comment" : "Context menu action to highlight the selected text as context to draft a new note."
|
||||||
|
},
|
||||||
"Highlighted" : {
|
"Highlighted" : {
|
||||||
"comment" : "Label to indicate that the user is highlighting their own post."
|
"comment" : "Label to indicate that the user is highlighting their own post."
|
||||||
},
|
},
|
||||||
@@ -733,12 +787,18 @@
|
|||||||
"Home" : {
|
"Home" : {
|
||||||
"comment" : "Navigation bar title for Home view where notes and replies appear from those who the user is following."
|
"comment" : "Navigation bar title for Home view where notes and replies appear from those who the user is following."
|
||||||
},
|
},
|
||||||
|
"How would you like to connect to your Coinos wallet?" : {
|
||||||
|
"comment" : "Question for the user when connecting a Coinos wallet."
|
||||||
|
},
|
||||||
"Howdy! I’m a graphic designer during the day and coder at night, but I’m also trying to spend more time outdoors.\n\nHope to meet folks who are on their own journeys to a peaceful and free life!" : {
|
"Howdy! I’m a graphic designer during the day and coder at night, but I’m also trying to spend more time outdoors.\n\nHope to meet folks who are on their own journeys to a peaceful and free life!" : {
|
||||||
"comment" : "First post example given to the user during onboarding, as a suggestion as to what they could post first"
|
"comment" : "First post example given to the user during onboarding, as a suggestion as to what they could post first"
|
||||||
},
|
},
|
||||||
"https://jb55.com" : {
|
"https://jb55.com" : {
|
||||||
"comment" : "Placeholder example text for website URL for user profile."
|
"comment" : "Placeholder example text for website URL for user profile."
|
||||||
},
|
},
|
||||||
|
"If your wallet balance is getting high, it's important to understand how to keep your funds secure. Please consider learning the best practices to ensure your assets remain safe. [Click here](https://damus.io/docs/wallet/high-balance-safety-reminder/) to learn more." : {
|
||||||
|
"comment" : "Text reminding the user has a high balance, recommending them to learn about self-custody"
|
||||||
|
},
|
||||||
"Illegal Content" : {
|
"Illegal Content" : {
|
||||||
"comment" : "Description of report type for illegal content."
|
"comment" : "Description of report type for illegal content."
|
||||||
},
|
},
|
||||||
@@ -758,7 +818,7 @@
|
|||||||
"comment" : "Description of report type for impersonation."
|
"comment" : "Description of report type for impersonation."
|
||||||
},
|
},
|
||||||
"In progress…" : {
|
"In progress…" : {
|
||||||
"comment" : "Loading message indicating that a contact list reset operation is in progress."
|
"comment" : "Loading message indicating that a first aid operation is in progress."
|
||||||
},
|
},
|
||||||
"Indefinite" : {
|
"Indefinite" : {
|
||||||
"comment" : "Mute a given item indefinitly (until user unmutes it). As opposed to muting the item for a given period of time."
|
"comment" : "Mute a given item indefinitly (until user unmutes it). As opposed to muting the item for a given period of time."
|
||||||
@@ -775,6 +835,9 @@
|
|||||||
"Invalid Nostr wallet connection string" : {
|
"Invalid Nostr wallet connection string" : {
|
||||||
"comment" : "Error message when an invalid Nostr wallet connection string is provided."
|
"comment" : "Error message when an invalid Nostr wallet connection string is provided."
|
||||||
},
|
},
|
||||||
|
"Invalid relay address" : {
|
||||||
|
"comment" : "Heading for an error when adding a relay"
|
||||||
|
},
|
||||||
"Invalid Tip Address" : {
|
"Invalid Tip Address" : {
|
||||||
"comment" : "Title of alerting as invalid tip address."
|
"comment" : "Title of alerting as invalid tip address."
|
||||||
},
|
},
|
||||||
@@ -871,6 +934,9 @@
|
|||||||
"Maybe later" : {
|
"Maybe later" : {
|
||||||
"comment" : "Text for button to disconnect from Nostr Wallet Connect lightning wallet."
|
"comment" : "Text for button to disconnect from Nostr Wallet Connect lightning wallet."
|
||||||
},
|
},
|
||||||
|
"Media from someone you don't follow" : {
|
||||||
|
"comment" : "Label on the image blur mask"
|
||||||
|
},
|
||||||
"Media previews" : {
|
"Media previews" : {
|
||||||
"comment" : "Setting to show media"
|
"comment" : "Setting to show media"
|
||||||
},
|
},
|
||||||
@@ -890,7 +956,7 @@
|
|||||||
"comment" : "Monthly renewal of purple subscription"
|
"comment" : "Monthly renewal of purple subscription"
|
||||||
},
|
},
|
||||||
"Mute" : {
|
"Mute" : {
|
||||||
"comment" : "Alert button to mute a user.\nButton label that allows the user to mute the user shown on-screen\nButton to mute a profile\nTitle for confirmation dialog to mute a profile."
|
"comment" : "Alert button to mute a user.\nButton label that allows the user to mute the user shown on-screen\nButton to mute a profile\nContext menu action to mute the selected word.\nTitle for confirmation dialog to mute a profile."
|
||||||
},
|
},
|
||||||
"Mute %@?" : {
|
"Mute %@?" : {
|
||||||
"comment" : "Alert message prompt to ask if a user should be muted."
|
"comment" : "Alert message prompt to ask if a user should be muted."
|
||||||
@@ -946,9 +1012,15 @@
|
|||||||
"No content available to share" : {
|
"No content available to share" : {
|
||||||
"comment" : "Title indicating that there was no available content to share"
|
"comment" : "Title indicating that there was no available content to share"
|
||||||
},
|
},
|
||||||
|
"No cover image" : {
|
||||||
|
"comment" : "Text letting user know there is no cover image."
|
||||||
|
},
|
||||||
"No image is currently setup" : {
|
"No image is currently setup" : {
|
||||||
"comment" : "Accessibility value on image control"
|
"comment" : "Accessibility value on image control"
|
||||||
},
|
},
|
||||||
|
"No initial relay list available to update." : {
|
||||||
|
"comment" : "Human readable error description"
|
||||||
|
},
|
||||||
"No logs to display" : {
|
"No logs to display" : {
|
||||||
"comment" : "Label to indicate that there are no developer mode logs available to be displayed on the screen"
|
"comment" : "Label to indicate that there are no developer mode logs available to be displayed on the screen"
|
||||||
},
|
},
|
||||||
@@ -961,6 +1033,9 @@
|
|||||||
"No profile picture is currently setup" : {
|
"No profile picture is currently setup" : {
|
||||||
"comment" : "Accessibility value on profile picture image control"
|
"comment" : "Accessibility value on profile picture image control"
|
||||||
},
|
},
|
||||||
|
"No relay list was found. You might experience issues using the app. If you suspect you have permanently lost your relay list (or if you never had one), you can fix this by resetting it" : {
|
||||||
|
"comment" : "Section footer for relay list first aid tools"
|
||||||
|
},
|
||||||
"No results" : {
|
"No results" : {
|
||||||
"comment" : "A label indicating that note search resulted in no results"
|
"comment" : "A label indicating that note search resulted in no results"
|
||||||
},
|
},
|
||||||
@@ -1024,6 +1099,31 @@
|
|||||||
"Notes & Replies" : {
|
"Notes & Replies" : {
|
||||||
"comment" : "Label for filter for seeing notes and replies (instead of only notes)."
|
"comment" : "Label for filter for seeing notes and replies (instead of only notes)."
|
||||||
},
|
},
|
||||||
|
"Notes from %@" : {
|
||||||
|
"comment" : "Text to indicate that notes from one pubkey in our trusted network are shown below."
|
||||||
|
},
|
||||||
|
"Notes from %@ & %@" : {
|
||||||
|
"comment" : "Text to indicate that notes from two pubkeys in our trusted network are shown below.",
|
||||||
|
"localizations" : {
|
||||||
|
"en-US" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Notes from %1$@ & %2$@"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Notes from %@, %@ & %@" : {
|
||||||
|
"comment" : "Text to indicate that notes from three pubkeys in our trusted network are shown below.",
|
||||||
|
"localizations" : {
|
||||||
|
"en-US" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Notes from %1$@, %2$@ & %3$@"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Notes with the #nsfw tag usually contains adult content or other \"Not safe for work\" content" : {
|
"Notes with the #nsfw tag usually contains adult content or other \"Not safe for work\" content" : {
|
||||||
"comment" : "Section footer clarifying what #nsfw (not safe for work) tags mean"
|
"comment" : "Section footer clarifying what #nsfw (not safe for work) tags mean"
|
||||||
},
|
},
|
||||||
@@ -1054,12 +1154,18 @@
|
|||||||
"Nudity" : {
|
"Nudity" : {
|
||||||
"comment" : "Description of report type for nudity."
|
"comment" : "Description of report type for nudity."
|
||||||
},
|
},
|
||||||
|
"NWC wallet" : {
|
||||||
|
"comment" : "Title for section in zap settings that controls general NWC wallet settings."
|
||||||
|
},
|
||||||
"Ok" : {
|
"Ok" : {
|
||||||
"comment" : "Button to dismiss the alert."
|
"comment" : "Button to dismiss the alert."
|
||||||
},
|
},
|
||||||
"OK" : {
|
"OK" : {
|
||||||
"comment" : "Button label indicating user wants to proceed.\nButton label to dismiss an error dialog"
|
"comment" : "Button label indicating user wants to proceed.\nButton label to dismiss an error dialog"
|
||||||
},
|
},
|
||||||
|
"One-click setup" : {
|
||||||
|
"comment" : "Button label for users to do a one-click Coinos wallet setup."
|
||||||
|
},
|
||||||
"Online" : {
|
"Online" : {
|
||||||
"comment" : "Relay status label that indicates a relay is connected."
|
"comment" : "Relay status label that indicates a relay is connected."
|
||||||
},
|
},
|
||||||
@@ -1112,7 +1218,7 @@
|
|||||||
"comment" : "Label to display that authentication to a server is pending."
|
"comment" : "Label to display that authentication to a server is pending."
|
||||||
},
|
},
|
||||||
"People" : {
|
"People" : {
|
||||||
"comment" : "Label for filter for seeing only people follows."
|
"comment" : "Label for filter for seeing only people follows.\nLabel for filter for seeing the people in this follow pack."
|
||||||
},
|
},
|
||||||
"People will be able to send you cash from your profile. No money goes to Damus." : {
|
"People will be able to send you cash from your profile. No money goes to Damus." : {
|
||||||
"comment" : "The description for one of the \"Why add Zaps?\" boxes"
|
"comment" : "The description for one of the \"Why add Zaps?\" boxes"
|
||||||
@@ -1123,27 +1229,66 @@
|
|||||||
"Plan" : {
|
"Plan" : {
|
||||||
"comment" : "Prompt selection of DeepL subscription plan to perform machine translations on notes"
|
"comment" : "Prompt selection of DeepL subscription plan to perform machine translations on notes"
|
||||||
},
|
},
|
||||||
|
"Please check for updates or contact your wallet provider." : {
|
||||||
|
"comment" : "Tip for not implemented error"
|
||||||
|
},
|
||||||
|
"Please check the address and try again" : {
|
||||||
|
"comment" : "Tip for an error where the relay address being added is invalid"
|
||||||
|
},
|
||||||
"Please choose relays from the list below to filter the current feed:" : {
|
"Please choose relays from the list below to filter the current feed:" : {
|
||||||
"comment" : "Instructions on how to filter a specific timeline feed by choosing relay servers to filter on."
|
"comment" : "Instructions on how to filter a specific timeline feed by choosing relay servers to filter on."
|
||||||
},
|
},
|
||||||
|
"Please contact support for further help." : {
|
||||||
|
"comment" : "Human readable tips for what to do for a failure to find the relay list"
|
||||||
|
},
|
||||||
|
"Please contact support." : {
|
||||||
|
"comment" : "Tip for an unknown relay error message."
|
||||||
|
},
|
||||||
|
"Please contact the developer of your wallet provider for help." : {
|
||||||
|
"comment" : "Human readable error description for an unknown error raised by a wallet provider."
|
||||||
|
},
|
||||||
"Please contact the person who provided the link, and ask for another link." : {
|
"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."
|
"comment" : "User-visible tip on what to do if a link contains a deprecated \"nrelay\" reference."
|
||||||
},
|
},
|
||||||
|
"Please copy the technical info and send it to our support team." : {
|
||||||
|
"comment" : "Tip on how to resolve issue when wallet returns an invalid response"
|
||||||
|
},
|
||||||
|
"Please deposit more funds and try again." : {
|
||||||
|
"comment" : "Tip for insufficient balance errors"
|
||||||
|
},
|
||||||
"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." : {
|
"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"
|
"comment" : "User-facing tips on what to do if a Purple welcome link doesn't work"
|
||||||
},
|
},
|
||||||
|
"Please go to Settings > First Aid > Repair relay list, or contact support." : {
|
||||||
|
"comment" : "Human readable tip for error"
|
||||||
|
},
|
||||||
|
"Please make sure you have logged-in with your private key." : {
|
||||||
|
"comment" : "Human readable tip for error"
|
||||||
|
},
|
||||||
|
"Please try again later or contact support if the issue persists." : {
|
||||||
|
"comment" : "Human readable tip for error"
|
||||||
|
},
|
||||||
|
"Please try again or contact your wallet provider for further assistance." : {
|
||||||
|
"comment" : "Tip for unspecified error"
|
||||||
|
},
|
||||||
"Please try again, check the URL for typos, or contact support for further help." : {
|
"Please try again, check the URL for typos, or contact support for further help." : {
|
||||||
"comment" : "User visible error tips"
|
"comment" : "User visible error tips"
|
||||||
},
|
},
|
||||||
"Please try opening this content on another Nostr app that supports this type of content." : {
|
"Please try opening this content on another Nostr app that supports this type of content." : {
|
||||||
"comment" : "User-visible advice on what to do if they see the error indicating a note has an unknown kind or is unsupported for viewing."
|
"comment" : "User-visible advice on what to do if they see the error indicating a note has an unknown kind or is unsupported for viewing."
|
||||||
},
|
},
|
||||||
|
"Please verify your credentials or permissions." : {
|
||||||
|
"comment" : "Tip for unauthorized access"
|
||||||
|
},
|
||||||
"Point your camera to a QR code…" : {
|
"Point your camera to a QR code…" : {
|
||||||
"comment" : "Text on QR code camera view instructing user to point to QR code"
|
"comment" : "Text on QR code camera view instructing user to point to QR code"
|
||||||
},
|
},
|
||||||
"Post" : {
|
"Post" : {
|
||||||
"comment" : "Button to post a note."
|
"comment" : "Button to post a note."
|
||||||
},
|
},
|
||||||
|
"Posts" : {
|
||||||
|
"comment" : "Label for filter for seeing the posts from the people in this follow pack."
|
||||||
|
},
|
||||||
"Private" : {
|
"Private" : {
|
||||||
"comment" : "Button text to indicate that the zap type is a private zap.\nHeading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading.\nPicker option to indicate that a zap should be sent privately and not identify the user to the public."
|
"comment" : "Button text to indicate that the zap type is a private zap.\nHeading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading.\nPicker option to indicate that a zap should be sent privately and not identify the user to the public."
|
||||||
},
|
},
|
||||||
@@ -1234,6 +1379,12 @@
|
|||||||
"Recommended" : {
|
"Recommended" : {
|
||||||
"comment" : "Title of the tab that shows the list of relays recommended by Damus."
|
"comment" : "Title of the tab that shows the list of relays recommended by Damus."
|
||||||
},
|
},
|
||||||
|
"Relay list" : {
|
||||||
|
"comment" : "Section title for Relay list first aid tools"
|
||||||
|
},
|
||||||
|
"Relay list has been repaired" : {
|
||||||
|
"comment" : "Message indicating that the relay list was successfully repaired."
|
||||||
|
},
|
||||||
"Relay Logs" : {
|
"Relay Logs" : {
|
||||||
"comment" : "Text label indicating that the text below it are developer mode logs."
|
"comment" : "Text label indicating that the text below it are developer mode logs."
|
||||||
},
|
},
|
||||||
@@ -1261,6 +1412,12 @@
|
|||||||
"Renews on" : {
|
"Renews on" : {
|
||||||
"comment" : "Indicating when the subscription will renew"
|
"comment" : "Indicating when the subscription will renew"
|
||||||
},
|
},
|
||||||
|
"Repair relay list" : {
|
||||||
|
"comment" : "Button to repair relay list."
|
||||||
|
},
|
||||||
|
"Replies outside your trusted network" : {
|
||||||
|
"comment" : "Section title in thread for replies from outside of the current user's trusted network, which is their follows and follows of follows."
|
||||||
|
},
|
||||||
"Reply" : {
|
"Reply" : {
|
||||||
"comment" : "Accessibility label for reply button"
|
"comment" : "Accessibility label for reply button"
|
||||||
},
|
},
|
||||||
@@ -1320,6 +1477,9 @@
|
|||||||
"Reset contact list" : {
|
"Reset contact list" : {
|
||||||
"comment" : "Button to reset contact list."
|
"comment" : "Button to reset contact list."
|
||||||
},
|
},
|
||||||
|
"Reset tips on launch" : {
|
||||||
|
"comment" : "Developer mode setting to reset tips upon app first launch. Tips are visual contextual hints that highlight new, interesting, or unused features users have not discovered yet."
|
||||||
|
},
|
||||||
"Retry" : {
|
"Retry" : {
|
||||||
"comment" : "Button to retry completing account creation after an error occurred."
|
"comment" : "Button to retry completing account creation after an error occurred."
|
||||||
},
|
},
|
||||||
@@ -1335,6 +1495,9 @@
|
|||||||
"Runtime error" : {
|
"Runtime error" : {
|
||||||
"comment" : "Indication that a runtime error occurred when running a NostrScript."
|
"comment" : "Indication that a runtime error occurred when running a NostrScript."
|
||||||
},
|
},
|
||||||
|
"Safety Reminder" : {
|
||||||
|
"comment" : "Heading for a safety reminder that appears when the user has too many funds, recommending them to learn about safeguarding their funds."
|
||||||
|
},
|
||||||
"Satoshi Nakamoto" : {
|
"Satoshi Nakamoto" : {
|
||||||
"comment" : "Name of Bitcoin creator(s)."
|
"comment" : "Name of Bitcoin creator(s)."
|
||||||
},
|
},
|
||||||
@@ -1482,6 +1645,9 @@
|
|||||||
"Show profile action sheets" : {
|
"Show profile action sheets" : {
|
||||||
"comment" : "Setting to show profile action sheets when clicking on a user's profile picture"
|
"comment" : "Setting to show profile action sheets when clicking on a user's profile picture"
|
||||||
},
|
},
|
||||||
|
"Show replies from your trusted network first" : {
|
||||||
|
"comment" : "Setting to show replies in threads from the current user's trusted network first."
|
||||||
|
},
|
||||||
"Show wallet selector" : {
|
"Show wallet selector" : {
|
||||||
"comment" : "Toggle to show or hide selection of wallet."
|
"comment" : "Toggle to show or hide selection of wallet."
|
||||||
},
|
},
|
||||||
@@ -1518,6 +1684,9 @@
|
|||||||
"Someone zapped you ⚡️" : {
|
"Someone zapped you ⚡️" : {
|
||||||
"comment" : "Title label for a push notification where someone zapped the user"
|
"comment" : "Title label for a push notification where someone zapped the user"
|
||||||
},
|
},
|
||||||
|
"Something went wrong when performing the one-click Coinos wallet setup." : {
|
||||||
|
"comment" : "Error label when user tries the one-click Coinos wallet setup but fails for some generic reason."
|
||||||
|
},
|
||||||
"Sorry, but for some reason there has been an issue while trying to crop this image. Please try again later. If the error persists, please contact [Damus support](mailto:support@damus.io)" : {
|
"Sorry, but for some reason there has been an issue while trying to crop this image. Please try again later. If the error persists, please contact [Damus support](mailto:support@damus.io)" : {
|
||||||
"comment" : "Cropping error message"
|
"comment" : "Cropping error message"
|
||||||
},
|
},
|
||||||
@@ -1572,6 +1741,9 @@
|
|||||||
"Take Photo" : {
|
"Take Photo" : {
|
||||||
"comment" : "Option to take a photo with the camera"
|
"comment" : "Option to take a photo with the camera"
|
||||||
},
|
},
|
||||||
|
"Tap to load" : {
|
||||||
|
"comment" : "Label for button that allows user to dismiss media content warning and unblur the image"
|
||||||
|
},
|
||||||
"Test (local)" : {
|
"Test (local)" : {
|
||||||
"comment" : "Label indicating a local test environment for Damus Purple functionality (Developer feature)\nLabel indicating a local test environment for Push notification functionality (Developer feature)"
|
"comment" : "Label indicating a local test environment for Damus Purple functionality (Developer feature)\nLabel indicating a local test environment for Push notification functionality (Developer feature)"
|
||||||
},
|
},
|
||||||
@@ -1593,18 +1765,21 @@
|
|||||||
"The camera was not capable of scanning the requested codes." : {
|
"The camera was not capable of scanning the requested codes." : {
|
||||||
"comment" : "Camera's bad output error label"
|
"comment" : "Camera's bad output error label"
|
||||||
},
|
},
|
||||||
"The relay you are trying to add is already added.\nYou're all set!" : {
|
|
||||||
"comment" : "An error message that appears when the user attempts to add a relay that has already been added."
|
|
||||||
},
|
|
||||||
"The social network you control" : {
|
"The social network you control" : {
|
||||||
"comment" : "Quick description of what Damus is"
|
"comment" : "Quick description of what Damus is"
|
||||||
},
|
},
|
||||||
|
"The specified relay that you are trying to udpate was not found in your relay list." : {
|
||||||
|
"comment" : "Human readable error description"
|
||||||
|
},
|
||||||
"There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@" : {
|
"There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@" : {
|
||||||
"comment" : "In-app purchase error message for the user"
|
"comment" : "In-app purchase error message for the user"
|
||||||
},
|
},
|
||||||
"There is no content available to share at this time. Please close this view and try again." : {
|
"There is no content available to share at this time. Please close this view and try again." : {
|
||||||
"comment" : "Label explaining that no content is available to share and instructing the user to close the view and try again."
|
"comment" : "Label explaining that no content is available to share and instructing the user to close the view and try again."
|
||||||
},
|
},
|
||||||
|
"There was a problem creating the relay list event." : {
|
||||||
|
"comment" : "Human readable error description"
|
||||||
|
},
|
||||||
"There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io" : {
|
"There was an error loading your account. Please try again later. If problem persists, please contact us at support@damus.io" : {
|
||||||
"comment" : "Error label when Purple account information fails to load"
|
"comment" : "Error label when Purple account information fails to load"
|
||||||
},
|
},
|
||||||
@@ -1614,15 +1789,27 @@
|
|||||||
"This device's in-app purchase is registered to a different Nostr account. Unable to manage this Purple account. If you believe this was a mistake, please contact us via support@damus.io." : {
|
"This device's in-app purchase is registered to a different Nostr account. Unable to manage this Purple account. If you believe this was a mistake, please contact us via support@damus.io." : {
|
||||||
"comment" : "Notice label that user cannot manage their In-App purchases"
|
"comment" : "Notice label that user cannot manage their In-App purchases"
|
||||||
},
|
},
|
||||||
|
"This feature is not implemented by your wallet." : {
|
||||||
|
"comment" : "Error description for not implemented feature"
|
||||||
|
},
|
||||||
"This is a public key, you will not be able to make notes or interact in any way. This is used for viewing accounts from their perspective." : {
|
"This is a public key, you will not be able to make notes or interact in any way. This is used for viewing accounts from their perspective." : {
|
||||||
"comment" : "Warning that the inputted account key is a public key and the result of what happens because of it."
|
"comment" : "Warning that the inputted account key is a public key and the result of what happens because of it."
|
||||||
},
|
},
|
||||||
|
"This is an unexpected error, please contact support." : {
|
||||||
|
"comment" : "Human readable tip for error"
|
||||||
|
},
|
||||||
"This is my first post on Nostr 💜. I love drawing and folding Origami!\n\nNice to meet you all! #introductions #plebchain " : {
|
"This is my first post on Nostr 💜. I love drawing and folding Origami!\n\nNice to meet you all! #introductions #plebchain " : {
|
||||||
"comment" : "First post example given to the user during onboarding, as a suggestion as to what they could post first"
|
"comment" : "First post example given to the user during onboarding, as a suggestion as to what they could post first"
|
||||||
},
|
},
|
||||||
"This note contains too many items and cannot be rendered" : {
|
"This note contains too many items and cannot be rendered" : {
|
||||||
"comment" : "Error message indicating that a note is too big and cannot be rendered"
|
"comment" : "Error message indicating that a note is too big and cannot be rendered"
|
||||||
},
|
},
|
||||||
|
"This operation is restricted by your wallet." : {
|
||||||
|
"comment" : "Error description for restricted operation"
|
||||||
|
},
|
||||||
|
"This relay is already in your list." : {
|
||||||
|
"comment" : "Human readable tip for error"
|
||||||
|
},
|
||||||
"This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?" : {
|
"This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?" : {
|
||||||
"comment" : "Comment explaining why a user cannot be zapped."
|
"comment" : "Comment explaining why a user cannot be zapped."
|
||||||
},
|
},
|
||||||
@@ -1635,6 +1822,12 @@
|
|||||||
"Toggle key visibility" : {
|
"Toggle key visibility" : {
|
||||||
"comment" : "Accessibility label for toggling the visibility of the private key input field"
|
"comment" : "Accessibility label for toggling the visibility of the private key input field"
|
||||||
},
|
},
|
||||||
|
"Toggle visibility of content from outside your trusted network" : {
|
||||||
|
"comment" : "Title of tip that informs users what trusted network means and that they can toggle the visibility of content from outside their trusted network."
|
||||||
|
},
|
||||||
|
"Toggle visibility of replies from outside your trusted network" : {
|
||||||
|
"comment" : "Title of tip that informs users what trusted network means and that they can toggle the visibility of threaded replies from outside their trusted network."
|
||||||
|
},
|
||||||
"Top hits" : {
|
"Top hits" : {
|
||||||
"comment" : "A label indicating that the notes being displayed below it are all top note search results"
|
"comment" : "A label indicating that the notes being displayed below it are all top note search results"
|
||||||
},
|
},
|
||||||
@@ -1665,9 +1858,15 @@
|
|||||||
"Truncate timeline text" : {
|
"Truncate timeline text" : {
|
||||||
"comment" : "Setting to truncate text in timeline"
|
"comment" : "Setting to truncate text in timeline"
|
||||||
},
|
},
|
||||||
|
"Trusted Network" : {
|
||||||
|
"comment" : "Human-readable short description of the 'trusted network filter' when it is enabled, and therefore showing content from only the trusted network."
|
||||||
|
},
|
||||||
"Try checking the link again, your internet connection, or contact the person who provided you the link for help." : {
|
"Try checking the link again, your internet connection, or contact the person who provided you the link for help." : {
|
||||||
"comment" : "Tips on what to do if a note cannot be found."
|
"comment" : "Tips on what to do if a note cannot be found."
|
||||||
},
|
},
|
||||||
|
"Try restarting your wallet or contacting support if the problem persists." : {
|
||||||
|
"comment" : "Tip for internal error"
|
||||||
|
},
|
||||||
"Type %@ to delete" : {
|
"Type %@ to delete" : {
|
||||||
"comment" : "Text field prompt asking user to type DELETE in all caps to confirm that they want to proceed with deleting their account."
|
"comment" : "Text field prompt asking user to type DELETE in all caps to confirm that they want to proceed with deleting their account."
|
||||||
},
|
},
|
||||||
@@ -1705,7 +1904,7 @@
|
|||||||
"comment" : "Label represnting a button that the user can tap to unmute a given hashtag so they start seeing it in their feed again."
|
"comment" : "Label represnting a button that the user can tap to unmute a given hashtag so they start seeing it in their feed again."
|
||||||
},
|
},
|
||||||
"Untitled" : {
|
"Untitled" : {
|
||||||
"comment" : "Title of longform event if it is untitled."
|
"comment" : "Title of follow list event if it is untitled.\nTitle of longform event if it is untitled."
|
||||||
},
|
},
|
||||||
"Update" : {
|
"Update" : {
|
||||||
"comment" : "Update button text for updating image url."
|
"comment" : "Update button text for updating image url."
|
||||||
@@ -1761,10 +1960,28 @@
|
|||||||
"Visit the Damus website on a web browser to manage billing" : {
|
"Visit the Damus website on a web browser to manage billing" : {
|
||||||
"comment" : "Instruction on how to manage billing externally"
|
"comment" : "Instruction on how to manage billing externally"
|
||||||
},
|
},
|
||||||
|
"Wait a few moments, and then try again." : {
|
||||||
|
"comment" : "Tip for rate limit error"
|
||||||
|
},
|
||||||
|
"Wait for the quota to reset, or configure your wallet provider to allow a higher limit." : {
|
||||||
|
"comment" : "Tip for quota exceeded"
|
||||||
|
},
|
||||||
"Wallet" : {
|
"Wallet" : {
|
||||||
"comment" : "Navigation title for Wallet view\nNavigation title for attaching Nostr Wallet Connect lightning wallet.\nSidebar menu label for Wallet view.\nTitle for section in zap settings that controls the Lightning wallet selection."
|
"comment" : "Navigation title for Wallet view\nNavigation title for attaching Nostr Wallet Connect lightning wallet.\nSidebar menu label for Wallet view.\nTitle for section in zap settings that controls the Lightning wallet selection."
|
||||||
},
|
},
|
||||||
"WARNING:\n\nThis will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY." : {
|
"Wallet provider returned a response that we could not decrypt." : {
|
||||||
|
"comment" : "Error description shown to the user when a response from the wallet provider contains data the app could not decrypt."
|
||||||
|
},
|
||||||
|
"Wallet provider returned a response that we do not understand." : {
|
||||||
|
"comment" : "Error description shown to the user when a response from the wallet provider contains data the app does not understand"
|
||||||
|
},
|
||||||
|
"Wallet provider returned an invalid response." : {
|
||||||
|
"comment" : "Error description shown to the user when a response from the wallet provider is invalid"
|
||||||
|
},
|
||||||
|
"WARNING:\n\nThis will attempt to repair your relay list based on other information we have. You may lose any relays you have added manually. Only proceed if you have lost your relay list beyond recoverability or if you are ok with losing any manually added relays." : {
|
||||||
|
"comment" : "Alert for repairing the user's relay list."
|
||||||
|
},
|
||||||
|
"WARNING:\n\nThis will reset your contact list, including the list of everyone you follow and potentially the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY." : {
|
||||||
"comment" : "Alert for resetting the user's contact list."
|
"comment" : "Alert for resetting the user's contact list."
|
||||||
},
|
},
|
||||||
"WARNING:\n\nTHIS WILL SIGN AN EVENT THAT DELETES THIS ACCOUNT.\n\nYOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.\n\n ARE YOU SURE YOU WANT TO CONTINUE?" : {
|
"WARNING:\n\nTHIS WILL SIGN AN EVENT THAT DELETES THIS ACCOUNT.\n\nYOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.\n\n ARE YOU SURE YOU WANT TO CONTINUE?" : {
|
||||||
@@ -1830,6 +2047,9 @@
|
|||||||
"you" : {
|
"you" : {
|
||||||
"comment" : "You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself."
|
"comment" : "You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself."
|
||||||
},
|
},
|
||||||
|
"You are not authorized to perform this action with your wallet." : {
|
||||||
|
"comment" : "Error description for unauthorized access"
|
||||||
|
},
|
||||||
"You cannot share content because you are not logged in. Please close this view, log in to your account, and try again." : {
|
"You cannot share content because you are not logged in. Please close this view, log in to your account, and try again." : {
|
||||||
"comment" : "Label explaining that sharing cannot proceed because the user is not logged in."
|
"comment" : "Label explaining that sharing cannot proceed because the user is not logged in."
|
||||||
},
|
},
|
||||||
@@ -1839,18 +2059,27 @@
|
|||||||
"You clicked on a Purple welcome link, but we could not find your checkout. This is likely a bug." : {
|
"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"
|
"comment" : "Error label upon continuing in the app from a Damus Purple purchase"
|
||||||
},
|
},
|
||||||
|
"You do not have permission to alter this relay list." : {
|
||||||
|
"comment" : "Human readable error description"
|
||||||
|
},
|
||||||
"You drive the conversation and we want to make it easier for people to support your work beyond follows, reposts, and likes." : {
|
"You drive the conversation and we want to make it easier for people to support your work beyond follows, reposts, and likes." : {
|
||||||
"comment" : "Text explaining the benefit of connecting a lightning wallet for content creators."
|
"comment" : "Text explaining the benefit of connecting a lightning wallet for content creators."
|
||||||
},
|
},
|
||||||
"You have no bookmarks yet, add them in the context menu" : {
|
"You have no bookmarks yet, add them in the context menu" : {
|
||||||
"comment" : "Text indicating that there are no bookmarks to be viewed"
|
"comment" : "Text indicating that there are no bookmarks to be viewed"
|
||||||
},
|
},
|
||||||
|
"You must be logged in with your nsec to use this option." : {
|
||||||
|
"comment" : "Warning text for users who cannot create a Coinos account via the one-click setup without being logged in with their nsec."
|
||||||
|
},
|
||||||
"You opened an invalid link. The link you tried to open refers to \"nrelay\", which has been deprecated and is not supported." : {
|
"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."
|
"comment" : "User-visible error description for a user who tries to open a deprecated \"nrelay\" link."
|
||||||
},
|
},
|
||||||
"You unlocked" : {
|
"You unlocked" : {
|
||||||
"comment" : "Part 1 of 2 in message 'You unlocked automatic translations' the user gets when they sign up for Damus Purple"
|
"comment" : "Part 1 of 2 in message 'You unlocked automatic translations' the user gets when they sign up for Damus Purple"
|
||||||
},
|
},
|
||||||
|
"Your connected wallet raised an unknown error. Message: %s" : {
|
||||||
|
"comment" : "Human readable error description for unknown error"
|
||||||
|
},
|
||||||
"Your content is being broadcasted to the network. Please wait." : {
|
"Your content is being broadcasted to the network. Please wait." : {
|
||||||
"comment" : "Label explaining that their content sharing action is in progress"
|
"comment" : "Label explaining that their content sharing action is in progress"
|
||||||
},
|
},
|
||||||
@@ -1860,6 +2089,9 @@
|
|||||||
"Your Name" : {
|
"Your Name" : {
|
||||||
"comment" : "Label for Your Name section of user profile form."
|
"comment" : "Label for Your Name section of user profile form."
|
||||||
},
|
},
|
||||||
|
"Your profile will not be shared with Coinos." : {
|
||||||
|
"comment" : "Label text for users to reassure them that their nsec is not shared with a third party."
|
||||||
|
},
|
||||||
"Your Purple subscription expires in %@ days. Renew?" : {
|
"Your Purple subscription expires in %@ days. Renew?" : {
|
||||||
"comment" : "A notification message explaining to the user that their Damus Purple Subscription is expiring soon, prompting them to renew."
|
"comment" : "A notification message explaining to the user that their Damus Purple Subscription is expiring soon, prompting them to renew."
|
||||||
},
|
},
|
||||||
@@ -1869,9 +2101,24 @@
|
|||||||
"Your Purple subscription has expired. Renew?" : {
|
"Your Purple subscription has expired. Renew?" : {
|
||||||
"comment" : "A notification message explaining to the user that their Damus Purple Subscription has expired, prompting them to renew."
|
"comment" : "A notification message explaining to the user that their Damus Purple Subscription has expired, prompting them to renew."
|
||||||
},
|
},
|
||||||
|
"Your relay list appears to be broken, so we cannot connect you to your Nostr network." : {
|
||||||
|
"comment" : "Human readable error description for a failure to parse the relay list due to a bad relay list"
|
||||||
|
},
|
||||||
"Your report will be sent to the relays you are connected to" : {
|
"Your report will be sent to the relays you are connected to" : {
|
||||||
"comment" : "Footer text to inform user what will happen when the report is submitted."
|
"comment" : "Footer text to inform user what will happen when the report is submitted."
|
||||||
},
|
},
|
||||||
|
"Your transaction quota has been exceeded." : {
|
||||||
|
"comment" : "Error description for quota exceeded"
|
||||||
|
},
|
||||||
|
"Your trusted network is comprised of profiles you follow and profiles that they follow." : {
|
||||||
|
"comment" : "Description of the tip that informs users what trusted network means."
|
||||||
|
},
|
||||||
|
"Your wallet does not have sufficient balance for this transaction." : {
|
||||||
|
"comment" : "Error description for insufficient balance"
|
||||||
|
},
|
||||||
|
"Your wallet is temporarily being rate limited." : {
|
||||||
|
"comment" : "Error description for rate limit error"
|
||||||
|
},
|
||||||
"Zap" : {
|
"Zap" : {
|
||||||
"comment" : "Accessibility label for zap button\nButton label that allows the user to zap (i.e. send a Bitcoin tip via the lightning network) the user shown on-screen\nText underneath the number of sats indicating that it's the amount used for zaps.\nTitle of notification when a non-private zap is received."
|
"comment" : "Accessibility label for zap button\nButton label that allows the user to zap (i.e. send a Bitcoin tip via the lightning network) the user shown on-screen\nText underneath the number of sats indicating that it's the amount used for zaps.\nTitle of notification when a non-private zap is received."
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
@@ -2,6 +2,22 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>follow_pack_user_count</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@FOLLOW_PACK_USERS@</string>
|
||||||
|
<key>FOLLOW_PACK_USERS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>user</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>users</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>followed_by_three_and_others</key>
|
<key>followed_by_three_and_others</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -50,6 +66,22 @@
|
|||||||
<string>Following</string>
|
<string>Following</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>hellthread_notifications_disabled</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@HELLTHREAD_PROFILES@</string>
|
||||||
|
<key>HELLTHREAD_PROFILES</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>Hide notifications that tag more than %d profile</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>Hide notifications that tag more than %d profiles</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>imports_count</key>
|
<key>imports_count</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -66,6 +98,22 @@
|
|||||||
<string>Imports</string>
|
<string>Imports</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>notes_from_three_and_others</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@OTHERS@</string>
|
||||||
|
<key>OTHERS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>Notes from %2$@, %3$@, %4$@ & %1$d other in your trusted network</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>Notes from %2$@, %3$@, %4$@ & %1$d others in your trusted network</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>people_reposted_count</key>
|
<key>people_reposted_count</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -82,6 +130,22 @@
|
|||||||
<string>%2$@ and %1$d others reposted</string>
|
<string>%2$@ and %1$d others reposted</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>quoted_reposts_count</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@QUOTE_REPOSTS@</string>
|
||||||
|
<key>QUOTE_REPOSTS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>Quote</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>Quotes</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>reacted_tagged_in_3</key>
|
<key>reacted_tagged_in_3</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -242,22 +306,6 @@
|
|||||||
<string>Reposts</string>
|
<string>Reposts</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>quoted_reposts_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@QUOTE_REPOSTS@</string>
|
|
||||||
<key>QUOTE_REPOSTS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Quote</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Quotes</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>sats</key>
|
<key>sats</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
"project" : "damus.xcodeproj",
|
"project" : "damus.xcodeproj",
|
||||||
"targetLocale" : "en-US",
|
"targetLocale" : "en-US",
|
||||||
"toolInfo" : {
|
"toolInfo" : {
|
||||||
"toolBuildNumber" : "16E140",
|
"toolBuildNumber" : "16F6",
|
||||||
"toolID" : "com.apple.dt.xcode",
|
"toolID" : "com.apple.dt.xcode",
|
||||||
"toolName" : "Xcode",
|
"toolName" : "Xcode",
|
||||||
"toolVersion" : "16.3"
|
"toolVersion" : "16.4"
|
||||||
},
|
},
|
||||||
"version" : "1.0"
|
"version" : "1.0"
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -44,6 +44,20 @@
|
|||||||
<string>フォロー中</string>
|
<string>フォロー中</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>hellthread_notifications_disabled</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@HELLTHREAD_PROFILES@</string>
|
||||||
|
<key>HELLTHREAD_PROFILES</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>%d以上のプロフィールをタグづけしている通知を表示しない</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>imports_count</key>
|
<key>imports_count</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -72,6 +86,20 @@
|
|||||||
<string>%2$@と他%1$d人がリポストしました</string>
|
<string>%2$@と他%1$d人がリポストしました</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>quoted_reposts_count</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@QUOTE_REPOSTS@</string>
|
||||||
|
<key>QUOTE_REPOSTS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>引用</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>reacted_tagged_in_3</key>
|
<key>reacted_tagged_in_3</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -212,20 +240,6 @@
|
|||||||
<string>リポスト</string>
|
<string>リポスト</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>quoted_reposts_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@QUOTE_REPOSTS@</string>
|
|
||||||
<key>QUOTE_REPOSTS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>引用</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>sats</key>
|
<key>sats</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
|||||||
Binary file not shown.
@@ -2,6 +2,22 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>follow_pack_user_count</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@FOLLOW_PACK_USERS@</string>
|
||||||
|
<key>FOLLOW_PACK_USERS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>gebruiker</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>gebruikers</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>followed_by_three_and_others</key>
|
<key>followed_by_three_and_others</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -50,6 +66,22 @@
|
|||||||
<string>Volgend</string>
|
<string>Volgend</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>hellthread_notifications_disabled</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@HELLTHREAD_PROFILES@</string>
|
||||||
|
<key>HELLTHREAD_PROFILES</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>Onderdruk meldingen die meer dan %d profiel vermelden</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>Onderdruk meldingen die meer dan %d profielen vermelden</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>imports_count</key>
|
<key>imports_count</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -66,6 +98,22 @@
|
|||||||
<string>Importeringen</string>
|
<string>Importeringen</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>notes_from_three_and_others</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@OTHERS@</string>
|
||||||
|
<key>OTHERS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>Notities van %2$@, %3$@ en %4$@; %1$d ander in je netwerk</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>Notities van %2$@, %3$@ en %4$@; %1$d anderen in je netwerk</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>people_reposted_count</key>
|
<key>people_reposted_count</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -82,6 +130,22 @@
|
|||||||
<string>%2$@ en %1$d anderen hebben herplaatst</string>
|
<string>%2$@ en %1$d anderen hebben herplaatst</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>quoted_reposts_count</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@QUOTE_REPOSTS@</string>
|
||||||
|
<key>QUOTE_REPOSTS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>Citaat</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>Citaten</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>reacted_tagged_in_3</key>
|
<key>reacted_tagged_in_3</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
@@ -242,22 +306,6 @@
|
|||||||
<string>Herplaatsingen</string>
|
<string>Herplaatsingen</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>quoted_reposts_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@QUOTE_REPOSTS@</string>
|
|
||||||
<key>QUOTE_REPOSTS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Citaat</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Citaten</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>sats</key>
|
<key>sats</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
|||||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user