Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
8678ca7144
|
|||
| 422167f7aa | |||
| de84456a57 | |||
| b70bf1f647 | |||
| 3db77a16a0 | |||
| 2256e2e625 | |||
| a641f972ff | |||
| c5846008f2 | |||
| 62bf767be5 | |||
| c00746758e | |||
| a89f90d7ee | |||
| 07d0818ee8 | |||
| 0ce7414488 | |||
| f090596067 | |||
| 61b3ad2990 | |||
| 8b24befaf7 | |||
| 57789de5cd | |||
| 62c539afbf | |||
| b53e6db96b | |||
| b0d6d33573 | |||
| c5b0e539d8 | |||
| 601fa49a6e | |||
| 216029410b | |||
| a5b2a5c8b9 | |||
| 980394bf0b | |||
| ed73899e5b | |||
| d0eb86dfa3 | |||
| 337c4de337 | |||
| e885f38c54 | |||
| 3dbdc42d8b | |||
| 1389e50b8e | |||
| 092d84f499 | |||
| c6a226fff8 | |||
| e023d1e9cb | |||
| e6b8e39106 | |||
| ced028755c | |||
| dabf737654 | |||
| 72d141af61 | |||
| 4d43e590e0 | |||
| c413589582 | |||
| c218e0dcdd | |||
| 892765eaa5 | |||
| 455f1f7e1f | |||
| 797762e7d2 | |||
| efe6689bfb | |||
| e30541c37e | |||
| b126257d05 | |||
| fde21559c7 | |||
| d551b5f28b | |||
| 961ff6f28b | |||
| fdfd0f0275 | |||
| 8b1b597f2a | |||
| f3de41ff08 | |||
| bf010be27a | |||
| b013c1f1fd | |||
| 50dfa9e2ed | |||
| 78450792cb | |||
| 4dc2571177 | |||
| 04493b53dc | |||
| f383388f42 | |||
| d4cdc7706d | |||
|
b70406d669
|
|||
| a2866ff6b3 | |||
| 1f0e31faa0 | |||
| 2ff12cdfa6 | |||
| b7b7d65612 | |||
| d205be3e0a | |||
| 0bea81c632 | |||
| e4842cca3c | |||
| 87d4752aa4 | |||
| 6ec533b0cd | |||
| 51a58360f9 | |||
| fe025532e8 | |||
| 6eb548a0a9 | |||
| bcaa1d2354 | |||
| 296d96d6df | |||
| 28854fdc93 | |||
| 2901cc860f | |||
| 3db13ae171 | |||
| 49dedaec04 | |||
| 491d4c4d25 | |||
| afcbaea331 | |||
| 340e134046 | |||
| e68952fa0c | |||
| 83af4ddd89 | |||
| 53262afa01 | |||
| 95148e9c9c | |||
| 5bf4a2cc5a | |||
|
2d29403145
|
|||
| df20b67fc1 | |||
| 0b5d68c0b8 | |||
| f4024895ba | |||
| bcdd0b4e23 | |||
| 1c655d47b2 | |||
| 24cc361d60 | |||
| 71bb9d6c92 | |||
| 271e3ad54a | |||
| dac21a1562 | |||
| ae9ae66b39 | |||
| e7281fdacc | |||
| baa5454e2a | |||
| 60a892d73b | |||
| 0ee360f2fa | |||
| c59d2a96af | |||
| ba3a6b07b2 | |||
| 043eb5b436 | |||
| 8f237b47eb | |||
| a0caf9ce07 | |||
| 3277aac220 | |||
| e67dac13c6 | |||
| 5f2c8223bd | |||
| 14977fe3dd | |||
| 1d3c181b85 |
@@ -1,3 +1,21 @@
|
||||
## [1.5-5] - 2023-06-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Remove note zaps to fit apples appstore guidelines
|
||||
- Fix zap sheet popping (William Casarin)
|
||||
- Fix CustomizeZapView from randomly disappearing (William Casarin)
|
||||
- Fix "zapped your profile" strings to say "zapped you" (Terry Yiu)
|
||||
- Fix reconnect loop issues on iOS17 (William Casarin)
|
||||
- Fix some more thread jankiness (William Casarin)
|
||||
- Fix spelling of Nostr to use Titlecase instead of lowercase (Terry Yiu)
|
||||
- Rename all usages of the term Post as a noun to Note to conform to the Nostr spec (Terry Yiu)
|
||||
- Fix text cutoff on login with npub (gladiusKatana)
|
||||
- Fix hangs due to video player (William Casarin)
|
||||
|
||||
|
||||
[1.5-5]: https://github.com/damus-io/damus/releases/tag/v1.5-5
|
||||
|
||||
## [1.5-2] - 2023-05-30
|
||||
|
||||
### Added
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F229A91366008A0F29 /* ProfileViewTests.swift */; };
|
||||
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */; };
|
||||
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
|
||||
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */; };
|
||||
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; };
|
||||
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
|
||||
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FC297E3CFF0090C62D /* RepostsModel.swift */; };
|
||||
@@ -131,6 +132,7 @@
|
||||
4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */; };
|
||||
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */; };
|
||||
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */ = {isa = PBXBuildFile; productRef = 4C649880286E0EE300EAE2B3 /* secp256k1 */; };
|
||||
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C5132A4437C10062CAC0 /* ZapUserView.swift */; };
|
||||
4C75EFA427FA577B0006080F /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA327FA577B0006080F /* PostView.swift */; };
|
||||
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA527FF87A20006080F /* Nostr.swift */; };
|
||||
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFAC28049CFB0006080F /* PostButton.swift */; };
|
||||
@@ -169,6 +171,8 @@
|
||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
||||
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
|
||||
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
|
||||
4C9AA1482A44442E003F49FD /* CustomizeZapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */; };
|
||||
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */; };
|
||||
4C9BB83129C0ED4F00FC4E37 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
|
||||
4C9BB83429C12D9900FC4E37 /* EventProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */; };
|
||||
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; };
|
||||
@@ -196,6 +200,7 @@
|
||||
4CB883AE2976FA9300DC99E7 /* FormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883AD2976FA9300DC99E7 /* FormatTests.swift */; };
|
||||
4CB883B0297705DD00DC99E7 /* ZapButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883AF297705DD00DC99E7 /* ZapButton.swift */; };
|
||||
4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB883B5297730E400DC99E7 /* LNUrls.swift */; };
|
||||
4CB8FC232A41ABA800763C51 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8FC222A41ABA500763C51 /* AboutView.swift */; };
|
||||
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */; };
|
||||
4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */; };
|
||||
4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */; };
|
||||
@@ -239,7 +244,7 @@
|
||||
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794729941DA700F758CC /* RelayFilters.swift */; };
|
||||
4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */; };
|
||||
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794D2996B16A00F758CC /* RelayToggle.swift */; };
|
||||
4CE879502996B2BD00F758CC /* RelayStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794F2996B2BD00F758CC /* RelayStatus.swift */; };
|
||||
4CE879502996B2BD00F758CC /* RelayStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794F2996B2BD00F758CC /* RelayStatusView.swift */; };
|
||||
4CE879522996B68900F758CC /* RelayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879512996B68900F758CC /* RelayType.swift */; };
|
||||
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879542996BAB900F758CC /* RelayPaidDetail.swift */; };
|
||||
4CE879582996C45300F758CC /* ZapsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879572996C45300F758CC /* ZapsView.swift */; };
|
||||
@@ -270,12 +275,12 @@
|
||||
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; };
|
||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
||||
50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50088DA029E8271A008A1FDF /* WebSocket.swift */; };
|
||||
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; };
|
||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
|
||||
5019CADD2A0FB0A9000069E1 /* ProfileDatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5019CADC2A0FB0A9000069E1 /* ProfileDatabaseTests.swift */; };
|
||||
501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */; };
|
||||
501F8C5829FF5FC5001AFC1D /* Damus.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5629FF5FC5001AFC1D /* Damus.xcdatamodeld */; };
|
||||
501F8C5A29FF70F5001AFC1D /* ProfileDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */; };
|
||||
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; };
|
||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
|
||||
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
|
||||
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; };
|
||||
50DA11262A16A23F00236234 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50DA11252A16A23F00236234 /* Launch.storyboard */; };
|
||||
@@ -365,6 +370,7 @@
|
||||
3A41E559299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CondensedProfilePicturesView.swift; sourceTree = "<group>"; };
|
||||
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -567,6 +573,7 @@
|
||||
4C633351283D419F00B1C9C3 /* SignalModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalModel.swift; sourceTree = "<group>"; };
|
||||
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesView.swift; sourceTree = "<group>"; };
|
||||
4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesModel.swift; sourceTree = "<group>"; };
|
||||
4C73C5132A4437C10062CAC0 /* ZapUserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapUserView.swift; sourceTree = "<group>"; };
|
||||
4C75EFA327FA577B0006080F /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
|
||||
4C75EFA527FF87A20006080F /* Nostr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nostr.swift; sourceTree = "<group>"; };
|
||||
4C75EFA72804823E0006080F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -610,6 +617,8 @@
|
||||
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
|
||||
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
|
||||
4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = "<group>"; };
|
||||
4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapModel.swift; sourceTree = "<group>"; };
|
||||
4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusModel.swift; sourceTree = "<group>"; };
|
||||
4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayName.swift; sourceTree = "<group>"; };
|
||||
4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfileName.swift; sourceTree = "<group>"; };
|
||||
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapView.swift; sourceTree = "<group>"; };
|
||||
@@ -637,6 +646,7 @@
|
||||
4CB883AD2976FA9300DC99E7 /* FormatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormatTests.swift; sourceTree = "<group>"; };
|
||||
4CB883AF297705DD00DC99E7 /* ZapButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapButton.swift; sourceTree = "<group>"; };
|
||||
4CB883B5297730E400DC99E7 /* LNUrls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LNUrls.swift; sourceTree = "<group>"; };
|
||||
4CB8FC222A41ABA500763C51 /* AboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNameView.swift; sourceTree = "<group>"; };
|
||||
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowsYou.swift; sourceTree = "<group>"; };
|
||||
4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteLink.swift; sourceTree = "<group>"; };
|
||||
@@ -682,7 +692,7 @@
|
||||
4CE8794729941DA700F758CC /* RelayFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilters.swift; sourceTree = "<group>"; };
|
||||
4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayMetadatas.swift; sourceTree = "<group>"; };
|
||||
4CE8794D2996B16A00F758CC /* RelayToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayToggle.swift; sourceTree = "<group>"; };
|
||||
4CE8794F2996B2BD00F758CC /* RelayStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayStatus.swift; sourceTree = "<group>"; };
|
||||
4CE8794F2996B2BD00F758CC /* RelayStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayStatusView.swift; sourceTree = "<group>"; };
|
||||
4CE879512996B68900F758CC /* RelayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayType.swift; sourceTree = "<group>"; };
|
||||
4CE879542996BAB900F758CC /* RelayPaidDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPaidDetail.swift; sourceTree = "<group>"; };
|
||||
4CE879572996C45300F758CC /* ZapsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapsView.swift; sourceTree = "<group>"; };
|
||||
@@ -714,12 +724,12 @@
|
||||
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; };
|
||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
||||
50088DA029E8271A008A1FDF /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; };
|
||||
501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; };
|
||||
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
|
||||
5019CADC2A0FB0A9000069E1 /* ProfileDatabaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDatabaseTests.swift; sourceTree = "<group>"; };
|
||||
501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistedProfile.swift; sourceTree = "<group>"; };
|
||||
501F8C5729FF5FC5001AFC1D /* Damus.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Damus.xcdatamodel; sourceTree = "<group>"; };
|
||||
501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDatabase.swift; sourceTree = "<group>"; };
|
||||
501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; };
|
||||
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
|
||||
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
|
||||
50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; };
|
||||
50DA11252A16A23F00236234 /* Launch.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Launch.storyboard; sourceTree = "<group>"; };
|
||||
@@ -866,6 +876,7 @@
|
||||
4C0A3F8D280F63FF000448DE /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C9AA1462A444422003F49FD /* Zaps */,
|
||||
4C54AA0829A55416003E4487 /* Notifications */,
|
||||
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */,
|
||||
4C0A3F8E280F640A000448DE /* ThreadModel.swift */,
|
||||
@@ -969,6 +980,7 @@
|
||||
children = (
|
||||
4C54AA0929A55429003E4487 /* EventGroup.swift */,
|
||||
4C54AA0B29A5543C003E4487 /* ZapGroup.swift */,
|
||||
4C9AA1492A4587A6003F49FD /* NotificationStatusModel.swift */,
|
||||
);
|
||||
path = Notifications;
|
||||
sourceTree = "<group>";
|
||||
@@ -1156,6 +1168,14 @@
|
||||
path = Buttons;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C9AA1462A444422003F49FD /* Zaps */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C9AA1472A44442E003F49FD /* CustomizeZapModel.swift */,
|
||||
);
|
||||
path = Zaps;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CAAD8AE29888A9B00060CEA /* Relays */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1165,7 +1185,7 @@
|
||||
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */,
|
||||
F7908E91298B0F0700AB113A /* RelayDetailView.swift */,
|
||||
4CE8794D2996B16A00F758CC /* RelayToggle.swift */,
|
||||
4CE8794F2996B2BD00F758CC /* RelayStatus.swift */,
|
||||
4CE8794F2996B2BD00F758CC /* RelayStatusView.swift */,
|
||||
4CE879512996B68900F758CC /* RelayType.swift */,
|
||||
4CDA128929E9D10C0006FA5A /* SignalView.swift */,
|
||||
);
|
||||
@@ -1196,6 +1216,7 @@
|
||||
4CB9D4A52992D01900A9A7E4 /* Profile */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CB8FC222A41ABA500763C51 /* AboutView.swift */,
|
||||
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
|
||||
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
|
||||
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */,
|
||||
@@ -1207,6 +1228,7 @@
|
||||
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */,
|
||||
4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */,
|
||||
4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */,
|
||||
3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */,
|
||||
);
|
||||
path = Profile;
|
||||
sourceTree = "<group>";
|
||||
@@ -1389,6 +1411,7 @@
|
||||
4CE879572996C45300F758CC /* ZapsView.swift */,
|
||||
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */,
|
||||
4CA3FA0F29F593D000FDB3C3 /* ZapTypePicker.swift */,
|
||||
4C73C5132A4437C10062CAC0 /* ZapUserView.swift */,
|
||||
);
|
||||
path = Zaps;
|
||||
sourceTree = "<group>";
|
||||
@@ -1758,6 +1781,7 @@
|
||||
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
||||
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
|
||||
4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */,
|
||||
4C9AA1482A44442E003F49FD /* CustomizeZapModel.swift in Sources */,
|
||||
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
|
||||
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */,
|
||||
4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */,
|
||||
@@ -1836,6 +1860,7 @@
|
||||
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
|
||||
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
|
||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */,
|
||||
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
|
||||
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
||||
4CE1399429F0669900AC6A0B /* BigButton.swift in Sources */,
|
||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
||||
@@ -1883,6 +1908,7 @@
|
||||
4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */,
|
||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */,
|
||||
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */,
|
||||
4CB8FC232A41ABA800763C51 /* AboutView.swift in Sources */,
|
||||
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */,
|
||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
|
||||
F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */,
|
||||
@@ -1897,11 +1923,12 @@
|
||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */,
|
||||
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */,
|
||||
3AE45AF6297BB2E700C1D842 /* LibreTranslateServer.swift in Sources */,
|
||||
4CE879502996B2BD00F758CC /* RelayStatus.swift in Sources */,
|
||||
4CE879502996B2BD00F758CC /* RelayStatusView.swift in Sources */,
|
||||
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */,
|
||||
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
||||
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
|
||||
4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */,
|
||||
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */,
|
||||
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */,
|
||||
4C1A9A1D29DDCF9B00516EAC /* NotificationSettingsView.swift in Sources */,
|
||||
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
||||
@@ -1913,6 +1940,7 @@
|
||||
4C1A9A1F29DDD24B00516EAC /* AppearanceSettingsView.swift in Sources */,
|
||||
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */,
|
||||
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */,
|
||||
4C9AA14A2A4587A6003F49FD /* NotificationStatusModel.swift in Sources */,
|
||||
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
|
||||
4CE4F0F429D779B5005914DB /* PostBox.swift in Sources */,
|
||||
4C2859622A12A7F0004746F7 /* GoldSupportGradient.swift in Sources */,
|
||||
@@ -2208,7 +2236,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -2256,7 +2284,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
||||
@@ -24,9 +24,11 @@ struct GradientButtonStyle: ButtonStyle {
|
||||
struct GradientButtonStyle_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
Button("Dynamic Size", action: {
|
||||
Button(action: {
|
||||
print("dynamic size")
|
||||
})
|
||||
}) {
|
||||
Text(verbatim: "Dynamic Size")
|
||||
}
|
||||
.buttonStyle(GradientButtonStyle())
|
||||
|
||||
|
||||
@@ -34,7 +36,7 @@ struct GradientButtonStyle_Previews: PreviewProvider {
|
||||
print("infinite width")
|
||||
}) {
|
||||
HStack {
|
||||
Text("Infinite Width")
|
||||
Text(verbatim: "Infinite Width")
|
||||
}
|
||||
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ enum ImageShape {
|
||||
}
|
||||
|
||||
// MARK: - Image Carousel
|
||||
@MainActor
|
||||
struct ImageCarousel: View {
|
||||
var urls: [MediaUrl]
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ struct InvoiceView: View {
|
||||
var PayButton: some View {
|
||||
Button {
|
||||
if settings.show_wallet_selector {
|
||||
showing_select_wallet = true
|
||||
present_sheet(.select_wallet(invoice: invoice.string))
|
||||
} else {
|
||||
open_with_wallet(wallet: settings.default_wallet.model, invoice: invoice.string)
|
||||
}
|
||||
@@ -79,9 +79,6 @@ struct InvoiceView: View {
|
||||
}
|
||||
.padding(30)
|
||||
}
|
||||
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
|
||||
SelectWalletView(default_wallet: settings.default_wallet, showingSelectWallet: $showing_select_wallet, our_pubkey: our_pubkey, invoice: invoice.string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,3 +113,7 @@ struct InvoiceView_Previews: PreviewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func present_sheet(_ sheet: Sheets) {
|
||||
notify(.present_sheet, sheet)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ struct Reposted: View {
|
||||
.foregroundColor(Color.gray)
|
||||
ProfileName(pubkey: pubkey, profile: profile, damus: damus, show_nip5_domain: false)
|
||||
.foregroundColor(Color.gray)
|
||||
Text("Reposted", comment: "Text indicating that the post was reposted (i.e. re-shared).")
|
||||
Text("Reposted", comment: "Text indicating that the note was reposted (i.e. re-shared).")
|
||||
.foregroundColor(Color.gray)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,17 @@ struct UserViewRow: View {
|
||||
struct UserView: View {
|
||||
let damus_state: DamusState
|
||||
let pubkey: String
|
||||
let spacer: Bool
|
||||
|
||||
@State var about_text: Text? = nil
|
||||
|
||||
init(damus_state: DamusState, pubkey: String, spacer: Bool = true) {
|
||||
self.damus_state = damus_state
|
||||
self.pubkey = pubkey
|
||||
self.spacer = spacer
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||
@@ -42,16 +50,16 @@ struct UserView: View {
|
||||
VStack(alignment: .leading) {
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_nip5_domain: false)
|
||||
if let about = profile?.about {
|
||||
let blocks = parse_mentions(content: about, tags: [])
|
||||
let about_string = render_blocks(blocks: blocks, profiles: damus_state.profiles).content.attributed
|
||||
Text(about_string)
|
||||
if let about_text {
|
||||
about_text
|
||||
.lineLimit(3)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
if spacer {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +32,16 @@ struct ZapButton: View {
|
||||
let lnurl: String
|
||||
|
||||
@ObservedObject var zaps: ZapsDataModel
|
||||
@StateObject var button: ZapButtonModel = ZapButtonModel()
|
||||
|
||||
var our_zap: Zapping? {
|
||||
zaps.zaps.first(where: { z in z.request.pubkey == damus_state.pubkey })
|
||||
zaps.zaps.first(where: { z in z.request.ev.pubkey == damus_state.pubkey })
|
||||
}
|
||||
|
||||
var zap_img: String {
|
||||
if damus_state.settings.nozaps {
|
||||
return "zap"
|
||||
}
|
||||
|
||||
switch our_zap {
|
||||
case .none:
|
||||
return "zap"
|
||||
@@ -50,19 +53,16 @@ struct ZapButton: View {
|
||||
}
|
||||
|
||||
var zap_color: Color {
|
||||
if damus_state.settings.nozaps {
|
||||
return Color.gray
|
||||
}
|
||||
|
||||
if our_zap == nil {
|
||||
return Color.gray
|
||||
}
|
||||
|
||||
// always orange !
|
||||
return Color.orange
|
||||
/*
|
||||
if our_zap.is_paid {
|
||||
return Color.orange
|
||||
} else {
|
||||
return Color.yellow
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func tap() {
|
||||
@@ -124,7 +124,7 @@ struct ZapButton: View {
|
||||
.frame(width:20, height: 20)
|
||||
})
|
||||
|
||||
if zaps.zap_total > 0 {
|
||||
if !damus_state.settings.nozaps && zaps.zap_total > 0 {
|
||||
Text(verbatim: format_msats_abbrev(zaps.zap_total))
|
||||
.font(.footnote)
|
||||
.foregroundColor(zap_color)
|
||||
@@ -132,43 +132,22 @@ struct ZapButton: View {
|
||||
}
|
||||
.accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button"))
|
||||
.simultaneousGesture(LongPressGesture().onEnded {_ in
|
||||
button.showing_zap_customizer = true
|
||||
// when we don't have nozaps mode enable, long press shows the zap customizer
|
||||
if !damus_state.settings.nozaps {
|
||||
present_sheet(.zap(target: target, lnurl: lnurl))
|
||||
}
|
||||
|
||||
// long press does nothing in nozaps mode
|
||||
})
|
||||
.highPriorityGesture(TapGesture().onEnded {
|
||||
tap()
|
||||
// when we have appstore mode on, only show the zap customizer as "user zaps"
|
||||
if damus_state.settings.nozaps {
|
||||
present_sheet(.zap(target: target, lnurl: lnurl))
|
||||
} else {
|
||||
// otherwise we restore the original behavior of one-tap zaps
|
||||
tap()
|
||||
}
|
||||
})
|
||||
.sheet(isPresented: $button.showing_zap_customizer) {
|
||||
CustomizeZapView(state: damus_state, target: target, lnurl: lnurl)
|
||||
}
|
||||
.sheet(isPresented: $button.showing_select_wallet, onDismiss: {button.showing_select_wallet = false}) {
|
||||
SelectWalletView(default_wallet: damus_state.settings.default_wallet, showingSelectWallet: $button.showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: button.invoice ?? "")
|
||||
}
|
||||
.onReceive(handle_notify(.zapping)) { notif in
|
||||
let zap_ev = notif.object as! ZappingEvent
|
||||
|
||||
guard zap_ev.target.id == self.target.id else {
|
||||
return
|
||||
}
|
||||
|
||||
guard !zap_ev.is_custom else {
|
||||
return
|
||||
}
|
||||
|
||||
switch zap_ev.type {
|
||||
case .failed:
|
||||
break
|
||||
case .got_zap_invoice(let inv):
|
||||
if damus_state.settings.show_wallet_selector {
|
||||
self.button.invoice = inv
|
||||
self.button.showing_select_wallet = true
|
||||
} else {
|
||||
let wallet = damus_state.settings.default_wallet.model
|
||||
open_with_wallet(wallet: wallet, invoice: inv)
|
||||
}
|
||||
case .sent_from_nwc:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,17 +241,21 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
|
||||
}
|
||||
|
||||
var flusher: OnFlush? = nil
|
||||
// Don't donate on custom zaps
|
||||
if !is_custom && damus_state.settings.donation_percent > 0 {
|
||||
|
||||
// donations are only enabled on one-tap zaps and off appstore
|
||||
if !damus_state.settings.nozaps && !is_custom && damus_state.settings.donation_percent > 0 {
|
||||
flusher = .once({ pe in
|
||||
// send donation zap when the pending zap is flushed, this allows user to cancel and not send a donation
|
||||
Task.init { @MainActor in
|
||||
Task { @MainActor in
|
||||
await send_donation_zap(pool: damus_state.pool, postbox: damus_state.postbox, nwc: nwc_state.url, percent: damus_state.settings.donation_percent, base_msats: amount_msat)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let nwc_req = nwc_pay(url: nwc_state.url, pool: damus_state.pool, post: damus_state.postbox, invoice: inv, on_flush: flusher)
|
||||
// we don't have a delay on one-tap nozaps (since this will be from customize zap view)
|
||||
let delay = damus_state.settings.nozaps ? nil : 5.0
|
||||
|
||||
let nwc_req = nwc_pay(url: nwc_state.url, pool: damus_state.pool, post: damus_state.postbox, invoice: inv, delay: delay, on_flush: flusher)
|
||||
|
||||
guard let nwc_req, case .nwc(let pzap_state) = pending_zap_state else {
|
||||
print("nwc: failed to send nwc request for zapreq \(reqid.reqid)")
|
||||
|
||||
+123
-33
@@ -14,17 +14,38 @@ struct TimestampedProfile {
|
||||
let event: NostrEvent
|
||||
}
|
||||
|
||||
struct ZapSheet {
|
||||
let target: ZapTarget
|
||||
let lnurl: String
|
||||
}
|
||||
|
||||
struct SelectWallet {
|
||||
let invoice: String
|
||||
}
|
||||
|
||||
enum Sheets: Identifiable {
|
||||
case post(PostAction)
|
||||
case report(ReportTarget)
|
||||
case event(NostrEvent)
|
||||
case zap(ZapSheet)
|
||||
case select_wallet(SelectWallet)
|
||||
case filter
|
||||
|
||||
|
||||
static func zap(target: ZapTarget, lnurl: String) -> Sheets {
|
||||
return .zap(ZapSheet(target: target, lnurl: lnurl))
|
||||
}
|
||||
|
||||
static func select_wallet(invoice: String) -> Sheets {
|
||||
return .select_wallet(SelectWallet(invoice: invoice))
|
||||
}
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .report: return "report"
|
||||
case .post(let action): return "post-" + (action.ev?.id ?? "")
|
||||
case .event(let ev): return "event-" + ev.id
|
||||
case .zap(let sheet): return "zap-" + sheet.target.id
|
||||
case .select_wallet: return "select-wallet"
|
||||
case .filter: return "filter"
|
||||
}
|
||||
}
|
||||
@@ -75,7 +96,7 @@ struct ContentView: View {
|
||||
@State var confirm_overwrite_mutelist: Bool = false
|
||||
@SceneStorage("ContentView.filter_state") var filter_state : FilterState = .posts_and_replies
|
||||
@State private var isSideBarOpened = false
|
||||
@StateObject var home: HomeModel = HomeModel()
|
||||
var home: HomeModel = HomeModel()
|
||||
|
||||
let sub_id = UUID().description
|
||||
|
||||
@@ -115,8 +136,8 @@ struct ContentView: View {
|
||||
.safeAreaInset(edge: .top, spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
CustomPicker(selection: $filter_state, content: {
|
||||
Text("Posts", comment: "Label for filter for seeing only posts (instead of posts and replies).").tag(FilterState.posts)
|
||||
Text("Posts & Replies", comment: "Label for filter for seeing posts and replies (instead of only posts).").tag(FilterState.posts_and_replies)
|
||||
Text("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies).").tag(FilterState.posts)
|
||||
Text("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes).").tag(FilterState.posts_and_replies)
|
||||
})
|
||||
Divider()
|
||||
.frame(height: 1)
|
||||
@@ -128,7 +149,7 @@ struct ContentView: View {
|
||||
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
|
||||
ZStack {
|
||||
if let damus = self.damus_state {
|
||||
TimelineView(events: home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter)
|
||||
TimelineView(events: home.events, loading: .constant(false), damus: damus, show_friend_icon: false, filter: filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,13 +310,14 @@ struct ContentView: View {
|
||||
if selected_timeline == .search {
|
||||
Button(action: {
|
||||
//isFilterVisible.toggle()
|
||||
self.active_sheet = .filter
|
||||
present_sheet(.filter)
|
||||
}) {
|
||||
// checklist, checklist.checked, lisdt.bullet, list.bullet.circle, line.3.horizontal.decrease..., line.3.horizontail.decrease
|
||||
Label(NSLocalizedString("Filter", comment: "Button label text for filtering relay servers."), image: "filter")
|
||||
.foregroundColor(.gray)
|
||||
//.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,7 +330,7 @@ struct ContentView: View {
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
|
||||
TabBar(new_events: $home.new_events, selected: $selected_timeline, settings: damus.settings, action: switch_timeline)
|
||||
TabBar(nstatus: home.notification_status, selected: $selected_timeline, settings: damus.settings, action: switch_timeline)
|
||||
.padding([.bottom], 8)
|
||||
.background(Color(uiColor: .systemBackground).ignoresSafeArea())
|
||||
}
|
||||
@@ -327,6 +349,10 @@ struct ContentView: View {
|
||||
PostView(action: action, damus_state: damus_state!)
|
||||
case .event:
|
||||
EventDetailView()
|
||||
case .zap(let zapsheet):
|
||||
CustomizeZapView(state: damus_state!, target: zapsheet.target, lnurl: zapsheet.lnurl)
|
||||
case .select_wallet(let select):
|
||||
SelectWalletView(default_wallet: damus_state!.settings.default_wallet, active_sheet: $active_sheet, our_pubkey: damus_state!.pubkey, invoice: select.invoice)
|
||||
case .filter:
|
||||
let timeline = selected_timeline
|
||||
if #available(iOS 16.0, *) {
|
||||
@@ -434,6 +460,31 @@ struct ContentView: View {
|
||||
.onReceive(handle_notify(.unmute_thread)) { notif in
|
||||
home.filter_events()
|
||||
}
|
||||
.onReceive(handle_notify(.present_sheet)) { notif in
|
||||
let sheet = notif.object as! Sheets
|
||||
self.active_sheet = sheet
|
||||
}
|
||||
.onReceive(handle_notify(.zapping)) { notif in
|
||||
let zap_ev = notif.object as! ZappingEvent
|
||||
|
||||
guard !zap_ev.is_custom else {
|
||||
return
|
||||
}
|
||||
|
||||
switch zap_ev.type {
|
||||
case .failed:
|
||||
break
|
||||
case .got_zap_invoice(let inv):
|
||||
if damus_state!.settings.show_wallet_selector {
|
||||
present_sheet(.select_wallet(invoice: inv))
|
||||
} else {
|
||||
let wallet = damus_state!.settings.default_wallet.model
|
||||
open_with_wallet(wallet: wallet, invoice: inv)
|
||||
}
|
||||
case .sent_from_nwc:
|
||||
break
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { (phase: ScenePhase) in
|
||||
switch phase {
|
||||
case .background:
|
||||
@@ -746,24 +797,57 @@ func setup_notifications() {
|
||||
}
|
||||
}
|
||||
|
||||
func find_event(state: DamusState, evid: String, search_type: SearchType, find_from: [String]?, callback: @escaping (NostrEvent?) -> ()) {
|
||||
if let ev = state.events.lookup(evid) {
|
||||
callback(ev)
|
||||
return
|
||||
struct FindEvent {
|
||||
let type: FindEventType
|
||||
let find_from: [String]?
|
||||
|
||||
static func profile(pubkey: String, find_from: [String]? = nil) -> FindEvent {
|
||||
return FindEvent(type: .profile(pubkey), find_from: find_from)
|
||||
}
|
||||
|
||||
static func event(evid: String, find_from: [String]? = nil) -> FindEvent {
|
||||
return FindEvent(type: .event(evid), find_from: find_from)
|
||||
}
|
||||
}
|
||||
|
||||
enum FindEventType {
|
||||
case profile(String)
|
||||
case event(String)
|
||||
}
|
||||
|
||||
enum FoundEvent {
|
||||
case profile(Profile, NostrEvent)
|
||||
case invalid_profile(NostrEvent)
|
||||
case event(NostrEvent)
|
||||
}
|
||||
|
||||
func find_event(state: DamusState, query query_: FindEvent, callback: @escaping (FoundEvent?) -> ()) {
|
||||
|
||||
var filter: NostrFilter? = nil
|
||||
let find_from = query_.find_from
|
||||
let query = query_.type
|
||||
|
||||
switch query {
|
||||
case .profile(let pubkey):
|
||||
if let profile = state.profiles.lookup_with_timestamp(id: pubkey) {
|
||||
callback(.profile(profile.profile, profile.event))
|
||||
return
|
||||
}
|
||||
filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey])
|
||||
|
||||
case .event(let evid):
|
||||
if let ev = state.events.lookup(evid) {
|
||||
callback(.event(ev))
|
||||
return
|
||||
}
|
||||
|
||||
filter = NostrFilter(ids: [evid], limit: 1)
|
||||
}
|
||||
|
||||
let subid = UUID().description
|
||||
|
||||
var attempts: Int = 0
|
||||
var has_event = false
|
||||
|
||||
var filter = search_type == .event ? NostrFilter(ids: [evid]) : NostrFilter(authors: [evid])
|
||||
|
||||
if search_type == .profile {
|
||||
filter.kinds = [.metadata]
|
||||
}
|
||||
|
||||
filter.limit = 1
|
||||
var attempts = 0
|
||||
guard let filter else { return }
|
||||
|
||||
state.pool.subscribe_to(sub_id: subid, filters: [filter], to: find_from) { relay_id, res in
|
||||
guard case .nostr_event(let ev) = res else {
|
||||
@@ -779,15 +863,22 @@ func find_event(state: DamusState, evid: String, search_type: SearchType, find_f
|
||||
break
|
||||
case .event(_, let ev):
|
||||
has_event = true
|
||||
|
||||
state.pool.unsubscribe(sub_id: subid)
|
||||
|
||||
if search_type == .profile && ev.known_kind == .metadata {
|
||||
process_metadata_event(events: state.events, our_pubkey: state.pubkey, profiles: state.profiles, ev: ev) {
|
||||
callback(ev)
|
||||
switch query {
|
||||
case .profile:
|
||||
if ev.known_kind == .metadata {
|
||||
process_metadata_event(events: state.events, our_pubkey: state.pubkey, profiles: state.profiles, ev: ev) { profile in
|
||||
guard let profile else {
|
||||
callback(.invalid_profile(ev))
|
||||
return
|
||||
}
|
||||
callback(.profile(profile, ev))
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callback(ev)
|
||||
case .event:
|
||||
callback(.event(ev))
|
||||
}
|
||||
case .eose:
|
||||
if !has_event {
|
||||
@@ -810,11 +901,11 @@ func timeline_name(_ timeline: Timeline?) -> String {
|
||||
}
|
||||
switch timeline {
|
||||
case .home:
|
||||
return NSLocalizedString("Home", comment: "Navigation bar title for Home view where posts and replies appear from those who the user is following.")
|
||||
return NSLocalizedString("Home", comment: "Navigation bar title for Home view where notes and replies appear from those who the user is following.")
|
||||
case .notifications:
|
||||
return NSLocalizedString("Notifications", comment: "Toolbar label for Notifications view.")
|
||||
case .search:
|
||||
return NSLocalizedString("Universe 🛸", comment: "Toolbar label for the universal view where posts from all connected relay servers appear.")
|
||||
return NSLocalizedString("Universe 🛸", comment: "Toolbar label for the universal view where notes from all connected relay servers appear.")
|
||||
case .dms:
|
||||
return NSLocalizedString("DMs", comment: "Toolbar label for DMs view, where DM is the English abbreviation for Direct Message.")
|
||||
}
|
||||
@@ -912,10 +1003,9 @@ func on_open_url(state: DamusState, url: URL, result: @escaping (OpenResult?) ->
|
||||
if ref.key == "p" {
|
||||
result(.profile(ref.ref_id))
|
||||
} else if ref.key == "e" {
|
||||
find_event(state: state, evid: ref.ref_id, search_type: .event, find_from: nil) { ev in
|
||||
if let ev {
|
||||
result(.event(ev))
|
||||
}
|
||||
find_event(state: state, query: .event(evid: ref.ref_id)) { res in
|
||||
guard let res, case .event(let ev) = res else { return }
|
||||
result(.event(ev))
|
||||
}
|
||||
}
|
||||
case .filter(let filt):
|
||||
|
||||
@@ -20,7 +20,7 @@ class ActionBarModel: ObservableObject {
|
||||
@Published var our_zap: Zapping?
|
||||
@Published var likes: Int
|
||||
@Published var boosts: Int
|
||||
@Published var zaps: Int
|
||||
@Published private(set) var zaps: Int
|
||||
@Published var zap_total: Int64
|
||||
@Published var replies: Int
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import Foundation
|
||||
class Contacts {
|
||||
private var friends: Set<String> = Set()
|
||||
private var friend_of_friends: Set<String> = Set()
|
||||
/// Tracks which friends are friends of a given pubkey.
|
||||
private var pubkey_to_our_friends = [String : Set<String>]()
|
||||
private var muted: Set<String> = Set()
|
||||
|
||||
let our_pubkey: String
|
||||
@@ -58,6 +60,10 @@ class Contacts {
|
||||
|
||||
func remove_friend(_ pubkey: String) {
|
||||
friends.remove(pubkey)
|
||||
|
||||
pubkey_to_our_friends.forEach {
|
||||
pubkey_to_our_friends[$0.key]?.remove(pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
func get_friend_list() -> [String] {
|
||||
@@ -73,6 +79,15 @@ class Contacts {
|
||||
for tag in contact.tags {
|
||||
if tag.count >= 2 && tag[0] == "p" {
|
||||
friend_of_friends.insert(tag[1])
|
||||
|
||||
// Exclude themself and us.
|
||||
if contact.pubkey != our_pubkey && contact.pubkey != tag[1] {
|
||||
if pubkey_to_our_friends[tag[1]] == nil {
|
||||
pubkey_to_our_friends[tag[1]] = Set<String>()
|
||||
}
|
||||
|
||||
pubkey_to_our_friends[tag[1]]?.insert(contact.pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,6 +111,11 @@ class Contacts {
|
||||
func follow_state(_ pubkey: String) -> FollowState {
|
||||
return is_friend(pubkey) ? .follows : .unfollows
|
||||
}
|
||||
|
||||
/// Gets the list of pubkeys of our friends who follow the given pubkey.
|
||||
func get_friended_followers(_ pubkey: String) -> [String] {
|
||||
return Array((pubkey_to_our_friends[pubkey] ?? Set()))
|
||||
}
|
||||
}
|
||||
|
||||
func follow_user(pool: RelayPool, our_contacts: NostrEvent?, pubkey: String, privkey: String, follow: ReferencedId) -> NostrEvent? {
|
||||
|
||||
@@ -35,8 +35,17 @@ struct DamusState {
|
||||
func add_zap(zap: Zapping) -> Bool {
|
||||
// store generic zap mapping
|
||||
self.zaps.add_zap(zap: zap)
|
||||
let stored = self.events.store_zap(zap: zap)
|
||||
|
||||
// thread zaps
|
||||
if let ev = zap.event, !settings.nozaps, zap.is_in_thread {
|
||||
// [nozaps]: thread zaps are only available outside of the app store
|
||||
replies.count_replies(ev)
|
||||
events.add_replies(ev: ev)
|
||||
}
|
||||
|
||||
// associate with events as well
|
||||
return self.events.store_zap(zap: zap)
|
||||
return stored
|
||||
}
|
||||
|
||||
var pubkey: String {
|
||||
|
||||
+119
-79
@@ -23,7 +23,7 @@ struct NewEventsBits: OptionSet {
|
||||
static let notifications: NewEventsBits = [.zaps, .likes, .reposts, .mentions]
|
||||
}
|
||||
|
||||
class HomeModel: ObservableObject {
|
||||
class HomeModel {
|
||||
// Don't trigger a user notification for events older than a certain age
|
||||
static let event_max_age_for_notification: TimeInterval = 12 * 60 * 60
|
||||
|
||||
@@ -49,9 +49,10 @@ class HomeModel: ObservableObject {
|
||||
|
||||
var signal = SignalModel()
|
||||
|
||||
@Published var new_events: NewEventsBits = NewEventsBits()
|
||||
@Published var notifications = NotificationsModel()
|
||||
@Published var events: EventHolder = EventHolder()
|
||||
var notifications = NotificationsModel()
|
||||
var notification_status = NotificationStatusModel()
|
||||
var events: EventHolder = EventHolder()
|
||||
var zap_button: ZapButtonModel = ZapButtonModel()
|
||||
|
||||
init() {
|
||||
self.damus_state = DamusState.empty
|
||||
@@ -164,70 +165,38 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func handle_zap_event_with_zapper(profiles: Profiles, ev: NostrEvent, our_keypair: Keypair, zapper: String) {
|
||||
|
||||
guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else {
|
||||
return
|
||||
}
|
||||
|
||||
damus_state.add_zap(zap: .zap(zap))
|
||||
|
||||
guard zap.target.pubkey == our_keypair.pubkey else {
|
||||
return
|
||||
}
|
||||
|
||||
if !notifications.insert_zap(.zap(zap)) {
|
||||
return
|
||||
}
|
||||
|
||||
if handle_last_event(ev: ev, timeline: .notifications) {
|
||||
if damus_state.settings.zap_vibration {
|
||||
// Generate zap vibration
|
||||
zap_vibrate(zap_amount: zap.invoice.amount)
|
||||
}
|
||||
if damus_state.settings.zap_notification {
|
||||
// Create in-app local notification for zap received.
|
||||
switch zap.target {
|
||||
case .profile(let profile_id):
|
||||
create_in_app_profile_zap_notification(profiles: profiles, zap: zap, profile_id: profile_id)
|
||||
case .note(let note_target):
|
||||
create_in_app_event_zap_notification(profiles: profiles, zap: zap, evId: note_target.note_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func handle_zap_event(_ ev: NostrEvent) {
|
||||
// These are zap notifications
|
||||
guard let ptag = event_tag(ev, name: "p") else {
|
||||
return
|
||||
}
|
||||
process_zap_event(damus_state: damus_state, ev: ev) { zapres in
|
||||
guard case .done(let zap) = zapres else { return }
|
||||
|
||||
guard zap.target.pubkey == self.damus_state.keypair.pubkey else {
|
||||
return
|
||||
}
|
||||
|
||||
let our_keypair = damus_state.keypair
|
||||
if let local_zapper = damus_state.profiles.lookup_zapper(pubkey: ptag) {
|
||||
handle_zap_event_with_zapper(profiles: self.damus_state.profiles, ev: ev, our_keypair: our_keypair, zapper: local_zapper)
|
||||
return
|
||||
}
|
||||
|
||||
guard let profile = damus_state.profiles.lookup(id: ptag) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let lnurl = profile.lnurl else {
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
guard let zapper = await fetch_zapper_from_lnurl(lnurl) else {
|
||||
if !self.notifications.insert_zap(.zap(zap)) {
|
||||
return
|
||||
}
|
||||
|
||||
guard let new_bits = handle_last_events(new_events: self.notification_status.new_events, ev: ev, timeline: .notifications, shouldNotify: true) else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.damus_state.profiles.zappers[ptag] = zapper
|
||||
self.handle_zap_event_with_zapper(profiles: self.damus_state.profiles, ev: ev, our_keypair: our_keypair, zapper: zapper)
|
||||
if self.damus_state.settings.zap_vibration {
|
||||
// Generate zap vibration
|
||||
zap_vibrate(zap_amount: zap.invoice.amount)
|
||||
}
|
||||
|
||||
if self.damus_state.settings.zap_notification {
|
||||
// Create in-app local notification for zap received.
|
||||
switch zap.target {
|
||||
case .profile(let profile_id):
|
||||
create_in_app_profile_zap_notification(profiles: self.damus_state.profiles, zap: zap, profile_id: profile_id)
|
||||
case .note(let note_target):
|
||||
create_in_app_event_zap_notification(profiles: self.damus_state.profiles, zap: zap, evId: note_target.note_id)
|
||||
}
|
||||
}
|
||||
|
||||
self.notification_status.new_events = new_bits
|
||||
}
|
||||
|
||||
}
|
||||
@@ -402,12 +371,12 @@ class HomeModel: ObservableObject {
|
||||
|
||||
/// Send the initial filters, just our contact list mostly
|
||||
func send_initial_filters(relay_id: String) {
|
||||
var filter = NostrFilter(kinds: [.contacts],
|
||||
limit: 1,
|
||||
authors: [damus_state.pubkey])
|
||||
pool.send(.subscribe(.init(filters: [filter], sub_id: init_subid)), to: [relay_id])
|
||||
let filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey])
|
||||
let subscription = NostrSubscribe(filters: [filter], sub_id: init_subid)
|
||||
pool.send(.subscribe(subscription), to: [relay_id])
|
||||
}
|
||||
|
||||
/// After initial connection or reconnect, send subscription filters for the home timeline, DMs, and notifications
|
||||
func send_home_filters(relay_id: String?) {
|
||||
// TODO: since times should be based on events from a specific relay
|
||||
// perhaps we could mark this in the relay pool somehow
|
||||
@@ -417,7 +386,7 @@ class HomeModel: ObservableObject {
|
||||
|
||||
var contacts_filter = NostrFilter(kinds: [.metadata])
|
||||
contacts_filter.authors = friends
|
||||
|
||||
|
||||
var our_contacts_filter = NostrFilter(kinds: [.contacts, .metadata])
|
||||
our_contacts_filter.authors = [damus_state.pubkey]
|
||||
|
||||
@@ -472,7 +441,7 @@ class HomeModel: ObservableObject {
|
||||
notifications_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: notifications_filters)
|
||||
dms_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: dms_filters)
|
||||
|
||||
print_filters(relay_id: relay_id, filters: [home_filters, contacts_filters, notifications_filters, dms_filters])
|
||||
//print_filters(relay_id: relay_id, filters: [home_filters, contacts_filters, notifications_filters, dms_filters])
|
||||
|
||||
if let relay_id {
|
||||
pool.send(.subscribe(.init(filters: home_filters, sub_id: home_subid)), to: [relay_id])
|
||||
@@ -555,8 +524,8 @@ class HomeModel: ObservableObject {
|
||||
|
||||
@discardableResult
|
||||
func handle_last_event(ev: NostrEvent, timeline: Timeline, shouldNotify: Bool = true) -> Bool {
|
||||
if let new_bits = handle_last_events(new_events: self.new_events, ev: ev, timeline: timeline, shouldNotify: shouldNotify) {
|
||||
new_events = new_bits
|
||||
if let new_bits = handle_last_events(new_events: self.notification_status.new_events, ev: ev, timeline: timeline, shouldNotify: shouldNotify) {
|
||||
self.notification_status.new_events = new_bits
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -588,7 +557,7 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
|
||||
func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) {
|
||||
self.new_events = notifs
|
||||
notification_status.new_events = notifs
|
||||
|
||||
if damus_state.settings.dm_notification && ev.age < HomeModel.event_max_age_for_notification {
|
||||
let convo = ev.decrypted(privkey: self.damus_state.keypair.privkey) ?? NSLocalizedString("New encrypted direct message", comment: "Notification that the user has received a new direct message")
|
||||
@@ -606,7 +575,7 @@ class HomeModel: ObservableObject {
|
||||
|
||||
if !should_debounce_dms {
|
||||
self.incoming_dms.append(ev)
|
||||
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
||||
if let notifs = handle_incoming_dms(prev_events: notification_status.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
||||
got_new_dm(notifs: notifs, ev: ev)
|
||||
}
|
||||
self.incoming_dms = []
|
||||
@@ -616,7 +585,7 @@ class HomeModel: ObservableObject {
|
||||
incoming_dms.append(ev)
|
||||
|
||||
dm_debouncer.debounce { [self] in
|
||||
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
||||
if let notifs = handle_incoming_dms(prev_events: notification_status.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
||||
got_new_dm(notifs: notifs, ev: ev)
|
||||
}
|
||||
self.incoming_dms = []
|
||||
@@ -804,11 +773,11 @@ func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping (
|
||||
}
|
||||
}
|
||||
|
||||
func process_metadata_event(events: EventCache, our_pubkey: String, profiles: Profiles, ev: NostrEvent, completion: (() -> Void)? = nil) {
|
||||
func process_metadata_event(events: EventCache, our_pubkey: String, profiles: Profiles, ev: NostrEvent, completion: ((Profile?) -> Void)? = nil) {
|
||||
guard_valid_event(events: events, ev: ev) {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
|
||||
completion?()
|
||||
completion?(nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -816,7 +785,7 @@ func process_metadata_event(events: EventCache, our_pubkey: String, profiles: Pr
|
||||
|
||||
DispatchQueue.main.async {
|
||||
process_metadata_profile(our_pubkey: our_pubkey, profiles: profiles, profile: profile, ev: ev)
|
||||
completion?()
|
||||
completion?(profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1106,9 +1075,8 @@ func zap_notification_title(_ zap: Zap) -> String {
|
||||
}
|
||||
|
||||
func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String {
|
||||
let src = zap.private_request ?? zap.request.ev
|
||||
let anon = event_is_anonymous(ev: src)
|
||||
let pk = anon ? "anon" : src.pubkey
|
||||
let src = zap.request.ev
|
||||
let pk = zap.is_anon ? "anon" : src.pubkey
|
||||
let profile = profiles.lookup(id: pk)
|
||||
let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0))
|
||||
let formattedSats = format_msats_abbrev(zap.invoice.amount)
|
||||
@@ -1250,3 +1218,75 @@ func create_local_notification(profiles: Profiles, notify: LocalNotification) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum ProcessZapResult {
|
||||
case already_processed(Zap)
|
||||
case done(Zap)
|
||||
case failed
|
||||
}
|
||||
|
||||
func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @escaping (ProcessZapResult) -> Void) {
|
||||
// These are zap notifications
|
||||
guard let ptag = event_tag(ev, name: "p") else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
// just return the zap if we already have it
|
||||
if let zap = damus_state.zaps.zaps[ev.id], case .zap(let z) = zap {
|
||||
completion(.already_processed(z))
|
||||
return
|
||||
}
|
||||
|
||||
if let local_zapper = damus_state.profiles.lookup_zapper(pubkey: ptag) {
|
||||
guard let zap = process_zap_event_with_zapper(damus_state: damus_state, ev: ev, zapper: local_zapper) else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
damus_state.add_zap(zap: .zap(zap))
|
||||
completion(.done(zap))
|
||||
return
|
||||
}
|
||||
|
||||
guard let profile = damus_state.profiles.lookup(id: ptag) else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
guard let lnurl = profile.lnurl else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
guard let zapper = await fetch_zapper_from_lnurl(lnurl) else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
damus_state.profiles.zappers[ptag] = zapper
|
||||
guard let zap = process_zap_event_with_zapper(damus_state: damus_state, ev: ev, zapper: zapper) else {
|
||||
completion(.failed)
|
||||
return
|
||||
}
|
||||
damus_state.add_zap(zap: .zap(zap))
|
||||
completion(.done(zap))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fileprivate func process_zap_event_with_zapper(damus_state: DamusState, ev: NostrEvent, zapper: String) -> Zap? {
|
||||
let our_keypair = damus_state.keypair
|
||||
|
||||
guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
damus_state.add_zap(zap: .zap(zap))
|
||||
|
||||
return zap
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
enum MentionType {
|
||||
case pubkey
|
||||
case event
|
||||
|
||||
|
||||
var ref: String {
|
||||
switch self {
|
||||
case .pubkey:
|
||||
@@ -495,14 +495,17 @@ func make_post_tags(post_blocks: [PostBlock], tags: [[String]], silent_mentions:
|
||||
continue
|
||||
}
|
||||
|
||||
if let ind = find_tag_ref(type: ref.key, id: ref.ref_id, tags: tags) {
|
||||
let mention = Mention(index: ind, type: mention_type, ref: ref)
|
||||
if find_tag_ref(type: ref.key, id: ref.ref_id, tags: tags) != nil {
|
||||
// Mention index is nil because indexed mentions from NIP-08 is deprecated.
|
||||
// It has been replaced with NIP-27 text note references with nostr: prefixed URIs.
|
||||
let mention = Mention(index: nil, type: mention_type, ref: ref)
|
||||
let block = Block.mention(mention)
|
||||
blocks.append(block)
|
||||
} else {
|
||||
let ind = new_tags.count
|
||||
new_tags.append(refid_to_tag(ref))
|
||||
let mention = Mention(index: ind, type: mention_type, ref: ref)
|
||||
// Mention index is nil because indexed mentions from NIP-08 is deprecated.
|
||||
// It has been replaced with NIP-27 text note references with nostr: prefixed URIs.
|
||||
let mention = Mention(index: nil, type: mention_type, ref: ref)
|
||||
let block = Block.mention(mention)
|
||||
blocks.append(block)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// NotificationStatusModel.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-06-23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class NotificationStatusModel: ObservableObject {
|
||||
@Published var new_events: NewEventsBits = NewEventsBits()
|
||||
}
|
||||
@@ -21,12 +21,12 @@ class ZapGroup {
|
||||
}
|
||||
|
||||
func zap_requests() -> [NostrEvent] {
|
||||
zaps.map { z in z.request }
|
||||
zaps.map { z in z.request.ev }
|
||||
}
|
||||
|
||||
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
|
||||
for zap in zaps {
|
||||
if !isIncluded(zap.request) {
|
||||
if !isIncluded(zap.request.ev) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class ZapGroup {
|
||||
}
|
||||
|
||||
func filter(_ isIncluded: (NostrEvent) -> Bool) -> ZapGroup? {
|
||||
let new_zaps = zaps.filter { isIncluded($0.request) }
|
||||
let new_zaps = zaps.filter { isIncluded($0.request.ev) }
|
||||
guard new_zaps.count > 0 else {
|
||||
return nil
|
||||
}
|
||||
@@ -60,8 +60,8 @@ class ZapGroup {
|
||||
|
||||
msat_total += zap.amount
|
||||
|
||||
if !zappers.contains(zap.request.pubkey) {
|
||||
zappers.insert(zap.request.pubkey)
|
||||
if !zappers.contains(zap.request.ev.pubkey) {
|
||||
zappers.insert(zap.request.ev.pubkey)
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@@ -150,7 +150,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
}
|
||||
|
||||
for zap in incoming_zaps {
|
||||
pks.insert(zap.request.pubkey)
|
||||
pks.insert(zap.request.ev.pubkey)
|
||||
}
|
||||
|
||||
return Array(pks)
|
||||
@@ -307,7 +307,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
changed = changed || incoming_events.count != count
|
||||
|
||||
count = profile_zaps.zaps.count
|
||||
profile_zaps.zaps = profile_zaps.zaps.filter { zap in isIncluded(zap.request) }
|
||||
profile_zaps.zaps = profile_zaps.zaps.filter { zap in isIncluded(zap.request.ev) }
|
||||
changed = changed || profile_zaps.zaps.count != count
|
||||
|
||||
for el in reactions {
|
||||
@@ -325,7 +325,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
for el in zaps {
|
||||
count = el.value.zaps.count
|
||||
el.value.zaps = el.value.zaps.filter {
|
||||
isIncluded($0.request)
|
||||
isIncluded($0.request.ev)
|
||||
}
|
||||
changed = changed || el.value.zaps.count != count
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ struct NostrPost {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: parse nostr:{e,p}:pubkey uris as well
|
||||
func parse_post_mention_type(_ p: Parser) -> MentionType? {
|
||||
if parse_char(p, "@") {
|
||||
return .pubkey
|
||||
|
||||
@@ -10,15 +10,21 @@ import Foundation
|
||||
/// manages the lifetime of a thread
|
||||
class ThreadModel: ObservableObject {
|
||||
@Published var event: NostrEvent
|
||||
let original_event: NostrEvent
|
||||
var event_map: Set<NostrEvent>
|
||||
|
||||
init(event: NostrEvent, damus_state: DamusState) {
|
||||
self.damus_state = damus_state
|
||||
self.event_map = Set()
|
||||
self.event = event
|
||||
self.original_event = event
|
||||
add_event(event)
|
||||
}
|
||||
|
||||
var is_original: Bool {
|
||||
return original_event.id == event.id
|
||||
}
|
||||
|
||||
let damus_state: DamusState
|
||||
|
||||
let profiles_subid = UUID().description
|
||||
@@ -101,6 +107,10 @@ class ThreadModel: ObservableObject {
|
||||
|
||||
if ev.known_kind == .metadata {
|
||||
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||
} else if ev.known_kind == .zap {
|
||||
process_zap_event(damus_state: damus_state, ev: ev) { zap in
|
||||
|
||||
}
|
||||
} else if ev.is_textlike {
|
||||
self.add_event(ev)
|
||||
}
|
||||
@@ -116,3 +126,10 @@ class ThreadModel: ObservableObject {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
func get_top_zap(events: EventCache, evid: String) -> Zapping? {
|
||||
return events.get_cache_data(evid).zaps_model.zaps.first(where: { zap in
|
||||
!zap.request.marked_hidden
|
||||
})
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ class UserSettingsStore: ObservableObject {
|
||||
@StringSetting(key: "default_media_uploader", default_value: .nostrBuild)
|
||||
var default_media_uploader: MediaUploader
|
||||
|
||||
@Setting(key: "show_wallet_selector", default_value: true)
|
||||
@Setting(key: "show_wallet_selector", default_value: false)
|
||||
var show_wallet_selector: Bool
|
||||
|
||||
@Setting(key: "left_handed", default_value: false)
|
||||
@@ -126,6 +126,10 @@ class UserSettingsStore: ObservableObject {
|
||||
@Setting(key: "truncate_timeline_text", default_value: false)
|
||||
var truncate_timeline_text: Bool
|
||||
|
||||
/// Nozaps mode gimps note zapping to fit into apple's content-tipping guidelines. It can not be configurable to end-users on the app store
|
||||
@Setting(key: "nozaps", default_value: true)
|
||||
var nozaps: Bool
|
||||
|
||||
@Setting(key: "truncate_mention_text", default_value: true)
|
||||
var truncate_mention_text: Bool
|
||||
|
||||
|
||||
@@ -10,6 +10,4 @@ import Foundation
|
||||
class ZapButtonModel: ObservableObject {
|
||||
var invoice: String? = nil
|
||||
@Published var zapping: String = ""
|
||||
@Published var showing_select_wallet: Bool = false
|
||||
@Published var showing_zap_customizer: Bool = false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// CustomizeZapModel.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-06-22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
class CustomizeZapModel: ObservableObject {
|
||||
@Published var comment: String = ""
|
||||
@Published var custom_amount: String = ""
|
||||
@Published var custom_amount_sats: Int? = nil
|
||||
@Published var zap_type: ZapType = .pub
|
||||
@Published var invoice: String = ""
|
||||
@Published var error: String? = nil
|
||||
@Published var zapping: Bool = false
|
||||
@Published var show_zap_types: Bool = false
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
func set_defaults(settings: UserSettingsStore) {
|
||||
self.zap_type = settings.default_zap_type
|
||||
self.custom_amount = String(settings.default_zap_amount)
|
||||
self.custom_amount_sats = settings.default_zap_amount
|
||||
}
|
||||
}
|
||||
@@ -53,18 +53,13 @@ class ZapsModel: ObservableObject {
|
||||
case .notice:
|
||||
break
|
||||
case .eose:
|
||||
let events = state.events.lookup_zaps(target: target).map { $0.request }
|
||||
let events = state.events.lookup_zaps(target: target).map { $0.request.ev }
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state)
|
||||
case .event(_, let ev):
|
||||
guard ev.kind == 9735 else {
|
||||
return
|
||||
}
|
||||
|
||||
if let zap = state.zaps.zaps[ev.id] {
|
||||
state.events.store_zap(zap: zap)
|
||||
return
|
||||
}
|
||||
|
||||
guard let zapper = state.profiles.lookup_zapper(pubkey: target.pubkey) else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ class Profile: Codable {
|
||||
}
|
||||
|
||||
var damus_donation: Int? {
|
||||
get { return int("damus_donation"); }
|
||||
set(s) { set_int("damus_donation", s) }
|
||||
get { return int("damus_donation_v2"); }
|
||||
set(s) { set_int("damus_donation_v2", s) }
|
||||
}
|
||||
|
||||
var picture: String? {
|
||||
|
||||
@@ -60,24 +60,13 @@ func parse_nostr_ref_uri(_ p: Parser) -> ReferencedId? {
|
||||
if !parse_str(p, "nostr:") {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let typ = parse_nostr_ref_uri_type(p) else {
|
||||
|
||||
guard let ref = parse_post_bech32_mention(p) else {
|
||||
p.pos = start
|
||||
return nil
|
||||
}
|
||||
|
||||
if !parse_char(p, ":") {
|
||||
p.pos = start
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let pk = parse_hexstr(p, len: 64) else {
|
||||
p.pos = start
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: parse relays from nostr uris
|
||||
return ReferencedId(ref_id: pk, relay_id: nil, key: typ)
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
func decode_universal_link(_ s: String) -> NostrLink? {
|
||||
|
||||
@@ -37,9 +37,9 @@ public struct RelayURL: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
final class RelayConnection {
|
||||
private(set) var isConnected = false
|
||||
private(set) var isConnecting = false
|
||||
final class RelayConnection: ObservableObject {
|
||||
@Published private(set) var isConnected = false
|
||||
@Published private(set) var isConnecting = false
|
||||
|
||||
private(set) var last_connection_attempt: TimeInterval = 0
|
||||
private(set) var last_pong: Date? = nil
|
||||
@@ -129,6 +129,11 @@ final class RelayConnection {
|
||||
}
|
||||
case .error(let error):
|
||||
print("⚠️ Warning: RelayConnection (\(self.url)) error: \(error)")
|
||||
let nserr = error as NSError
|
||||
if nserr.domain == NSPOSIXErrorDomain && nserr.code == 57 {
|
||||
// ignore socket not connected?
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.isConnected = false
|
||||
self.isConnecting = false
|
||||
|
||||
@@ -18,11 +18,16 @@ struct QueuedRequest {
|
||||
let relay: String
|
||||
}
|
||||
|
||||
struct SeenEvent: Hashable {
|
||||
let relay_id: String
|
||||
let evid: String
|
||||
}
|
||||
|
||||
class RelayPool {
|
||||
var relays: [Relay] = []
|
||||
var handlers: [RelayHandler] = []
|
||||
var request_queue: [QueuedRequest] = []
|
||||
var seen: Set<String> = Set()
|
||||
var seen: Set<SeenEvent> = Set()
|
||||
var counts: [String: UInt64] = [:]
|
||||
|
||||
private let network_monitor = NWPathMonitor()
|
||||
@@ -233,7 +238,7 @@ class RelayPool {
|
||||
func record_seen(relay_id: String, event: NostrConnectionEvent) {
|
||||
if case .nostr_event(let ev) = event {
|
||||
if case .event(_, let nev) = ev {
|
||||
let k = relay_id + nev.id
|
||||
let k = SeenEvent(relay_id: relay_id, evid: nev.id)
|
||||
if !seen.contains(k) {
|
||||
seen.insert(k)
|
||||
if counts[relay_id] == nil {
|
||||
|
||||
@@ -13,6 +13,19 @@ enum WebSocketEvent {
|
||||
case message(URLSessionWebSocketTask.Message)
|
||||
case disconnected(URLSessionWebSocketTask.CloseCode, String?)
|
||||
case error(Error)
|
||||
|
||||
var description: String? {
|
||||
switch self {
|
||||
case .connected:
|
||||
return "Connected"
|
||||
case .message(_):
|
||||
return "Received message"
|
||||
case .disconnected(let close_code, let reason):
|
||||
return "Disconnected: Close code: \(close_code), reason: \(reason ?? "unknown")"
|
||||
case .error(let error):
|
||||
return "Error: \(error)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class WebSocket: NSObject, URLSessionWebSocketDelegate {
|
||||
|
||||
@@ -62,7 +62,7 @@ class ZapsDataModel: ObservableObject {
|
||||
}
|
||||
|
||||
func confirm_nwc(reqid: String) {
|
||||
guard let zap = zaps.first(where: { z in z.request.id == reqid }),
|
||||
guard let zap = zaps.first(where: { z in z.request.ev.id == reqid }),
|
||||
case .pending(let pzap) = zap
|
||||
else {
|
||||
return
|
||||
@@ -83,16 +83,16 @@ class ZapsDataModel: ObservableObject {
|
||||
}
|
||||
|
||||
func from(_ pubkey: String) -> [Zapping] {
|
||||
return self.zaps.filter { z in z.request.pubkey == pubkey }
|
||||
return self.zaps.filter { z in z.request.ev.pubkey == pubkey }
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func remove(reqid: String) -> Bool {
|
||||
guard zaps.first(where: { z in z.request.id == reqid }) != nil else {
|
||||
guard zaps.first(where: { z in z.request.ev.id == reqid }) != nil else {
|
||||
return false
|
||||
}
|
||||
|
||||
self.zaps = zaps.filter { z in z.request.id != reqid }
|
||||
self.zaps = zaps.filter { z in z.request.ev.id != reqid }
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -175,6 +175,9 @@ class EventCache {
|
||||
@discardableResult
|
||||
func store_zap(zap: Zapping) -> Bool {
|
||||
let data = get_cache_data(zap.target.id).zaps_model
|
||||
if let ev = zap.event {
|
||||
insert(ev)
|
||||
}
|
||||
return insert_uniq_sorted_zap_by_amount(zaps: &data.zaps, new_zap: zap)
|
||||
}
|
||||
|
||||
@@ -182,7 +185,7 @@ class EventCache {
|
||||
switch zap.target {
|
||||
case .note(let note_target):
|
||||
let zaps = get_cache_data(note_target.note_id).zaps_model
|
||||
zaps.remove(reqid: zap.request.id)
|
||||
zaps.remove(reqid: zap.request.ev.id)
|
||||
case .profile:
|
||||
// these aren't stored anywhere yet
|
||||
break
|
||||
@@ -201,6 +204,7 @@ class EventCache {
|
||||
return image_metadata[url.absoluteString.lowercased()]
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func lookup_media_size(url: URL) -> CGSize? {
|
||||
if let img_meta = lookup_img_metadata(url: url) {
|
||||
return img_meta.meta.dim?.size
|
||||
@@ -213,6 +217,7 @@ class EventCache {
|
||||
video_meta[url.absoluteString] = meta
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func get_video_player_model(url: URL) -> VideoPlayerModel {
|
||||
if let model = video_meta[url.absoluteString] {
|
||||
return model
|
||||
@@ -395,7 +400,7 @@ func preload_image(url: URL) {
|
||||
|
||||
print("Preloading image \(url.absoluteString)")
|
||||
|
||||
KingfisherManager.shared.retrieveImage(with: ImageResource(downloadURL: url)) { val in
|
||||
KingfisherManager.shared.retrieveImage(with: Kingfisher.ImageResource(downloadURL: url)) { val in
|
||||
print("Preloaded image \(url.absoluteString)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import Foundation
|
||||
class EventHolder: ObservableObject, ScrollQueue {
|
||||
private var has_event: Set<String>
|
||||
@Published var events: [NostrEvent]
|
||||
@Published var incoming: [NostrEvent]
|
||||
var incoming: [NostrEvent]
|
||||
var should_queue: Bool
|
||||
var on_queue: ((NostrEvent) -> Void)?
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ extension KFOptionSetter {
|
||||
|
||||
func onFailure(fallbackUrl: URL?, cacheKey: String?) -> Self {
|
||||
guard let url = fallbackUrl, let key = cacheKey else { return self }
|
||||
let imageResource = ImageResource(downloadURL: url, cacheKey: key)
|
||||
let imageResource = Kingfisher.ImageResource(downloadURL: url, cacheKey: key)
|
||||
let source = imageResource.convertToSource()
|
||||
options.alternativeSources = [source]
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ func hashtag_str(_ htag: String) -> CompatibleText {
|
||||
}
|
||||
text = Text(attributedString)
|
||||
let img = Image("\(name)-hashtag")
|
||||
text = text + Text("\(img)").baselineOffset(custom_hashtag.offset ?? 0.0)
|
||||
text = text + Text(img).baselineOffset(custom_hashtag.offset ?? 0.0)
|
||||
} else {
|
||||
attributedString.foregroundColor = DamusColors.purple
|
||||
}
|
||||
|
||||
@@ -59,17 +59,15 @@ struct ImageMetadata: Equatable {
|
||||
}
|
||||
|
||||
func process_blurhash(blurhash: String, size: CGSize?) async -> UIImage? {
|
||||
let res = Task.init {
|
||||
let res = Task.detached(priority: .low) {
|
||||
let size = get_blurhash_size(img_size: size ?? CGSize(width: 100.0, height: 100.0))
|
||||
guard let img = UIImage.init(blurHash: blurhash, size: size) else {
|
||||
let noimg: UIImage? = nil
|
||||
return noimg
|
||||
}
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
|
||||
return await res.value
|
||||
}
|
||||
|
||||
@@ -146,7 +144,7 @@ func calculate_blurhash(img: UIImage) async -> String? {
|
||||
return nil
|
||||
}
|
||||
|
||||
let res = Task.init {
|
||||
let res = Task.detached(priority: .low) {
|
||||
let bhs = get_blurhash_size(img_size: img.size)
|
||||
let smaller = img.resized(to: bhs)
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ func insert_uniq_sorted_zap(zaps: inout [Zapping], new_zap: Zapping, cmp: (Zappi
|
||||
var i: Int = 0
|
||||
|
||||
for zap in zaps {
|
||||
if new_zap.request.id == zap.request.id {
|
||||
if new_zap.request.ev.id == zap.request.ev.id {
|
||||
// replace pending
|
||||
if !new_zap.is_pending && zap.is_pending {
|
||||
print("nwc: replacing pending with real zap \(new_zap.request.id)")
|
||||
print("nwc: replacing pending with real zap \(new_zap.request.ev.id)")
|
||||
zaps[i] = new_zap
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -77,6 +77,9 @@ extension Notification.Name {
|
||||
static var update_stats: Notification.Name {
|
||||
return Notification.Name("update_stats")
|
||||
}
|
||||
static var present_sheet: Notification.Name {
|
||||
return Notification.Name("present_sheet")
|
||||
}
|
||||
static var zapping: Notification.Name {
|
||||
return Notification.Name("zapping")
|
||||
}
|
||||
|
||||
+28
-9
@@ -41,7 +41,16 @@ public enum ZapTarget: Equatable {
|
||||
|
||||
struct ZapRequest {
|
||||
let ev: NostrEvent
|
||||
let marked_hidden: Bool
|
||||
|
||||
var is_in_thread: Bool {
|
||||
return !self.ev.content.isEmpty && !marked_hidden
|
||||
}
|
||||
|
||||
init(ev: NostrEvent) {
|
||||
self.ev = ev
|
||||
self.marked_hidden = ev.tags.first(where: { t in t.count > 0 && t[0] == "hidden" }) != nil
|
||||
}
|
||||
}
|
||||
|
||||
enum ExtPendingZapStateType {
|
||||
@@ -129,7 +138,7 @@ struct ZapRequestId: Equatable {
|
||||
let reqid: String
|
||||
|
||||
init(from_zap: Zapping) {
|
||||
self.reqid = from_zap.request.id
|
||||
self.reqid = from_zap.request.ev.id
|
||||
}
|
||||
|
||||
init(from_makezap: MakeZapRequest) {
|
||||
@@ -198,12 +207,12 @@ enum Zapping {
|
||||
}
|
||||
}
|
||||
|
||||
var request: NostrEvent {
|
||||
var request: ZapRequest {
|
||||
switch self {
|
||||
case .zap(let zap):
|
||||
return zap.request_ev
|
||||
return zap.request
|
||||
case .pending(let pzap):
|
||||
return pzap.request.ev
|
||||
return pzap.request
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +236,15 @@ enum Zapping {
|
||||
}
|
||||
}
|
||||
|
||||
var is_in_thread: Bool {
|
||||
switch self {
|
||||
case .zap(let zap):
|
||||
return zap.request.is_in_thread
|
||||
case .pending(let pzap):
|
||||
return pzap.request.is_in_thread
|
||||
}
|
||||
}
|
||||
|
||||
var is_anon: Bool {
|
||||
switch self {
|
||||
case .zap(let zap):
|
||||
@@ -242,12 +260,12 @@ struct Zap {
|
||||
public let invoice: ZapInvoice
|
||||
public let zapper: String /// zap authorizer
|
||||
public let target: ZapTarget
|
||||
public let request: ZapRequest
|
||||
public let raw_request: ZapRequest
|
||||
public let is_anon: Bool
|
||||
public let private_request: NostrEvent?
|
||||
public let private_request: ZapRequest?
|
||||
|
||||
var request_ev: NostrEvent {
|
||||
return private_request ?? self.request.ev
|
||||
var request: ZapRequest {
|
||||
return private_request ?? self.raw_request
|
||||
}
|
||||
|
||||
public static func from_zap_event(zap_ev: NostrEvent, zapper: String, our_privkey: String?) -> Zap? {
|
||||
@@ -295,8 +313,9 @@ struct Zap {
|
||||
}
|
||||
|
||||
let is_anon = private_request == nil && event_is_anonymous(ev: zap_req)
|
||||
let preq = private_request.map { pr in ZapRequest(ev: pr) }
|
||||
|
||||
return Zap(event: zap_ev, invoice: zap_invoice, zapper: zapper, target: target, request: ZapRequest(ev: zap_req), is_anon: is_anon, private_request: private_request)
|
||||
return Zap(event: zap_ev, invoice: zap_invoice, zapper: zapper, target: target, raw_request: ZapRequest(ev: zap_req), is_anon: is_anon, private_request: preq)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-8
@@ -12,8 +12,8 @@ class Zaps {
|
||||
let our_pubkey: String
|
||||
var our_zaps: [String: [Zapping]]
|
||||
|
||||
var event_counts: [String: Int]
|
||||
var event_totals: [String: Int64]
|
||||
private(set) var event_counts: [String: Int]
|
||||
private(set) var event_totals: [String: Int64]
|
||||
|
||||
init(our_pubkey: String) {
|
||||
self.zaps = [:]
|
||||
@@ -27,13 +27,13 @@ class Zaps {
|
||||
var res: Zapping? = nil
|
||||
for kv in our_zaps {
|
||||
let ours = kv.value
|
||||
guard let zap = ours.first(where: { z in z.request.id == reqid }) else {
|
||||
guard let zap = ours.first(where: { z in z.request.ev.id == reqid }) else {
|
||||
continue
|
||||
}
|
||||
|
||||
res = zap
|
||||
|
||||
our_zaps[kv.key] = ours.filter { z in z.request.id != reqid }
|
||||
our_zaps[kv.key] = ours.filter { z in z.request.ev.id != reqid }
|
||||
|
||||
if let count = event_counts[zap.target.id] {
|
||||
event_counts[zap.target.id] = count - 1
|
||||
@@ -51,13 +51,16 @@ class Zaps {
|
||||
}
|
||||
|
||||
func add_zap(zap: Zapping) {
|
||||
if zaps[zap.request.id] != nil {
|
||||
if zaps[zap.request.ev.id] != nil {
|
||||
return
|
||||
}
|
||||
self.zaps[zap.request.id] = zap
|
||||
self.zaps[zap.request.ev.id] = zap
|
||||
if let zap_id = zap.event?.id {
|
||||
self.zaps[zap_id] = zap
|
||||
}
|
||||
|
||||
// record our zaps for an event
|
||||
if zap.request.pubkey == our_pubkey {
|
||||
if zap.request.ev.pubkey == our_pubkey {
|
||||
switch zap.target {
|
||||
case .note(let note_target):
|
||||
if our_zaps[note_target.note_id] == nil {
|
||||
@@ -71,7 +74,7 @@ class Zaps {
|
||||
}
|
||||
|
||||
// don't count tips to self. lame.
|
||||
guard zap.request.pubkey != zap.target.pubkey else {
|
||||
guard zap.request.ev.pubkey != zap.target.pubkey else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ struct EventActionBar: View {
|
||||
self.show_repost_action = true
|
||||
}
|
||||
}
|
||||
.accessibilityLabel(NSLocalizedString("Boosts", comment: "Accessibility label for boosts button"))
|
||||
.accessibilityLabel(NSLocalizedString("Reposts", comment: "Accessibility label for boosts button"))
|
||||
Text(verbatim: "\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
|
||||
.font(.footnote.weight(.medium))
|
||||
.foregroundColor(bar.boosted ? Color.green : Color.gray)
|
||||
@@ -95,7 +95,7 @@ struct EventActionBar: View {
|
||||
EventActionButton(img: "upload", col: Color.gray) {
|
||||
show_share_action = true
|
||||
}
|
||||
.accessibilityLabel(NSLocalizedString("Share", comment: "Button to share a post"))
|
||||
.accessibilityLabel(NSLocalizedString("Share", comment: "Button to share a note"))
|
||||
}
|
||||
.onAppear {
|
||||
self.bar.update(damus: damus_state, evid: self.event.id)
|
||||
|
||||
@@ -19,7 +19,11 @@ struct EventDetailBar: View {
|
||||
self.target = target
|
||||
self.target_pk = target_pk
|
||||
self._bar = ObservedObject(wrappedValue: make_actionbar_model(ev: target, damus: state))
|
||||
}
|
||||
|
||||
var ZapDetails: Text {
|
||||
let noun = Text(verbatim: zapsCountString(bar.zaps)).foregroundColor(.gray)
|
||||
return Text("\(Text(verbatim: bar.zaps.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -40,13 +44,11 @@ struct EventDetailBar: View {
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
||||
if bar.zaps > 0 {
|
||||
if !state.settings.nozaps && bar.zaps > 0 {
|
||||
let dst = ZapsView(state: state, target: .note(id: target, author: target_pk))
|
||||
NavigationLink(destination: dst) {
|
||||
let noun = Text(verbatim: zapsCountString(bar.zaps)).foregroundColor(.gray)
|
||||
Text("\(Text(verbatim: bar.zaps.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
ZapDetails
|
||||
}.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ let carousel_items = [
|
||||
CarouselItem(image: Image("undercover"),
|
||||
text: Text("\(Text("Private", comment: "Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading.").bold()). Creating an account doesn't require a phone number, email or name. Get started right away with zero friction.", comment: "Explanation of what is done to keep personally identifiable information private. There is a heading that precedes this explanation which is a variable to this string.")),
|
||||
CarouselItem(image: Image("bitcoin-p2p"),
|
||||
text: Text("\(Text("Earn Money", comment: "Heading indicating that this application allows users to earn money.").bold()). Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet.", comment: "Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string."))
|
||||
text: Text("\(Text("Earn Money", comment: "Heading indicating that this application allows users to earn money.").bold()). Tip your friends and stack sats with Bitcoin⚡️, the native currency of the internet.", comment: "Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string."))
|
||||
]
|
||||
|
||||
struct CarouselView: View {
|
||||
|
||||
@@ -66,7 +66,7 @@ struct CreateAccountView: View {
|
||||
self.is_done = true
|
||||
}) {
|
||||
HStack {
|
||||
Text("Create account now", comment: "Button to create account.")
|
||||
Text("Create account now", comment: "Button to create account.")
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||
@@ -96,7 +96,7 @@ struct LoginPrompt: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text("Already on nostr?", comment: "Ask the user if they already have an account on nostr")
|
||||
Text("Already on Nostr?", comment: "Ask the user if they already have an account on Nostr")
|
||||
.foregroundColor(Color("DamusMediumGrey"))
|
||||
|
||||
Button(NSLocalizedString("Login", comment: "Button to navigate to login view.")) {
|
||||
@@ -167,7 +167,7 @@ func FormLabel(_ title: String, optional: Bool = false) -> some View {
|
||||
Text(title)
|
||||
.bold()
|
||||
if optional {
|
||||
Text("- optional", comment: "Label indicating that a form input is optional.")
|
||||
Text("optional", comment: "Label indicating that a form input is optional.")
|
||||
.font(.callout)
|
||||
.foregroundColor(DamusColors.mediumGrey)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ struct EULAView: View {
|
||||
.ignoresSafeArea(),
|
||||
alignment: .top
|
||||
)
|
||||
.navigationTitle("EULA")
|
||||
.navigationTitle(NSLocalizedString("EULA", comment: "Navigation title of view that shows the EULA, an acronym for End User License Agreement."))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.navigationBarItems(leading: BackNav())
|
||||
|
||||
@@ -38,8 +38,9 @@ struct EventView: View {
|
||||
}
|
||||
} else if event.known_kind == .zap {
|
||||
if let zap = damus.zaps.zaps[event.id] {
|
||||
ZapEvent(damus: damus, zap: zap)
|
||||
ZapEvent(damus: damus, zap: zap, is_top_zap: options.contains(.top_zap))
|
||||
} else {
|
||||
Text("Invalid Zap", comment: "Text indicating that a zap event is malformed and could not be displayed.")
|
||||
EmptyView()
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -31,9 +31,9 @@ struct MutedEventView: View {
|
||||
.foregroundColor(DamusColors.adaptableGrey)
|
||||
|
||||
HStack {
|
||||
Text("Post from a user you've muted", comment: "Text to indicate that what is being shown is a post from a user who has been muted.")
|
||||
Text("Note from a user you've muted", comment: "Text to indicate that what is being shown is a note from a user who has been muted.")
|
||||
Spacer()
|
||||
Button(shown ? NSLocalizedString("Hide", comment: "Button to hide a post from a user who has been muted.") : NSLocalizedString("Show", comment: "Button to show a post from a user who has been muted.")) {
|
||||
Button(shown ? NSLocalizedString("Hide", comment: "Button to hide a note from a user who has been muted.") : NSLocalizedString("Show", comment: "Button to show a note from a user who has been muted.")) {
|
||||
shown.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ struct EventViewOptions: OptionSet {
|
||||
static let no_translate = EventViewOptions(rawValue: 1 << 6)
|
||||
static let small_pfp = EventViewOptions(rawValue: 1 << 7)
|
||||
static let nested = EventViewOptions(rawValue: 1 << 8)
|
||||
static let top_zap = EventViewOptions(rawValue: 1 << 9)
|
||||
|
||||
static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
|
||||
}
|
||||
|
||||
@@ -10,13 +10,23 @@ import SwiftUI
|
||||
struct ZapEvent: View {
|
||||
let damus: DamusState
|
||||
let zap: Zapping
|
||||
let is_top_zap: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .center) {
|
||||
Text("⚡️ \(format_msats(zap.amount))", comment: "Text indicating the zap amount. i.e. number of satoshis that were tipped to a user")
|
||||
Image("zap.fill")
|
||||
.foregroundColor(.orange)
|
||||
|
||||
Text(verbatim: format_msats(zap.amount))
|
||||
.font(.headline)
|
||||
.padding([.top], 2)
|
||||
|
||||
if is_top_zap {
|
||||
Text("Top Zap", comment: "Text indicating that this zap is the one with the highest amount of sats.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
.padding([.top], 2)
|
||||
}
|
||||
|
||||
if zap.is_private {
|
||||
Image("lock")
|
||||
@@ -31,7 +41,7 @@ struct ZapEvent: View {
|
||||
}
|
||||
}
|
||||
|
||||
TextEvent(damus: damus, event: zap.request, pubkey: zap.request.pubkey, options: [.no_action_bar, .no_replying_to])
|
||||
TextEvent(damus: damus, event: zap.request.ev, pubkey: zap.request.ev.pubkey, options: [.no_action_bar, .no_replying_to])
|
||||
.padding([.top], 1)
|
||||
}
|
||||
}
|
||||
@@ -41,18 +51,18 @@ struct ZapEvent: View {
|
||||
let test_zap_invoice = ZapInvoice(description: .description("description"), amount: 10000, string: "lnbc1", expiry: 1000000, payment_hash: Data(), created_at: 1000000)
|
||||
let test_zap_request_ev = NostrEvent(content: "hi", pubkey: "pk", kind: 9734)
|
||||
let test_zap_request = ZapRequest(ev: test_zap_request_ev)
|
||||
let test_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), request: test_zap_request, is_anon: false, private_request: nil)
|
||||
let test_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: nil)
|
||||
|
||||
let test_private_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), request: test_zap_request, is_anon: false, private_request: test_event)
|
||||
let test_private_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: .init(ev: test_event))
|
||||
|
||||
let test_pending_zap = PendingZap(amount_msat: 10000, target: .note(id: "id", author: "pk"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
|
||||
|
||||
struct ZapEvent_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
ZapEvent(damus: test_damus_state(), zap: .zap(test_zap))
|
||||
ZapEvent(damus: test_damus_state(), zap: .zap(test_zap), is_top_zap: true)
|
||||
|
||||
ZapEvent(damus: test_damus_state(), zap: .zap(test_private_zap))
|
||||
ZapEvent(damus: test_damus_state(), zap: .zap(test_private_zap), is_top_zap: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,27 @@ struct FollowUserView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct FollowersYouKnowView: View {
|
||||
let damus_state: DamusState
|
||||
let friended_followers: [String]
|
||||
|
||||
@EnvironmentObject var followers: FollowersModel
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading) {
|
||||
ForEach(friended_followers, id: \.self) { pk in
|
||||
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.navigationBarTitle(NSLocalizedString("Followers You Know", comment: "Navigation bar title for view that shows who is following a user."))
|
||||
}
|
||||
}
|
||||
|
||||
struct FollowersView: View {
|
||||
let damus_state: DamusState
|
||||
let whos: String
|
||||
|
||||
@EnvironmentObject var followers: FollowersModel
|
||||
|
||||
@@ -58,7 +76,6 @@ struct FollowingView: View {
|
||||
let damus_state: DamusState
|
||||
|
||||
let following: FollowingModel
|
||||
let whos: String
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
|
||||
@@ -80,9 +80,10 @@ struct LoginView: View {
|
||||
}
|
||||
|
||||
if parsed?.is_pub ?? false {
|
||||
Text("This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective.", comment: "Warning that the inputted account key is a public key and the result of what happens because of it.")
|
||||
Text("This is a public key, you will not be able to make notes or interact in any way. This is used for viewing accounts from their perspective.", comment: "Warning that the inputted account key is a public key and the result of what happens because of it.")
|
||||
.foregroundColor(Color.orange)
|
||||
.bold()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
if let p = parsed {
|
||||
@@ -339,7 +340,7 @@ struct CreateAccountPrompt: View {
|
||||
@Binding var create_account: Bool
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text("New to nostr?", comment: "Ask the user if they are new to nostr")
|
||||
Text("New to Nostr?", comment: "Ask the user if they are new to Nostr")
|
||||
.foregroundColor(Color("DamusMediumGrey"))
|
||||
|
||||
Button(NSLocalizedString("Create account", comment: "Button to navigate to create account view.")) {
|
||||
|
||||
@@ -29,7 +29,7 @@ struct TabButton: View {
|
||||
let timeline: Timeline
|
||||
let img: String
|
||||
@Binding var selected: Timeline
|
||||
@Binding var new_events: NewEventsBits
|
||||
@ObservedObject var nstatus: NotificationStatusModel
|
||||
|
||||
let settings: UserSettingsStore
|
||||
let action: (Timeline) -> ()
|
||||
@@ -38,7 +38,7 @@ struct TabButton: View {
|
||||
ZStack(alignment: .center) {
|
||||
Tab
|
||||
|
||||
if show_indicator(timeline: timeline, current: new_events, indicator_setting: settings.notification_indicators) {
|
||||
if show_indicator(timeline: timeline, current: nstatus.new_events, indicator_setting: settings.notification_indicators) {
|
||||
Circle()
|
||||
.size(CGSize(width: 8, height: 8))
|
||||
.frame(width: 10, height: 10, alignment: .topTrailing)
|
||||
@@ -53,7 +53,7 @@ struct TabButton: View {
|
||||
Button(action: {
|
||||
action(timeline)
|
||||
let bits = timeline_to_notification_bits(timeline, ev: nil)
|
||||
new_events = NewEventsBits(rawValue: new_events.rawValue & ~bits.rawValue)
|
||||
nstatus.new_events = NewEventsBits(rawValue: nstatus.new_events.rawValue & ~bits.rawValue)
|
||||
}) {
|
||||
Image(selected != timeline ? img : "\(img).fill")
|
||||
.contentShape(Rectangle())
|
||||
@@ -65,7 +65,7 @@ struct TabButton: View {
|
||||
|
||||
|
||||
struct TabBar: View {
|
||||
@Binding var new_events: NewEventsBits
|
||||
var nstatus: NotificationStatusModel
|
||||
@Binding var selected: Timeline
|
||||
|
||||
let settings: UserSettingsStore
|
||||
@@ -75,10 +75,10 @@ struct TabBar: View {
|
||||
VStack {
|
||||
Divider()
|
||||
HStack {
|
||||
TabButton(timeline: .home, img: "home", selected: $selected, new_events: $new_events, settings: settings, action: action).keyboardShortcut("1")
|
||||
TabButton(timeline: .dms, img: "messages", selected: $selected, new_events: $new_events, settings: settings, action: action).keyboardShortcut("2")
|
||||
TabButton(timeline: .search, img: "search", selected: $selected, new_events: $new_events, settings: settings, action: action).keyboardShortcut("3")
|
||||
TabButton(timeline: .notifications, img: "notification-bell", selected: $selected, new_events: $new_events, settings: settings, action: action).keyboardShortcut("4")
|
||||
TabButton(timeline: .home, img: "home", selected: $selected, nstatus: nstatus, settings: settings, action: action).keyboardShortcut("1")
|
||||
TabButton(timeline: .dms, img: "messages", selected: $selected, nstatus: nstatus, settings: settings, action: action).keyboardShortcut("2")
|
||||
TabButton(timeline: .search, img: "search", selected: $selected, nstatus: nstatus, settings: settings, action: action).keyboardShortcut("3")
|
||||
TabButton(timeline: .notifications, img: "notification-bell", selected: $selected, nstatus: nstatus, settings: settings, action: action).keyboardShortcut("4")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ enum UrlType {
|
||||
case .image(let url):
|
||||
return url
|
||||
case .video:
|
||||
return url
|
||||
return nil
|
||||
}
|
||||
case .link:
|
||||
return nil
|
||||
|
||||
@@ -14,6 +14,15 @@ enum EventGroupType {
|
||||
case zap(ZapGroup)
|
||||
case profile_zap(ZapGroup)
|
||||
|
||||
var is_note_zap: Bool {
|
||||
switch self {
|
||||
case .repost: return false
|
||||
case .reaction: return false
|
||||
case .zap: return true
|
||||
case .profile_zap: return false
|
||||
}
|
||||
}
|
||||
|
||||
var zap_group: ZapGroup? {
|
||||
switch self {
|
||||
case .profile_zap(let grp):
|
||||
@@ -42,18 +51,23 @@ enum EventGroupType {
|
||||
}
|
||||
|
||||
enum ReactingTo {
|
||||
case your_post
|
||||
case your_note
|
||||
case tagged_in
|
||||
case your_profile
|
||||
}
|
||||
|
||||
func determine_reacting_to(our_pubkey: String, ev: NostrEvent?) -> ReactingTo {
|
||||
func determine_reacting_to(our_pubkey: String, ev: NostrEvent?, group: EventGroupType, nozaps: Bool) -> ReactingTo {
|
||||
guard let ev else {
|
||||
return .your_profile
|
||||
}
|
||||
|
||||
if nozaps && group.is_note_zap {
|
||||
// ZAPPING NOTES IS NOT ALLOWED!!!! EVIL!!!
|
||||
return .your_profile
|
||||
}
|
||||
|
||||
if ev.pubkey == our_pubkey {
|
||||
return .your_post
|
||||
return .your_note
|
||||
}
|
||||
|
||||
return .tagged_in
|
||||
@@ -72,7 +86,7 @@ func event_group_author_name(profiles: Profiles, ind: Int, group: EventGroupType
|
||||
return NSLocalizedString("Anonymous", comment: "Placeholder author name of the anonymous person who zapped an event.")
|
||||
}
|
||||
|
||||
return event_author_name(profiles: profiles, pubkey: zap.request.pubkey)
|
||||
return event_author_name(profiles: profiles, pubkey: zap.request.ev.pubkey)
|
||||
} else {
|
||||
let ev = group.events[ind]
|
||||
return event_author_name(profiles: profiles, pubkey: ev.pubkey)
|
||||
@@ -89,9 +103,9 @@ func event_group_author_name(profiles: Profiles, ind: Int, group: EventGroupType
|
||||
"reacted_tagged_in_1" - returned when 1 reaction occurred to a post that the current user was tagged in
|
||||
"reacted_tagged_in_2" - returned when 2 reactions occurred to a post that the current user was tagged in
|
||||
"reacted_tagged_in_3" - returned when 3 or more reactions occurred to a post that the current user was tagged in
|
||||
"reacted_your_post_1" - returned when 1 reaction occurred to the current user's post
|
||||
"reacted_your_post_2" - returned when 2 reactions occurred to the current user's post
|
||||
"reacted_your_post_3" - returned when 3 or more reactions occurred to the current user's post
|
||||
"reacted_your_note_1" - returned when 1 reaction occurred to the current user's post
|
||||
"reacted_your_note_2" - returned when 2 reactions occurred to the current user's post
|
||||
"reacted_your_note_3" - returned when 3 or more reactions occurred to the current user's post
|
||||
"reacted_your_profile_1" - returned when 1 reaction occurred to the current user's profile
|
||||
"reacted_your_profile_2" - returned when 2 reactions occurred to the current user's profile
|
||||
"reacted_your_profile_3" - returned when 3 or more reactions occurred to the current user's profile
|
||||
@@ -99,9 +113,9 @@ func event_group_author_name(profiles: Profiles, ind: Int, group: EventGroupType
|
||||
"reposted_tagged_in_1" - returned when 1 repost occurred to a post that the current user was tagged in
|
||||
"reposted_tagged_in_2" - returned when 2 reposts occurred to a post that the current user was tagged in
|
||||
"reposted_tagged_in_3" - returned when 3 or more reposts occurred to a post that the current user was tagged in
|
||||
"reposted_your_post_1" - returned when 1 repost occurred to the current user's post
|
||||
"reposted_your_post_2" - returned when 2 reposts occurred to the current user's post
|
||||
"reposted_your_post_3" - returned when 3 or more reposts occurred to the current user's post
|
||||
"reposted_your_note_1" - returned when 1 repost occurred to the current user's post
|
||||
"reposted_your_note_2" - returned when 2 reposts occurred to the current user's post
|
||||
"reposted_your_note_3" - returned when 3 or more reposts occurred to the current user's post
|
||||
"reposted_your_profile_1" - returned when 1 repost occurred to the current user's profile
|
||||
"reposted_your_profile_2" - returned when 2 reposts occurred to the current user's profile
|
||||
"reposted_your_profile_3" - returned when 3 or more reposts occurred to the current user's profile
|
||||
@@ -109,20 +123,20 @@ func event_group_author_name(profiles: Profiles, ind: Int, group: EventGroupType
|
||||
"zapped_tagged_in_1" - returned when 1 zap occurred to a post that the current user was tagged in
|
||||
"zapped_tagged_in_2" - returned when 2 zaps occurred to a post that the current user was tagged in
|
||||
"zapped_tagged_in_3" - returned when 3 or more zaps occurred to a post that the current user was tagged in
|
||||
"zapped_your_post_1" - returned when 1 zap occurred to the current user's post
|
||||
"zapped_your_post_2" - returned when 2 zaps occurred to the current user's post
|
||||
"zapped_your_post_3" - returned when 3 or more zaps occurred to the current user's post
|
||||
"zapped_your_note_1" - returned when 1 zap occurred to the current user's post
|
||||
"zapped_your_note_2" - returned when 2 zaps occurred to the current user's post
|
||||
"zapped_your_note_3" - returned when 3 or more zaps occurred to the current user's post
|
||||
"zapped_your_profile_1" - returned when 1 zap occurred to the current user's profile
|
||||
"zapped_your_profile_2" - returned when 2 zaps occurred to the current user's profile
|
||||
"zapped_your_profile_3" - returned when 3 or more zaps occurred to the current user's profile
|
||||
*/
|
||||
func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?, locale: Locale? = nil) -> String {
|
||||
func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?, nozaps: Bool, locale: Locale? = nil) -> String {
|
||||
if group.events.count == 0 {
|
||||
return "??"
|
||||
}
|
||||
|
||||
let verb = reacting_to_verb(group: group)
|
||||
let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev)
|
||||
let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev, group: group, nozaps: nozaps)
|
||||
let localization_key = "\(verb)_\(reacting_to)_\(min(group.events.count, 3))"
|
||||
let format = localizedStringFormat(key: localization_key, locale: locale)
|
||||
|
||||
@@ -162,7 +176,7 @@ struct EventGroupView: View {
|
||||
let group: EventGroupType
|
||||
|
||||
var GroupDescription: some View {
|
||||
Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event))")
|
||||
Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event, nozaps: state.settings.nozaps))")
|
||||
}
|
||||
|
||||
func ZapIcon(_ zapgrp: ZapGroup) -> some View {
|
||||
@@ -207,16 +221,18 @@ struct EventGroupView: View {
|
||||
if let event {
|
||||
let thread = ThreadModel(event: event, damus_state: state)
|
||||
let dest = ThreadView(state: state, thread: thread)
|
||||
NavigationLink(destination: dest) {
|
||||
VStack(alignment: .leading) {
|
||||
GroupDescription
|
||||
EventBody(damus_state: state, event: event, size: .normal, options: [.truncate_content])
|
||||
.padding([.top], 1)
|
||||
.padding([.trailing])
|
||||
.foregroundColor(.gray)
|
||||
GroupDescription
|
||||
if !state.settings.nozaps || !group.is_note_zap {
|
||||
NavigationLink(destination: dest) {
|
||||
VStack(alignment: .leading) {
|
||||
EventBody(damus_state: state, event: event, size: .normal, options: [.truncate_content])
|
||||
.padding([.top], 1)
|
||||
.padding([.trailing])
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
} else {
|
||||
GroupDescription
|
||||
}
|
||||
|
||||
@@ -51,21 +51,7 @@ struct ParticipantsView: View {
|
||||
ForEach(originalReferences.pRefs) { participant in
|
||||
let pubkey = participant.id
|
||||
HStack {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_nip5_domain: false)
|
||||
if let about = profile?.about {
|
||||
let blocks = parse_mentions(content: about, tags: [])
|
||||
let about_string = render_blocks(blocks: blocks, profiles: damus_state.profiles).content.attributed
|
||||
Text(about_string)
|
||||
.lineLimit(3)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
UserView(damus_state: damus_state, pubkey: pubkey)
|
||||
|
||||
Image("check-circle.fill")
|
||||
.font(.system(size: 30))
|
||||
|
||||
@@ -13,7 +13,7 @@ enum NostrPostResult {
|
||||
case cancel
|
||||
}
|
||||
|
||||
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
|
||||
let POST_PLACEHOLDER = NSLocalizedString("Type your note here...", comment: "Text box prompt to ask user to type their note.")
|
||||
|
||||
class TagModel: ObservableObject {
|
||||
var diff = 0
|
||||
@@ -83,7 +83,9 @@ struct PostView: View {
|
||||
}
|
||||
}
|
||||
|
||||
var content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
var content = self.post.string
|
||||
.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
.replacingOccurrences(of: "\u{200B}", with: "") // these characters are added when adding mentions.
|
||||
|
||||
let imagesString = uploadedMedias.map { $0.uploadedURL.absoluteString }.joined(separator: " ")
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ struct UserSearch: View {
|
||||
|
||||
let tagAttributedString = NSMutableAttributedString(string: tagString,
|
||||
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
||||
NSAttributedString.Key.link: "@\(pk)"])
|
||||
NSAttributedString.Key.link: "nostr:\(pk)"])
|
||||
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// AboutView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-06-18.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AboutView: View {
|
||||
let state: DamusState
|
||||
let about: String
|
||||
let max_about_length = 280
|
||||
@State var show_full_about: Bool = false
|
||||
@State private var about_string: AttributedString? = nil
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let about_string {
|
||||
let truncated_about = show_full_about ? about_string : about_string.truncateOrNil(maxLength: max_about_length)
|
||||
SelectableText(attributedString: truncated_about ?? about_string, size: .subheadline)
|
||||
|
||||
if truncated_about != nil {
|
||||
if show_full_about {
|
||||
Button(NSLocalizedString("Show less", comment: "Button to show less of a long profile description.")) {
|
||||
show_full_about = false
|
||||
}
|
||||
.font(.footnote)
|
||||
} else {
|
||||
Button(NSLocalizedString("Show more", comment: "Button to show more of a long profile description.")) {
|
||||
show_full_about = true
|
||||
}
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(verbatim: "")
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
let blocks = parse_mentions(content: about, tags: [])
|
||||
about_string = render_blocks(blocks: blocks, profiles: state.profiles).content.attributed
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#Preview {
|
||||
AboutView()
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// CondensedProfilePicturesView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Terry Yiu on 6/19/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CondensedProfilePicturesView: View {
|
||||
let state: DamusState
|
||||
let pubkeys: [String]
|
||||
let maxPictures: Int
|
||||
|
||||
init(state: DamusState, pubkeys: [String], maxPictures: Int) {
|
||||
self.state = state
|
||||
self.pubkeys = pubkeys
|
||||
self.maxPictures = min(maxPictures, pubkeys.count)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
// Using ZStack to make profile pictures floating and stacked on top of each other.
|
||||
ZStack {
|
||||
ForEach((0..<maxPictures).reversed(), id: \.self) { index in
|
||||
ProfilePicView(pubkey: pubkeys[index], size: 32.0, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation)
|
||||
.offset(x: CGFloat(index) * 20)
|
||||
}
|
||||
}
|
||||
// Padding is needed so that other components drawn adjacent to this view don't get drawn on top.
|
||||
.padding(.trailing, CGFloat((maxPictures - 1) * 20))
|
||||
}
|
||||
}
|
||||
|
||||
struct CondensedProfilePicturesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CondensedProfilePicturesView(state: test_damus_state(), pubkeys: ["a", "b", "c", "d"], maxPictures: 3)
|
||||
}
|
||||
}
|
||||
@@ -103,13 +103,15 @@ struct EditMetadataView: View {
|
||||
TopSection
|
||||
Form {
|
||||
Section(NSLocalizedString("Your Name", comment: "Label for Your Name section of user profile form.")) {
|
||||
TextField("Satoshi Nakamoto", text: $display_name)
|
||||
let display_name_placeholder = "Satoshi Nakamoto"
|
||||
TextField(display_name_placeholder, text: $display_name)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
}
|
||||
|
||||
Section(NSLocalizedString("Username", comment: "Label for Username section of user profile form.")) {
|
||||
TextField("satoshi", text: $name)
|
||||
let username_placeholder = "satoshi"
|
||||
TextField(username_placeholder, text: $name)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ struct InnerProfilePicView: View {
|
||||
var Placeholder: some View {
|
||||
Circle()
|
||||
.frame(width: size, height: size)
|
||||
.foregroundColor(DamusColors.mediumGrey)
|
||||
.overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight)))
|
||||
.padding(2)
|
||||
}
|
||||
|
||||
@@ -46,6 +46,31 @@ func relaysCountString(_ count: Int, locale: Locale = Locale.current) -> String
|
||||
return String(format: format, locale: locale, count)
|
||||
}
|
||||
|
||||
func followedByString(_ friend_intersection: [String], profiles: Profiles, locale: Locale = Locale.current) -> String {
|
||||
let bundle = bundleForLocale(locale: locale)
|
||||
let names: [String] = friend_intersection.prefix(3).map {
|
||||
let profile = profiles.lookup(id: $0)
|
||||
return Profile.displayName(profile: profile, pubkey: $0).username.truncate(maxLength: 20)
|
||||
}
|
||||
|
||||
switch friend_intersection.count {
|
||||
case 0:
|
||||
return ""
|
||||
case 1:
|
||||
let format = NSLocalizedString("Followed by %@", bundle: bundle, comment: "Text to indicate that the user is followed by one of our follows.")
|
||||
return String(format: format, locale: locale, names[0])
|
||||
case 2:
|
||||
let format = NSLocalizedString("Followed by %@ & %@", bundle: bundle, comment: "Text to indicate that the user is followed by two of our follows.")
|
||||
return String(format: format, locale: locale, names[0], names[1])
|
||||
case 3:
|
||||
let format = NSLocalizedString("Followed by %@, %@ & %@", bundle: bundle, comment: "Text to indicate that the user is followed by three of our follows.")
|
||||
return String(format: format, locale: locale, names[0], names[1], names[2])
|
||||
default:
|
||||
let format = localizedStringFormat(key: "followed_by_three_and_others", locale: locale)
|
||||
return String(format: format, locale: locale, friend_intersection.count - 3, names[0], names[1], names[2])
|
||||
}
|
||||
}
|
||||
|
||||
struct EditButton: View {
|
||||
let damus_state: DamusState
|
||||
|
||||
@@ -93,18 +118,15 @@ struct ProfileView: View {
|
||||
let damus_state: DamusState
|
||||
let pfp_size: CGFloat = 90.0
|
||||
let bannerHeight: CGFloat = 150.0
|
||||
let max_about_length = 280
|
||||
|
||||
static let markdown = Markdown()
|
||||
|
||||
@State var showing_select_wallet: Bool = false
|
||||
@State var is_zoomed: Bool = false
|
||||
@State var show_share_sheet: Bool = false
|
||||
@State var show_qr_code: Bool = false
|
||||
@State var action_sheet_presented: Bool = false
|
||||
@State var filter_state : FilterState = .posts
|
||||
@State var yOffset: CGFloat = 0
|
||||
@State var show_full_about: Bool = false
|
||||
|
||||
@StateObject var profile: ProfileModel
|
||||
@StateObject var followers: FollowersModel
|
||||
@@ -247,7 +269,7 @@ struct ProfileView: View {
|
||||
func lnButton(lnurl: String, profile: Profile) -> some View {
|
||||
let button_img = profile.reactions == false ? "zap.fill" : "zap"
|
||||
return Button(action: {
|
||||
zap_button_model.showing_zap_customizer = true
|
||||
present_sheet(.zap(target: .profile(self.profile.pubkey), lnurl: lnurl))
|
||||
}) {
|
||||
Image(button_img)
|
||||
.foregroundColor(button_img == "zap.fill" ? .orange : Color.primary)
|
||||
@@ -274,38 +296,6 @@ struct ProfileView: View {
|
||||
|
||||
}
|
||||
.cornerRadius(24)
|
||||
.sheet(isPresented: $zap_button_model.showing_zap_customizer) {
|
||||
CustomizeZapView(state: damus_state, target: ZapTarget.profile(self.profile.pubkey), lnurl: lnurl)
|
||||
}
|
||||
.sheet(isPresented: $zap_button_model.showing_select_wallet, onDismiss: {zap_button_model.showing_select_wallet = false}) {
|
||||
SelectWalletView(default_wallet: damus_state.settings.default_wallet, showingSelectWallet: $zap_button_model.showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: zap_button_model.invoice ?? "")
|
||||
}
|
||||
.onReceive(handle_notify(.zapping)) { notif in
|
||||
let zap_ev = notif.object as! ZappingEvent
|
||||
|
||||
guard zap_ev.target.id == self.profile.pubkey else {
|
||||
return
|
||||
}
|
||||
|
||||
guard !zap_ev.is_custom else {
|
||||
return
|
||||
}
|
||||
|
||||
switch zap_ev.type {
|
||||
case .failed:
|
||||
break
|
||||
case .got_zap_invoice(let inv):
|
||||
if damus_state.settings.show_wallet_selector {
|
||||
zap_button_model.invoice = inv
|
||||
zap_button_model.showing_select_wallet = true
|
||||
} else {
|
||||
let wallet = damus_state.settings.default_wallet.model
|
||||
open_with_wallet(wallet: wallet, invoice: inv)
|
||||
}
|
||||
case .sent_from_nwc:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dmButton: some View {
|
||||
@@ -403,28 +393,7 @@ struct ProfileView: View {
|
||||
nameSection(profile_data: profile_data)
|
||||
|
||||
if let about = profile_data?.about {
|
||||
let blocks = parse_mentions(content: about, tags: [])
|
||||
let about_string = render_blocks(blocks: blocks, profiles: damus_state.profiles).content.attributed
|
||||
let truncated_about = show_full_about ? about_string : about_string.truncateOrNil(maxLength: max_about_length)
|
||||
|
||||
SelectableText(attributedString: truncated_about ?? about_string, size: .subheadline)
|
||||
|
||||
if truncated_about != nil {
|
||||
if show_full_about {
|
||||
Button(NSLocalizedString("Show less", comment: "Button to show less of a long profile description.")) {
|
||||
show_full_about = false
|
||||
}
|
||||
.font(.footnote)
|
||||
} else {
|
||||
Button(NSLocalizedString("Show more", comment: "Button to show more of a long profile description.")) {
|
||||
show_full_about = true
|
||||
}
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(verbatim: "")
|
||||
.font(.subheadline)
|
||||
AboutView(state: damus_state, about: about)
|
||||
}
|
||||
|
||||
if let url = profile_data?.website_url {
|
||||
@@ -435,7 +404,7 @@ struct ProfileView: View {
|
||||
if let contact = profile.contacts {
|
||||
let contacts = contact.referenced_pubkeys.map { $0.ref_id }
|
||||
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
|
||||
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) {
|
||||
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model)) {
|
||||
HStack {
|
||||
let noun_text = Text(verbatim: "\(followingCountString(profile.following))").font(.subheadline).foregroundColor(.gray)
|
||||
Text("\(Text(verbatim: profile.following.formatted()).font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
|
||||
@@ -443,7 +412,7 @@ struct ProfileView: View {
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
let fview = FollowersView(damus_state: damus_state, whos: profile.pubkey)
|
||||
let fview = FollowersView(damus_state: damus_state)
|
||||
.environmentObject(followers)
|
||||
if followers.contacts != nil {
|
||||
NavigationLink(destination: fview) {
|
||||
@@ -476,65 +445,89 @@ struct ProfileView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if profile.pubkey != damus_state.pubkey {
|
||||
let friended_followers = damus_state.contacts.get_friended_followers(profile.pubkey)
|
||||
if !friended_followers.isEmpty {
|
||||
Spacer()
|
||||
|
||||
NavigationLink(destination: FollowersYouKnowView(damus_state: damus_state, friended_followers: friended_followers)) {
|
||||
HStack {
|
||||
CondensedProfilePicturesView(state: damus_state, pubkeys: friended_followers, maxPictures: 3)
|
||||
Text(followedByString(friended_followers, profiles: damus_state.profiles))
|
||||
.font(.subheadline).foregroundColor(.gray)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView(.vertical) {
|
||||
VStack(spacing: 0) {
|
||||
bannerSection
|
||||
.zIndex(1)
|
||||
|
||||
VStack() {
|
||||
aboutSection
|
||||
|
||||
VStack(spacing: 0) {
|
||||
CustomPicker(selection: $filter_state, content: {
|
||||
Text("Posts", comment: "Label for filter for seeing only your posts (instead of posts and replies).").tag(FilterState.posts)
|
||||
Text("Posts & Replies", comment: "Label for filter for seeing your posts and replies (instead of only your posts).").tag(FilterState.posts_and_replies)
|
||||
})
|
||||
Divider()
|
||||
.frame(height: 1)
|
||||
}
|
||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||
ZStack {
|
||||
ScrollView(.vertical) {
|
||||
VStack(spacing: 0) {
|
||||
bannerSection
|
||||
.zIndex(1)
|
||||
|
||||
if filter_state == FilterState.posts {
|
||||
InnerTimelineView(events: profile.events, damus: damus_state, filter: FilterState.posts.filter)
|
||||
VStack() {
|
||||
aboutSection
|
||||
|
||||
VStack(spacing: 0) {
|
||||
CustomPicker(selection: $filter_state, content: {
|
||||
Text("Notes", comment: "Label for filter for seeing only your notes (instead of notes and replies).").tag(FilterState.posts)
|
||||
Text("Notes & Replies", comment: "Label for filter for seeing your notes and replies (instead of only your notes).").tag(FilterState.posts_and_replies)
|
||||
})
|
||||
Divider()
|
||||
.frame(height: 1)
|
||||
}
|
||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||
|
||||
if filter_state == FilterState.posts {
|
||||
InnerTimelineView(events: profile.events, damus: damus_state, filter: FilterState.posts.filter)
|
||||
}
|
||||
if filter_state == FilterState.posts_and_replies {
|
||||
InnerTimelineView(events: profile.events, damus: damus_state, filter: FilterState.posts_and_replies.filter)
|
||||
}
|
||||
}
|
||||
if filter_state == FilterState.posts_and_replies {
|
||||
InnerTimelineView(events: profile.events, damus: damus_state, filter: FilterState.posts_and_replies.filter)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, Theme.safeAreaInsets?.left)
|
||||
.zIndex(-yOffset > navbarHeight ? 0 : 1)
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.overlay(customNavbar, alignment: .top)
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
.onAppear() {
|
||||
profile.subscribe()
|
||||
//followers.subscribe()
|
||||
}
|
||||
.onDisappear {
|
||||
profile.unsubscribe()
|
||||
followers.unsubscribe()
|
||||
// our profilemodel needs a bit more help
|
||||
}
|
||||
.sheet(isPresented: $show_share_sheet) {
|
||||
if let npub = bech32_pubkey(profile.pubkey) {
|
||||
if let url = URL(string: "https://damus.io/" + npub) {
|
||||
ShareSheet(activityItems: [url])
|
||||
.padding(.horizontal, Theme.safeAreaInsets?.left)
|
||||
.zIndex(-yOffset > navbarHeight ? 0 : 1)
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.overlay(customNavbar, alignment: .top)
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
.onAppear() {
|
||||
profile.subscribe()
|
||||
//followers.subscribe()
|
||||
}
|
||||
.onDisappear {
|
||||
profile.unsubscribe()
|
||||
followers.unsubscribe()
|
||||
// our profilemodel needs a bit more help
|
||||
}
|
||||
.sheet(isPresented: $show_share_sheet) {
|
||||
if let npub = bech32_pubkey(profile.pubkey) {
|
||||
if let url = URL(string: "https://damus.io/" + npub) {
|
||||
ShareSheet(activityItems: [url])
|
||||
}
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $show_qr_code) {
|
||||
QRCodeView(damus_state: damus_state, pubkey: profile.pubkey)
|
||||
}
|
||||
|
||||
if profile.pubkey == damus_state.pubkey && damus_state.is_privkey_user {
|
||||
PostButtonContainer(is_left_handed: damus_state.settings.left_handed) {
|
||||
notify(.compose, PostAction.posting)
|
||||
}
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $show_qr_code) {
|
||||
QRCodeView(damus_state: damus_state, pubkey: profile.pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+260
-80
@@ -8,11 +8,56 @@
|
||||
import SwiftUI
|
||||
import CoreImage.CIFilterBuiltins
|
||||
|
||||
struct ProfileScanResult: Equatable {
|
||||
let pubkey: String
|
||||
|
||||
init(hex: String) {
|
||||
self.pubkey = hex
|
||||
}
|
||||
|
||||
init?(string: String) {
|
||||
var str = string
|
||||
guard str.count != 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if str.hasPrefix("nostr:") {
|
||||
str.removeFirst("nostr:".count)
|
||||
}
|
||||
|
||||
if let _ = hex_decode(str), str.count == 64 {
|
||||
self = .init(hex: str)
|
||||
return
|
||||
}
|
||||
|
||||
if str.starts(with: "npub"), let b32 = try? bech32_decode(str) {
|
||||
let hex = hex_encode(b32.data)
|
||||
self = .init(hex: hex)
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
struct QRCodeView: View {
|
||||
let damus_state: DamusState
|
||||
@State var pubkey: String
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
@State private var selectedTab = 0
|
||||
|
||||
@State var scanResult: ProfileScanResult? = nil
|
||||
|
||||
@State var showProfileView: Bool = false
|
||||
@State var profile: Profile? = nil
|
||||
@State var error: String? = nil
|
||||
|
||||
@State private var outerTrimEnd: CGFloat = 0
|
||||
var animationDuration: Double = 0.5
|
||||
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
|
||||
var maybe_key: String? {
|
||||
guard let key = bech32_pubkey(pubkey) else {
|
||||
@@ -22,87 +67,221 @@ struct QRCodeView: View {
|
||||
return key
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .center) {
|
||||
|
||||
ZStack(alignment: .topLeading) {
|
||||
DamusGradient()
|
||||
Button {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} label: {
|
||||
Image("close")
|
||||
.foregroundColor(.white)
|
||||
.font(.subheadline)
|
||||
.padding(.leading, 20)
|
||||
}
|
||||
.zIndex(1)
|
||||
}
|
||||
|
||||
VStack(alignment: .center) {
|
||||
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
|
||||
if (damus_state.profiles.lookup(id: pubkey)?.picture) != nil {
|
||||
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 4.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||
.padding(.top, 50)
|
||||
} else {
|
||||
Image(systemName: "person.fill")
|
||||
.font(.system(size: 60))
|
||||
.foregroundColor(DamusColors.white)
|
||||
.padding(.top, 50)
|
||||
}
|
||||
|
||||
if let display_name = profile?.display_name {
|
||||
Text(display_name)
|
||||
.foregroundColor(DamusColors.white)
|
||||
.font(.system(size: 24, weight: .heavy))
|
||||
}
|
||||
if let name = profile?.name {
|
||||
Text("@" + name)
|
||||
.foregroundColor(DamusColors.white)
|
||||
.font(.body)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if let key = maybe_key {
|
||||
Image(uiImage: generateQRCode(pubkey: "nostr:" + key))
|
||||
.interpolation(.none)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 200, height: 200)
|
||||
.padding()
|
||||
.cornerRadius(10)
|
||||
.overlay(RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(DamusColors.white, lineWidth: 1))
|
||||
.shadow(radius: 10)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if (pubkey == damus_state.pubkey) {
|
||||
Text("Follow me on nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")
|
||||
.foregroundColor(DamusColors.white)
|
||||
.font(.system(size: 24, weight: .heavy))
|
||||
.padding(.top)
|
||||
} else {
|
||||
Text("Follow them on nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user (someone else).")
|
||||
.foregroundColor(DamusColors.white)
|
||||
.font(.system(size: 24, weight: .heavy))
|
||||
.padding(.top)
|
||||
}
|
||||
|
||||
Text("Scan the code", comment: "Text on QR code view to prompt viewer to scan the QR code on screen with their device camera.")
|
||||
.foregroundColor(DamusColors.white)
|
||||
.font(.system(size: 18, weight: .ultraLight))
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
}
|
||||
.modifier(SwipeToDismissModifier(minDistance: nil, onDismiss: {
|
||||
@ViewBuilder
|
||||
func navImage(systemImage: String) -> some View {
|
||||
Image(systemName: systemImage)
|
||||
.frame(width: 33, height: 33)
|
||||
.background(Color.black.opacity(0.6))
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
var navBackButton: some View {
|
||||
Button {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}))
|
||||
} label: {
|
||||
navImage(systemImage: "chevron.left")
|
||||
}
|
||||
}
|
||||
|
||||
var customNavbar: some View {
|
||||
HStack {
|
||||
navBackButton
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 5)
|
||||
.padding(.horizontal)
|
||||
.accentColor(DamusColors.white)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .topLeading) {
|
||||
DamusGradient()
|
||||
}
|
||||
TabView(selection: $selectedTab) {
|
||||
QRView
|
||||
.tag(0)
|
||||
if pubkey == damus_state.pubkey {
|
||||
QRCameraView()
|
||||
.tag(1)
|
||||
}
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
||||
.onAppear {
|
||||
UIScrollView.appearance().isScrollEnabled = false
|
||||
}
|
||||
.gesture(
|
||||
DragGesture()
|
||||
.onChanged { _ in }
|
||||
)
|
||||
}
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.overlay(customNavbar, alignment: .top)
|
||||
}
|
||||
|
||||
var QRView: some View {
|
||||
VStack(alignment: .center) {
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
|
||||
if (damus_state.profiles.lookup(id: damus_state.pubkey)?.picture) != nil {
|
||||
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
||||
.padding(.top, 50)
|
||||
} else {
|
||||
Image(systemName: "person.fill")
|
||||
.font(.system(size: 60))
|
||||
.padding(.top, 50)
|
||||
}
|
||||
|
||||
if let display_name = profile?.display_name {
|
||||
Text(display_name)
|
||||
.font(.system(size: 24, weight: .heavy))
|
||||
}
|
||||
if let name = profile?.name {
|
||||
Text("@" + name)
|
||||
.font(.body)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if let key = maybe_key {
|
||||
Image(uiImage: generateQRCode(pubkey: "nostr:" + key))
|
||||
.interpolation(.none)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 300, height: 300)
|
||||
.cornerRadius(10)
|
||||
.overlay(RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(DamusColors.white, lineWidth: 5.0))
|
||||
.shadow(radius: 10)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Follow me on Nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")
|
||||
.font(.system(size: 24, weight: .heavy))
|
||||
.padding(.top)
|
||||
|
||||
Text("Scan the code", comment: "Text on QR code view to prompt viewer to scan the QR code on screen with their device camera.")
|
||||
.font(.system(size: 18, weight: .ultraLight))
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
selectedTab = 1
|
||||
}) {
|
||||
HStack {
|
||||
Text("Scan Code", comment: "Button to switch to scan QR Code page.")
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||
}
|
||||
.buttonStyle(GradientButtonStyle())
|
||||
.padding(50)
|
||||
}
|
||||
}
|
||||
|
||||
func QRCameraView() -> some View {
|
||||
return VStack(alignment: .center) {
|
||||
Text("Scan a user's pubkey", comment: "Text to prompt scanning a QR code of a user's pubkey to open their profile.")
|
||||
.padding(.top, 50)
|
||||
.font(.system(size: 24, weight: .heavy))
|
||||
|
||||
Spacer()
|
||||
|
||||
CodeScannerView(codeTypes: [.qr], scanMode: .continuous, simulatedData: "npub1k92qsr95jcumkpu6dffurkvwwycwa2euvx4fthv78ru7gqqz0nrs2ngfwd", shouldVibrateOnSuccess: false) { result in
|
||||
switch result {
|
||||
case .success(let success):
|
||||
handleProfileScan(success.string)
|
||||
case .failure(let failure):
|
||||
self.error = failure.localizedDescription
|
||||
}
|
||||
}
|
||||
.scaledToFit()
|
||||
.frame(width: 300, height: 300)
|
||||
.cornerRadius(10)
|
||||
.overlay(RoundedRectangle(cornerRadius: 10).stroke(DamusColors.white, lineWidth: 5.0))
|
||||
.overlay(RoundedRectangle(cornerRadius: 10).trim(from: 0.0, to: outerTrimEnd).stroke(DamusColors.black, lineWidth: 5.5)
|
||||
.rotationEffect(.degrees(-90)))
|
||||
.shadow(radius: 10)
|
||||
|
||||
Spacer()
|
||||
|
||||
if let scanResult {
|
||||
let dst = ProfileView(damus_state: damus_state, pubkey: scanResult.pubkey)
|
||||
NavigationLink(destination: dst, isActive: $showProfileView) {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
selectedTab = 0
|
||||
}) {
|
||||
HStack {
|
||||
Text("View QR Code", comment: "Button to switch to view users QR Code")
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.frame( maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||
}
|
||||
.buttonStyle(GradientButtonStyle())
|
||||
.padding(50)
|
||||
}
|
||||
}
|
||||
|
||||
func handleProfileScan(_ scanned_str: String) {
|
||||
guard let result = ProfileScanResult(string: scanned_str) else {
|
||||
self.error = "Invalid profile QR"
|
||||
return
|
||||
}
|
||||
|
||||
self.error = nil
|
||||
|
||||
guard result != self.scanResult else {
|
||||
return
|
||||
}
|
||||
|
||||
generator.impactOccurred()
|
||||
cameraAnimate {
|
||||
scanResult = result
|
||||
|
||||
find_event(state: damus_state, query: .profile(pubkey: result.pubkey)) { res in
|
||||
guard let res else {
|
||||
error = "Profile not found"
|
||||
return
|
||||
}
|
||||
|
||||
switch res {
|
||||
case .invalid_profile:
|
||||
error = "Profile was found but was corrupt."
|
||||
|
||||
case .profile:
|
||||
show_profile_after_delay()
|
||||
|
||||
case .event:
|
||||
print("invalid search result")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func show_profile_after_delay() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration) {
|
||||
showProfileView = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func cameraAnimate(completion: @escaping () -> Void) {
|
||||
outerTrimEnd = 0.0
|
||||
withAnimation(.easeInOut(duration: animationDuration)) {
|
||||
outerTrimEnd = 1.05 // Set to 1.05 instead of 1.0 since sometimes `completion()` runs before the value reaches 1.0. This ensures the animation is done.
|
||||
}
|
||||
completion()
|
||||
}
|
||||
|
||||
func generateQRCode(pubkey: String) -> UIImage {
|
||||
@@ -130,3 +309,4 @@ struct QRCodeView_Previews: PreviewProvider {
|
||||
QRCodeView(damus_state: test_damus_state(), pubkey: test_event.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,11 +75,13 @@ struct RelayDetailView: View {
|
||||
UserViewRow(damus_state: state, pubkey: pubkey)
|
||||
}
|
||||
}
|
||||
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
|
||||
HStack {
|
||||
Text(relay)
|
||||
Spacer()
|
||||
RelayStatus(pool: state.pool, relay: relay)
|
||||
if let relay_connection {
|
||||
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
|
||||
HStack {
|
||||
Text(relay)
|
||||
Spacer()
|
||||
RelayStatusView(connection: relay_connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
if nip11.is_paid {
|
||||
@@ -88,7 +90,7 @@ struct RelayDetailView: View {
|
||||
}, header: {
|
||||
Text("Paid Relay", comment: "Section header that indicates the relay server requires payment.")
|
||||
}, footer: {
|
||||
Text("This is a paid relay, you must pay for posts to be accepted.", comment: "Footer description that explains that the relay server requires payment to post.")
|
||||
Text("This is a paid relay, you must pay for notes to be accepted.", comment: "Footer description that explains that the relay server requires payment to post.")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -134,6 +136,10 @@ struct RelayDetailView: View {
|
||||
}
|
||||
return attrString
|
||||
}
|
||||
|
||||
private var relay_connection: RelayConnection? {
|
||||
state.pool.get_relay(relay)?.connection
|
||||
}
|
||||
}
|
||||
|
||||
struct RelayDetailView_Previews: PreviewProvider {
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
//
|
||||
// RelayStatus.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-10.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RelayStatus: View {
|
||||
let pool: RelayPool
|
||||
let relay: String
|
||||
|
||||
let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
|
||||
|
||||
@State var conn_color: Color = .gray
|
||||
@State var conn_image: String = "network"
|
||||
@State var connecting: Bool = false
|
||||
|
||||
func update_connection() {
|
||||
for relay in pool.relays {
|
||||
if relay.id == self.relay {
|
||||
let c = relay.connection
|
||||
if c.isConnected {
|
||||
conn_image = "globe"
|
||||
conn_color = .green
|
||||
} else if c.isConnecting {
|
||||
connecting = true
|
||||
} else {
|
||||
conn_image = "warning.fill"
|
||||
conn_color = .red
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if connecting {
|
||||
ProgressView()
|
||||
.frame(width: 20, height: 20)
|
||||
.padding(.trailing, 5)
|
||||
} else {
|
||||
Image(conn_image)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundColor(conn_color)
|
||||
.padding(.trailing, 5)
|
||||
}
|
||||
}
|
||||
.onReceive(timer) { _ in
|
||||
update_connection()
|
||||
}
|
||||
.onAppear() {
|
||||
update_connection()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct RelayStatus_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RelayStatus(pool: test_damus_state().pool, relay: "relay")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// RelayStatusView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-10.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RelayStatusView: View {
|
||||
@ObservedObject var connection: RelayConnection
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if connection.isConnecting {
|
||||
ProgressView()
|
||||
} else {
|
||||
Image(connection.isConnected ? "globe" : "warning.fill")
|
||||
.resizable()
|
||||
.foregroundColor(connection.isConnected ? .green : .red)
|
||||
}
|
||||
}
|
||||
.frame(width: 20, height: 20)
|
||||
.padding(.trailing, 5)
|
||||
}
|
||||
}
|
||||
|
||||
struct RelayStatusView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let connection = test_damus_state().pool.get_relay("relay")!.connection
|
||||
RelayStatusView(connection: connection)
|
||||
}
|
||||
}
|
||||
@@ -26,12 +26,18 @@ struct RelayToggle: View {
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
RelayStatus(pool: state.pool, relay: relay_id)
|
||||
if let relay_connection {
|
||||
RelayStatusView(connection: relay_connection)
|
||||
}
|
||||
RelayType(is_paid: state.relay_metadata.lookup(relay_id: relay_id)?.is_paid ?? false)
|
||||
Toggle(relay_id, isOn: toggle_binding(relay_id: relay_id))
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
|
||||
private var relay_connection: RelayConnection? {
|
||||
state.pool.get_relay(relay_id)?.connection
|
||||
}
|
||||
}
|
||||
|
||||
struct RelayToggle_Previews: PreviewProvider {
|
||||
|
||||
@@ -20,8 +20,8 @@ struct RelayView: View {
|
||||
if showActionButtons {
|
||||
RemoveButton(privkey: privkey, showText: false)
|
||||
}
|
||||
else {
|
||||
RelayStatus(pool: state.pool, relay: relay)
|
||||
else if let relay_connection {
|
||||
RelayStatusView(connection: relay_connection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@ struct RelayView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var relay_connection: RelayConnection? {
|
||||
state.pool.get_relay(relay)?.connection
|
||||
}
|
||||
|
||||
func CopyAction(relay: String) -> some View {
|
||||
Button {
|
||||
UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text")
|
||||
|
||||
@@ -72,24 +72,20 @@ struct SearchingEventView: View {
|
||||
}
|
||||
|
||||
case .event:
|
||||
if let ev = state.events.lookup(evid) {
|
||||
self.search_state = .found(ev)
|
||||
return
|
||||
}
|
||||
find_event(state: state, evid: evid, search_type: search_type, find_from: nil) { ev in
|
||||
if let ev {
|
||||
self.search_state = .found(ev)
|
||||
} else {
|
||||
find_event(state: state, query: .event(evid: evid)) { res in
|
||||
guard case .event(let ev) = res else {
|
||||
self.search_state = .not_found
|
||||
return
|
||||
}
|
||||
self.search_state = .found(ev)
|
||||
}
|
||||
case .profile:
|
||||
find_event(state: state, evid: evid, search_type: search_type, find_from: nil) { ev in
|
||||
if state.profiles.lookup(id: evid) != nil {
|
||||
self.search_state = .found_profile(evid)
|
||||
} else {
|
||||
find_event(state: state, query: .profile(pubkey: evid)) { res in
|
||||
guard case .profile(_, let ev) = res else {
|
||||
self.search_state = .not_found
|
||||
return
|
||||
}
|
||||
self.search_state = .found_profile(ev.pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import SwiftUI
|
||||
|
||||
struct SelectWalletView: View {
|
||||
let default_wallet: Wallet
|
||||
@Binding var showingSelectWallet: Bool
|
||||
@Binding var active_sheet: Sheets?
|
||||
let our_pubkey: String
|
||||
let invoice: String
|
||||
@State var invoice_copied: Bool = false
|
||||
@@ -59,7 +59,7 @@ struct SelectWalletView: View {
|
||||
}.padding(.vertical, 2.5)
|
||||
}
|
||||
}.navigationBarTitle(Text("Pay the Lightning invoice", comment: "Navigation bar title for view to pay Lightning invoice."), displayMode: .inline).navigationBarItems(trailing: Button(action: {
|
||||
self.showingSelectWallet = false
|
||||
self.active_sheet = nil
|
||||
}) {
|
||||
Text("Done", comment: "Button to dismiss wallet selection view for paying Lightning invoice.").bold()
|
||||
})
|
||||
@@ -68,9 +68,9 @@ struct SelectWalletView: View {
|
||||
}
|
||||
|
||||
struct SelectWalletView_Previews: PreviewProvider {
|
||||
@State static var show: Bool = true
|
||||
@State static var active_sheet: Sheets? = nil
|
||||
|
||||
static var previews: some View {
|
||||
SelectWalletView(default_wallet: .lnlink, showingSelectWallet: $show, our_pubkey: "", invoice: "")
|
||||
SelectWalletView(default_wallet: .lnlink, active_sheet: $active_sheet, our_pubkey: "", invoice: "")
|
||||
}
|
||||
}
|
||||
|
||||
+13
-18
@@ -34,18 +34,13 @@ struct SetupView: View {
|
||||
.shadow(color: DamusColors.purple, radius: 2)
|
||||
.frame(width: 56, height: 56, alignment: .center)
|
||||
.padding(.top, 20.0)
|
||||
|
||||
HStack {
|
||||
Text("Welcome to", comment: "Welcome text shown on the first screen when user is not logged in.")
|
||||
.font(.title)
|
||||
.fontWeight(.heavy)
|
||||
Text("Damus")
|
||||
.font(.title)
|
||||
.fontWeight(.heavy)
|
||||
.foregroundStyle(DamusLogoGradient.gradient)
|
||||
}
|
||||
|
||||
Text("The go-to iOS nostr client", comment: "Quick description of what Damus is")
|
||||
|
||||
Text("Welcome to Damus", comment: "Welcome text shown on the first screen when user is not logged in.")
|
||||
.font(.title)
|
||||
.fontWeight(.heavy)
|
||||
.foregroundStyle(DamusLogoGradient.gradient)
|
||||
|
||||
Text("The go-to iOS Nostr client", comment: "Quick description of what Damus is")
|
||||
.foregroundColor(DamusColors.mediumGrey)
|
||||
.padding(.top, 10)
|
||||
|
||||
@@ -61,7 +56,7 @@ struct SetupView: View {
|
||||
eula.toggle()
|
||||
}) {
|
||||
HStack {
|
||||
Text("Let's get started!", comment: "Button to continue to login page.")
|
||||
Text("Let's get started!", comment: "Button to continue to login page.")
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||
@@ -90,7 +85,7 @@ struct LearnAboutNostrLink: View {
|
||||
Button(action: {
|
||||
openURL(URL(string: "https://nostr.com")!)
|
||||
}, label: {
|
||||
Text("Learn more about nostr")
|
||||
Text("Learn more about Nostr", comment: "Button that opens up a webpage where the user can learn more about Nostr.")
|
||||
.foregroundColor(.accentColor)
|
||||
})
|
||||
|
||||
@@ -106,11 +101,11 @@ struct WhatIsNostr: View {
|
||||
HStack(alignment: .top) {
|
||||
Image("nostr-logo")
|
||||
VStack(alignment: .leading) {
|
||||
Text("What is nostr?")
|
||||
Text("What is Nostr?", comment: "Heading text for section describing what is Nostr.")
|
||||
.fontWeight(.bold)
|
||||
.padding(.vertical, 10)
|
||||
|
||||
Text("Nostr is a protocol, designed for simplicity, that aims to create a censorship-resistant global social network")
|
||||
Text("Nostr is a protocol, designed for simplicity, that aims to create a censorship-resistant global social network", comment: "Description about what is Nostr.")
|
||||
.foregroundColor(DamusColors.mediumGrey)
|
||||
|
||||
LearnAboutNostrLink()
|
||||
@@ -125,11 +120,11 @@ struct WhyWeNeedNostr: View {
|
||||
HStack(alignment: .top) {
|
||||
Image("lightbulb")
|
||||
VStack(alignment: .leading) {
|
||||
Text("Why we need nostr?")
|
||||
Text("Why we need Nostr?", comment: "Heading text for section describing why Nostr is needed.")
|
||||
.fontWeight(.bold)
|
||||
.padding(.vertical, 10)
|
||||
|
||||
Text("Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken")
|
||||
Text("Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken", comment: "Description about why Nostr is needed.")
|
||||
.foregroundColor(DamusColors.mediumGrey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
struct ThreadView: View {
|
||||
let state: DamusState
|
||||
|
||||
@StateObject var thread: ThreadModel
|
||||
@ObservedObject var thread: ThreadModel
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var parent_events: [NostrEvent] {
|
||||
@@ -22,11 +22,13 @@ struct ThreadView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
//let top_zap = get_top_zap(events: state.events, evid: thread.event.id)
|
||||
ScrollViewReader { reader in
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
// MARK: - Parents events view
|
||||
ForEach(parent_events, id: \.id) { parent_event in
|
||||
|
||||
MutedEventView(damus_state: state,
|
||||
event: parent_event,
|
||||
selected: false)
|
||||
@@ -39,6 +41,7 @@ struct ThreadView: View {
|
||||
Divider()
|
||||
.padding(.top, 4)
|
||||
.padding(.leading, 25 * 2)
|
||||
|
||||
}.background(GeometryReader { geometry in
|
||||
// get the height and width of the EventView view
|
||||
let eventHeight = geometry.frame(in: .global).height
|
||||
@@ -59,6 +62,13 @@ struct ThreadView: View {
|
||||
)
|
||||
.id(self.thread.event.id)
|
||||
|
||||
/*
|
||||
if let top_zap {
|
||||
ZapEvent(damus: state, zap: top_zap, is_top_zap: true)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
*/
|
||||
|
||||
ForEach(child_events, id: \.id) { child_event in
|
||||
MutedEventView(
|
||||
damus_state: state,
|
||||
@@ -70,7 +80,7 @@ struct ThreadView: View {
|
||||
thread.set_active_event(child_event)
|
||||
scroll_to_event(scroller: reader, id: child_event.id, delay: 0.1, animate: false)
|
||||
}
|
||||
|
||||
|
||||
Divider()
|
||||
.padding([.top], 4)
|
||||
}
|
||||
|
||||
@@ -15,10 +15,14 @@ struct InnerTimelineView: View {
|
||||
@State var nav_target: NostrEvent
|
||||
@State var navigating: Bool = false
|
||||
|
||||
static var count: Int = 0
|
||||
|
||||
init(events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool) {
|
||||
self.events = events
|
||||
self.state = damus
|
||||
self.filter = filter
|
||||
print("rendering InnerTimelineView \(InnerTimelineView.count)")
|
||||
InnerTimelineView.count += 1
|
||||
// dummy event to avoid MaybeThreadView
|
||||
self._nav_target = State(initialValue: test_event)
|
||||
}
|
||||
|
||||
@@ -31,9 +31,7 @@ struct TimelineView: View {
|
||||
.shimmer(loading)
|
||||
.disabled(loading)
|
||||
.background(GeometryReader { proxy -> Color in
|
||||
DispatchQueue.main.async {
|
||||
handle_scroll_queue(proxy, queue: self.events)
|
||||
}
|
||||
handle_scroll_queue(proxy, queue: self.events)
|
||||
return Color.clear
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,25 +43,34 @@ struct DamusVideoPlayer: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
VideoPlayer(url: url, model: model)
|
||||
.onAppear {
|
||||
model.start()
|
||||
GeometryReader { geo in
|
||||
let localFrame = geo.frame(in: .local)
|
||||
let localCenter = CGPoint(x: localFrame.midX, y: localFrame.midY)
|
||||
let globalCenter = geo.frame(in: .global).origin.applying(.init(translationX: localCenter.x, y: localCenter.y))
|
||||
let centerY = globalCenter.y
|
||||
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
VideoPlayer(url: url, model: model)
|
||||
if model.has_audio == true {
|
||||
MuteIcon
|
||||
.zIndex(11.0)
|
||||
.onTapGesture {
|
||||
self.model.muted = !self.model.muted
|
||||
}
|
||||
}
|
||||
|
||||
if model.has_audio == true {
|
||||
MuteIcon
|
||||
.zIndex(11.0)
|
||||
.onTapGesture {
|
||||
self.model.muted = !self.model.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: model.size) { size in
|
||||
guard let size else {
|
||||
return
|
||||
.onChange(of: model.size) { size in
|
||||
guard let size else {
|
||||
return
|
||||
}
|
||||
video_size = size
|
||||
}
|
||||
.onChange(of: centerY) { _ in
|
||||
let screenHeight = UIScreen.main.bounds.height
|
||||
let screenMidY = screenHeight / 2
|
||||
let tol = 0.20 * screenHeight /// tolerance - can vary to taste ie., % of screen height of a centered box in which video plays
|
||||
model.play = centerY > screenMidY - tol && centerY < screenMidY + tol /// video plays when inside tolerance box
|
||||
}
|
||||
video_size = size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ enum VideoHandler {
|
||||
case onStateChanged((VideoState) -> Void)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public class VideoPlayerModel: ObservableObject {
|
||||
@Published var autoReplay: Bool = true
|
||||
@Published var muted: Bool = true
|
||||
@@ -164,15 +165,11 @@ public extension VideoPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13, *)
|
||||
public extension VideoPlayer {
|
||||
|
||||
|
||||
}
|
||||
|
||||
func get_video_size(player: AVPlayer) -> CGSize? {
|
||||
// TODO: make this async?
|
||||
return player.currentImage?.size
|
||||
func get_video_size(player: AVPlayer) async -> CGSize? {
|
||||
let res = Task.detached(priority: .background) {
|
||||
return player.currentImage?.size
|
||||
}
|
||||
return await res.value
|
||||
}
|
||||
|
||||
func video_has_audio(player: AVPlayer) async -> Bool {
|
||||
@@ -220,7 +217,7 @@ extension VideoPlayer: UIViewRepresentable {
|
||||
if let player = uiView.player {
|
||||
Task {
|
||||
let has_audio = await video_has_audio(player: player)
|
||||
let size = get_video_size(player: player)
|
||||
let size = await get_video_size(player: player)
|
||||
Task { @MainActor in
|
||||
if let size {
|
||||
self.model.size = size
|
||||
@@ -285,13 +282,16 @@ extension VideoPlayer: UIViewRepresentable {
|
||||
self.videoPlayer = videoPlayer
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func startObserver(uiView: VideoPlayerView) {
|
||||
guard observer == nil else { return }
|
||||
|
||||
observer = uiView.addPeriodicTimeObserver(forInterval: .init(seconds: 0.25, preferredTimescale: 60)) { [weak self, unowned uiView] time in
|
||||
guard let `self` = self else { return }
|
||||
|
||||
self.videoPlayer.model.time = time
|
||||
Task { @MainActor in
|
||||
self.videoPlayer.model.time = time
|
||||
}
|
||||
self.observerTime = time
|
||||
|
||||
self.updateBuffer(uiView: uiView)
|
||||
@@ -313,6 +313,7 @@ extension VideoPlayer: UIViewRepresentable {
|
||||
self.observerBuffer = nil
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func updateBuffer(uiView: VideoPlayerView) {
|
||||
let bufferProgress = uiView.bufferProgress
|
||||
guard bufferProgress != observerBuffer else { return }
|
||||
|
||||
@@ -67,7 +67,7 @@ struct NWCPaste: View {
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "doc.on.clipboard")
|
||||
Text("Paste")
|
||||
Text("Paste", comment: "Button to paste a Nostr Wallet Connect string to connect the wallet for use in Damus for zaps.")
|
||||
}
|
||||
.frame(minWidth: 300, maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .center)
|
||||
.foregroundColor(colorScheme == .light ? DamusColors.black : DamusColors.white)
|
||||
|
||||
@@ -20,9 +20,11 @@ struct WalletView: View {
|
||||
|
||||
func MainWalletView(nwc: WalletConnectURL) -> some View {
|
||||
VStack {
|
||||
SupportDamus
|
||||
|
||||
Spacer()
|
||||
if !damus_state.settings.nozaps {
|
||||
SupportDamus
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text(verbatim: nwc.relay.id)
|
||||
|
||||
|
||||
@@ -48,18 +48,10 @@ struct CustomizeZapView: View {
|
||||
let state: DamusState
|
||||
let target: ZapTarget
|
||||
let lnurl: String
|
||||
@State var comment: String
|
||||
@State var custom_amount: String
|
||||
@State var custom_amount_sats: Int?
|
||||
@State var zap_type: ZapType
|
||||
@State var invoice: String
|
||||
@State var error: String?
|
||||
@State var showing_wallet_selector: Bool
|
||||
@State var zapping: Bool
|
||||
@State var show_zap_types: Bool = false
|
||||
|
||||
let zap_amounts: [ZapAmountItem]
|
||||
|
||||
@StateObject var model: CustomizeZapModel = CustomizeZapModel()
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
@@ -72,21 +64,12 @@ struct CustomizeZapView: View {
|
||||
}
|
||||
|
||||
init(state: DamusState, target: ZapTarget, lnurl: String) {
|
||||
self._comment = State(initialValue: "")
|
||||
self.target = target
|
||||
self.zap_amounts = get_zap_amount_items(state.settings.default_zap_amount)
|
||||
self._error = State(initialValue: nil)
|
||||
self._invoice = State(initialValue: "")
|
||||
self._showing_wallet_selector = State(initialValue: false)
|
||||
self._zap_type = State(initialValue: state.settings.default_zap_type)
|
||||
self._custom_amount = State(initialValue: String(state.settings.default_zap_amount))
|
||||
self._custom_amount_sats = State(initialValue: nil)
|
||||
self._zapping = State(initialValue: false)
|
||||
self.lnurl = lnurl
|
||||
self.state = state
|
||||
}
|
||||
|
||||
|
||||
func amount_parts(_ n: Int) -> [ZapAmountItem] {
|
||||
var i: Int = -1
|
||||
let start = n * 3
|
||||
@@ -101,7 +84,10 @@ struct CustomizeZapView: View {
|
||||
func AmountsPart(n: Int) -> some View {
|
||||
HStack(alignment: .center, spacing: 15) {
|
||||
ForEach(amount_parts(n)) { entry in
|
||||
ZapAmountButton(zapAmountItem: entry, action: {custom_amount_sats = entry.amount; custom_amount = String(entry.amount)})
|
||||
ZapAmountButton(zapAmountItem: entry, action: {
|
||||
model.custom_amount_sats = entry.amount
|
||||
model.custom_amount = String(entry.amount)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,17 +111,17 @@ struct CustomizeZapView: View {
|
||||
.font(.headline)
|
||||
.frame(width: 70, height: 70)
|
||||
.foregroundColor(fontColor())
|
||||
.background(custom_amount_sats == zapAmountItem.amount ? fillColor() : DamusColors.adaptableGrey)
|
||||
.background(model.custom_amount_sats == zapAmountItem.amount ? fillColor() : DamusColors.adaptableGrey)
|
||||
.cornerRadius(15)
|
||||
.overlay(RoundedRectangle(cornerRadius: 15)
|
||||
.stroke(DamusColors.purple.opacity(custom_amount_sats == zapAmountItem.amount ? 1.0 : 0.0), lineWidth: 2))
|
||||
.stroke(DamusColors.purple.opacity(model.custom_amount_sats == zapAmountItem.amount ? 1.0 : 0.0), lineWidth: 2))
|
||||
}
|
||||
}
|
||||
|
||||
var CustomZapTextField: some View {
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
TextField("", text: $custom_amount)
|
||||
.placeholder(when: custom_amount.isEmpty, alignment: .center) {
|
||||
TextField("", text: $model.custom_amount)
|
||||
.placeholder(when: model.custom_amount.isEmpty, alignment: .center) {
|
||||
Text(verbatim: 0.formatted())
|
||||
}
|
||||
.accentColor(.clear)
|
||||
@@ -143,16 +129,16 @@ struct CustomizeZapView: View {
|
||||
.minimumScaleFactor(0.01)
|
||||
.keyboardType(.numberPad)
|
||||
.multilineTextAlignment(.center)
|
||||
.onReceive(Just(custom_amount)) { newValue in
|
||||
.onChange(of: model.custom_amount) { newValue in
|
||||
if let parsed = handle_string_amount(new_value: newValue) {
|
||||
self.custom_amount = parsed.formatted()
|
||||
self.custom_amount_sats = parsed
|
||||
model.custom_amount = parsed.formatted()
|
||||
model.custom_amount_sats = parsed
|
||||
} else {
|
||||
self.custom_amount = ""
|
||||
self.custom_amount_sats = nil
|
||||
model.custom_amount = ""
|
||||
model.custom_amount_sats = nil
|
||||
}
|
||||
}
|
||||
Text(verbatim: satsString(custom_amount_sats ?? 0))
|
||||
Text(verbatim: satsString(model.custom_amount_sats ?? 0))
|
||||
.font(.system(size: 18, weight: .heavy))
|
||||
}
|
||||
}
|
||||
@@ -160,12 +146,12 @@ struct CustomizeZapView: View {
|
||||
var ZapReply: some View {
|
||||
HStack {
|
||||
if #available(iOS 16.0, *) {
|
||||
TextField(NSLocalizedString("Send a reply with your zap...", comment: "Placeholder text for a comment to send as part of a zap to the user."), text: $comment, axis: .vertical)
|
||||
TextField(NSLocalizedString("Send a message with your zap...", comment: "Placeholder text for a comment to send as part of a zap to the user."), text: $model.comment, axis: .vertical)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
.lineLimit(5)
|
||||
} else {
|
||||
TextField(NSLocalizedString("Send a reply with your zap...", comment: "Placeholder text for a comment to send as part of a zap to the user."), text: $comment)
|
||||
TextField(NSLocalizedString("Send a message with your zap...", comment: "Placeholder text for a comment to send as part of a zap to the user."), text: $model.comment)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
}
|
||||
@@ -179,24 +165,24 @@ struct CustomizeZapView: View {
|
||||
|
||||
var ZapButton: some View {
|
||||
VStack {
|
||||
if zapping {
|
||||
if model.zapping {
|
||||
Text("Zapping...", comment: "Text to indicate that the app is in the process of sending a zap.")
|
||||
} else {
|
||||
Button(NSLocalizedString("Zap", comment: "Button to send a zap.")) {
|
||||
let amount = custom_amount_sats
|
||||
send_zap(damus_state: state, target: target, lnurl: lnurl, is_custom: true, comment: comment, amount_sats: amount, zap_type: zap_type)
|
||||
self.zapping = true
|
||||
Button(NSLocalizedString("Zap User", comment: "Button to send a zap.")) {
|
||||
let amount = model.custom_amount_sats
|
||||
send_zap(damus_state: state, target: target, lnurl: lnurl, is_custom: true, comment: model.comment, amount_sats: amount, zap_type: model.zap_type)
|
||||
model.zapping = true
|
||||
}
|
||||
.disabled(custom_amount_sats == 0 || custom_amount.isEmpty)
|
||||
.disabled(model.custom_amount_sats == 0 || model.custom_amount.isEmpty)
|
||||
.font(.system(size: 28, weight: .bold))
|
||||
.frame(width: 130, height: 50)
|
||||
.frame(width: 180, height: 50)
|
||||
.foregroundColor(.white)
|
||||
.background(LINEAR_GRADIENT)
|
||||
.opacity(custom_amount_sats == 0 || custom_amount.isEmpty ? 0.5 : 1.0)
|
||||
.opacity(model.custom_amount_sats == 0 || model.custom_amount.isEmpty ? 0.5 : 1.0)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
|
||||
if let error {
|
||||
if let error = model.error {
|
||||
Text(error)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
@@ -212,56 +198,83 @@ struct CustomizeZapView: View {
|
||||
return
|
||||
}
|
||||
|
||||
self.zapping = false
|
||||
model.zapping = false
|
||||
|
||||
switch zap_ev.type {
|
||||
case .failed(let err):
|
||||
switch err {
|
||||
case .fetching_invoice:
|
||||
self.error = NSLocalizedString("Error fetching lightning invoice", comment: "Message to display when there was an error fetching a lightning invoice while attempting to zap.")
|
||||
model.error = NSLocalizedString("Error fetching lightning invoice", comment: "Message to display when there was an error fetching a lightning invoice while attempting to zap.")
|
||||
case .bad_lnurl:
|
||||
self.error = NSLocalizedString("Invalid lightning address", comment: "Message to display when there was an error attempting to zap due to an invalid lightning address.")
|
||||
model.error = NSLocalizedString("Invalid lightning address", comment: "Message to display when there was an error attempting to zap due to an invalid lightning address.")
|
||||
case .canceled:
|
||||
self.error = NSLocalizedString("Zap attempt from connected wallet was canceled.", comment: "Message to display when a zap from the user's connected wallet was canceled.")
|
||||
model.error = NSLocalizedString("Zap attempt from connected wallet was canceled.", comment: "Message to display when a zap from the user's connected wallet was canceled.")
|
||||
case .send_failed:
|
||||
self.error = NSLocalizedString("Zap attempt from connected wallet failed.", comment: "Message to display when sending a zap from the user's connected wallet failed.")
|
||||
model.error = NSLocalizedString("Zap attempt from connected wallet failed.", comment: "Message to display when sending a zap from the user's connected wallet failed.")
|
||||
}
|
||||
break
|
||||
case .got_zap_invoice(let inv):
|
||||
if state.settings.show_wallet_selector {
|
||||
self.invoice = inv
|
||||
self.showing_wallet_selector = true
|
||||
model.invoice = inv
|
||||
present_sheet(.select_wallet(invoice: inv))
|
||||
} else {
|
||||
end_editing()
|
||||
let wallet = state.settings.default_wallet.model
|
||||
open_with_wallet(wallet: wallet, invoice: inv)
|
||||
self.showing_wallet_selector = false
|
||||
dismiss()
|
||||
}
|
||||
case .sent_from_nwc:
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
MainContent
|
||||
.sheet(isPresented: $showing_wallet_selector) {
|
||||
SelectWalletView(default_wallet: state.settings.default_wallet, showingSelectWallet: $showing_wallet_selector, our_pubkey: state.pubkey, invoice: invoice)
|
||||
VStack(alignment: .center, spacing: 20) {
|
||||
ScrollView {
|
||||
HStack(alignment: .center) {
|
||||
UserView(damus_state: state, pubkey: target.pubkey)
|
||||
|
||||
ZapTypeButton()
|
||||
}
|
||||
.padding([.horizontal, .top])
|
||||
|
||||
CustomZapTextField
|
||||
|
||||
AmountPicker
|
||||
|
||||
ZapReply
|
||||
|
||||
ZapButton
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.onReceive(handle_notify(.zapping)) { notif in
|
||||
receive_zap(notif: notif)
|
||||
}
|
||||
.background(fillColor().edgesIgnoringSafeArea(.all))
|
||||
.onTapGesture {
|
||||
hideKeyboard()
|
||||
}
|
||||
.sheet(isPresented: $model.show_zap_types) {
|
||||
if #available(iOS 16.0, *) {
|
||||
ZapPicker
|
||||
.presentationDetents([.medium])
|
||||
.presentationDragIndicator(.visible)
|
||||
} else {
|
||||
ZapPicker
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
model.set_defaults(settings: state.settings)
|
||||
}
|
||||
.onReceive(handle_notify(.zapping)) { notif in
|
||||
receive_zap(notif: notif)
|
||||
}
|
||||
.background(fillColor().edgesIgnoringSafeArea(.all))
|
||||
.onTapGesture {
|
||||
hideKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
func ZapTypeButton() -> some View {
|
||||
Button(action: {
|
||||
show_zap_types = true
|
||||
model.show_zap_types = true
|
||||
}) {
|
||||
switch zap_type {
|
||||
switch model.zap_type {
|
||||
case .pub:
|
||||
Image("globe")
|
||||
Text("Public", comment: "Button text to indicate that the zap type is a public zap.")
|
||||
@@ -283,43 +296,8 @@ struct CustomizeZapView: View {
|
||||
.cornerRadius(15)
|
||||
}
|
||||
|
||||
var CustomZap: some View {
|
||||
VStack(alignment: .center, spacing: 20) {
|
||||
|
||||
ZapTypeButton()
|
||||
.padding(.top, 50)
|
||||
|
||||
Spacer()
|
||||
|
||||
CustomZapTextField
|
||||
|
||||
AmountPicker
|
||||
|
||||
ZapReply
|
||||
|
||||
ZapButton
|
||||
|
||||
Spacer()
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.sheet(isPresented: $show_zap_types) {
|
||||
if #available(iOS 16.0, *) {
|
||||
ZapPicker
|
||||
.presentationDetents([.medium])
|
||||
.presentationDragIndicator(.visible)
|
||||
} else {
|
||||
ZapPicker
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ZapPicker: some View {
|
||||
ZapTypePicker(zap_type: $zap_type, settings: state.settings, profiles: state.profiles, pubkey: target.pubkey)
|
||||
}
|
||||
|
||||
var MainContent: some View {
|
||||
CustomZap
|
||||
ZapTypePicker(zap_type: $model.zap_type, settings: state.settings, profiles: state.profiles, pubkey: target.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// ZapUserView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-06-22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ZapUserView: View {
|
||||
let state: DamusState
|
||||
let pubkey: String
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center) {
|
||||
Text("Zap")
|
||||
.font(.title2)
|
||||
|
||||
UserView(damus_state: state, pubkey: pubkey, spacer: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ZapUserView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ZapUserView(state: test_damus_state(), pubkey: "anon")
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ struct ZapsView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
ForEach(zaps.zaps, id: \.request.id) { zap in
|
||||
ZapEvent(damus: state, zap: zap)
|
||||
ForEach(zaps.zaps, id: \.request.ev.id) { zap in
|
||||
ZapEvent(damus: state, zap: zap, is_top_zap: false)
|
||||
.padding([.horizontal])
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -66,7 +66,7 @@
|
||||
<string>%2$@ και %1$d ακόμα αντέδρασαν σε μια δημοσίευση που σας επισημαίνει</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
<key>reacted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTED@</string>
|
||||
@@ -162,7 +162,7 @@
|
||||
<string>%2$@ και %1$d ακόμα αναδημοσίευσαν μια δημοσίευση που σας επισημαίνει</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
<key>reposted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTED@</string>
|
||||
@@ -269,9 +269,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>@</string>
|
||||
<key>one</key>
|
||||
<string>Λάβατε %2$@ sat από %3$@: "%4$@"</string>
|
||||
<string>Λάβατε %2$@ sat από %3$@: "%4$@"</string>
|
||||
<key>other</key>
|
||||
<string>Λάβατε %2$@ sats από %3$@: "%4$@"</string>
|
||||
<string>Λάβατε %2$@ sats από %3$@: "%4$@"</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_tagged_in_3</key>
|
||||
@@ -290,7 +290,7 @@
|
||||
<string>%2$@ και %1$d ακόμα έστειλαν zap σε μια δημοσίευση που σας επισημαίνει</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
<key>zapped_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPPED@</string>
|
||||
|
||||
Binary file not shown.
@@ -18,6 +18,22 @@
|
||||
<string>... %d other notes ...</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>followed_by_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>Followed by %2$@, %3$@, %4$@ & %1$d other</string>
|
||||
<key>other</key>
|
||||
<string>Followed by %2$@, %3$@, %4$@ & %1$d others</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>followers_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
@@ -61,12 +77,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other reacted to a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d other reacted to a note you were tagged in</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others reacted to a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d others reacted to a note you were tagged in</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
<key>reacted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTED@</string>
|
||||
@@ -77,9 +93,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other reacted to your post</string>
|
||||
<string>%2$@ and %1$d other reacted to your note</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others reacted to your post</string>
|
||||
<string>%2$@ and %1$d others reacted to your note</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_profile_3</key>
|
||||
@@ -157,12 +173,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other reposted a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d other reposted a note you were tagged in</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others reposted a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d others reposted a note you were tagged in</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
<key>reposted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTED@</string>
|
||||
@@ -173,9 +189,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other reposted your post</string>
|
||||
<string>%2$@ and %1$d other reposted your note</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others reposted your post</string>
|
||||
<string>%2$@ and %1$d others reposted your note</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_profile_3</key>
|
||||
@@ -269,9 +285,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>@</string>
|
||||
<key>one</key>
|
||||
<string>You received %2$@ sat from %3$@: "%4$@"</string>
|
||||
<string>You received %2$@ sat from %3$@: "%4$@"</string>
|
||||
<key>other</key>
|
||||
<string>You received %2$@ sats from %3$@: "%4$@"</string>
|
||||
<string>You received %2$@ sats from %3$@: "%4$@"</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_tagged_in_3</key>
|
||||
@@ -285,12 +301,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other zapped a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d other zapped a note you were tagged in</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others zapped a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d others zapped a note you were tagged in</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
<key>zapped_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPPED@</string>
|
||||
@@ -301,9 +317,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other zapped your post</string>
|
||||
<string>%2$@ and %1$d other zapped your note</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others zapped your post</string>
|
||||
<string>%2$@ and %1$d others zapped your note</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_profile_3</key>
|
||||
@@ -317,9 +333,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other zapped your profile</string>
|
||||
<string>%2$@ and %1$d other zapped you</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others zapped your profile</string>
|
||||
<string>%2$@ and %1$d others zapped you</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
|
||||
<file original="damus/en-US.lproj/InfoPlist.strings" source-language="en-US" target-language="en-US" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.3" build-num="14E222b"/>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.3.1" build-num="14E300c"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||
@@ -39,7 +39,7 @@
|
||||
</file>
|
||||
<file original="damus/en-US.lproj/Localizable.strings" source-language="en-US" target-language="en-US" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.3" build-num="14E222b"/>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.3.1" build-num="14E300c"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="%@ %@" xml:space="preserve">
|
||||
@@ -73,9 +73,9 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs</target>
|
||||
<note>Explanation of what is done to keep private data encrypted. There is a heading that precedes this explanation which is a variable to this string.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet." xml:space="preserve">
|
||||
<source>%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet.</source>
|
||||
<target>%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet.</target>
|
||||
<trans-unit id="%@. Tip your friends and stack sats with Bitcoin⚡️, the native currency of the internet." xml:space="preserve">
|
||||
<source>%@. Tip your friends and stack sats with Bitcoin⚡️, the native currency of the internet.</source>
|
||||
<target>%@. Tip your friends and stack sats with Bitcoin⚡️, the native currency of the internet.</target>
|
||||
<note>Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%lld%%" xml:space="preserve">
|
||||
@@ -133,11 +133,6 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Accessibility</target>
|
||||
<note>Section header for accessibility settings</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Account ID" xml:space="preserve">
|
||||
<source>Account ID</source>
|
||||
<target>Account ID</target>
|
||||
<note>Label to indicate the public ID of the account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Actions" xml:space="preserve">
|
||||
<source>Actions</source>
|
||||
<target>Actions</target>
|
||||
@@ -151,14 +146,18 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Add Bookmark" xml:space="preserve">
|
||||
<source>Add Bookmark</source>
|
||||
<target>Add Bookmark</target>
|
||||
<note>Button text to add bookmark to a note.
|
||||
Context menu option for adding a note bookmark.</note>
|
||||
<note>Button text to add bookmark to a note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add all" xml:space="preserve">
|
||||
<source>Add all</source>
|
||||
<target>Add all</target>
|
||||
<note>Button label to re-add all original participants as profiles to reply to in a note</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add bookmark" xml:space="preserve">
|
||||
<source>Add bookmark</source>
|
||||
<target>Add bookmark</target>
|
||||
<note>Context menu option for adding a note bookmark.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Admin" xml:space="preserve">
|
||||
<source>Admin</source>
|
||||
<target>Admin</target>
|
||||
@@ -169,6 +168,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>All</target>
|
||||
<note>Label for filter for all notifications.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Already on Nostr?" xml:space="preserve">
|
||||
<source>Already on Nostr?</source>
|
||||
<target>Already on Nostr?</target>
|
||||
<note>Ask the user if they already have an account on Nostr</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Always show images" xml:space="preserve">
|
||||
<source>Always show images</source>
|
||||
<target>Always show images</target>
|
||||
@@ -373,41 +377,41 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Copy Link</target>
|
||||
<note>Button to copy link to note</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy Note ID" xml:space="preserve">
|
||||
<source>Copy Note ID</source>
|
||||
<target>Copy Note ID</target>
|
||||
<note>Context menu option for copying the ID of the note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy Note JSON" xml:space="preserve">
|
||||
<source>Copy Note JSON</source>
|
||||
<target>Copy Note JSON</target>
|
||||
<note>Context menu option for copying the JSON text from the note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy Report ID" xml:space="preserve">
|
||||
<source>Copy Report ID</source>
|
||||
<target>Copy Report ID</target>
|
||||
<note>Button to copy report ID.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy Text" xml:space="preserve">
|
||||
<source>Copy Text</source>
|
||||
<target>Copy Text</target>
|
||||
<note>Context menu option for copying the text from an note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy URL" xml:space="preserve">
|
||||
<source>Copy URL</source>
|
||||
<target>Copy URL</target>
|
||||
<note>Label for button in context menu to copy URL of the selected uploaded media asset.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy User Pubkey" xml:space="preserve">
|
||||
<source>Copy User Pubkey</source>
|
||||
<target>Copy User Pubkey</target>
|
||||
<note>Context menu option for copying the ID of the user who created the note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy invoice" xml:space="preserve">
|
||||
<source>Copy invoice</source>
|
||||
<target>Copy invoice</target>
|
||||
<note>Title of section for copying a Lightning invoice identifier.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy note ID" xml:space="preserve">
|
||||
<source>Copy note ID</source>
|
||||
<target>Copy note ID</target>
|
||||
<note>Context menu option for copying the ID of the note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy note JSON" xml:space="preserve">
|
||||
<source>Copy note JSON</source>
|
||||
<target>Copy note JSON</target>
|
||||
<note>Context menu option for copying the JSON text from the note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy text" xml:space="preserve">
|
||||
<source>Copy text</source>
|
||||
<target>Copy text</target>
|
||||
<note>Context menu option for copying the text from an note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy user public key" xml:space="preserve">
|
||||
<source>Copy user public key</source>
|
||||
<target>Copy user public key</target>
|
||||
<note>Context menu option for copying the ID of the user who created the note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Could not find the user you're looking for" xml:space="preserve">
|
||||
<source>Could not find the user you're looking for</source>
|
||||
<target>Could not find the user you're looking for</target>
|
||||
@@ -418,15 +422,15 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Could not find user to mute...</target>
|
||||
<note>Alert message to indicate that the muted user could not be found.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create" xml:space="preserve">
|
||||
<source>Create</source>
|
||||
<target>Create</target>
|
||||
<note>Button to create account.</note>
|
||||
<trans-unit id="Create account" xml:space="preserve">
|
||||
<source>Create account</source>
|
||||
<target>Create account</target>
|
||||
<note>Button to navigate to create account view.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create Account" xml:space="preserve">
|
||||
<source>Create Account</source>
|
||||
<target>Create Account</target>
|
||||
<note>Button to create an account.</note>
|
||||
<trans-unit id="Create account now" xml:space="preserve">
|
||||
<source>Create account now</source>
|
||||
<target>Create account now</target>
|
||||
<note>Button to create account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Create new mutelist" xml:space="preserve">
|
||||
<source>Create new mutelist</source>
|
||||
@@ -451,11 +455,6 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
Setting to enable DM Local Notification
|
||||
Toolbar label for DMs view, where DM is the English abbreviation for Direct Message.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Damus" xml:space="preserve">
|
||||
<source>Damus</source>
|
||||
<target>Damus</target>
|
||||
<note>Name of the app, shown on the first screen when user is not logged in.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="DeepL (Proprietary, Higher Accuracy)" xml:space="preserve">
|
||||
<source>DeepL (Proprietary, Higher Accuracy)</source>
|
||||
<target>DeepL (Proprietary, Higher Accuracy)</target>
|
||||
@@ -507,9 +506,9 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Disconnect Wallet</target>
|
||||
<note>Text for button to disconnect from Nostr Wallet Connect lightning wallet.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Display Name" xml:space="preserve">
|
||||
<source>Display Name</source>
|
||||
<target>Display Name</target>
|
||||
<trans-unit id="Display name" xml:space="preserve">
|
||||
<source>Display name</source>
|
||||
<target>Display name</target>
|
||||
<note>Label to prompt display name entry.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Done" xml:space="preserve">
|
||||
@@ -520,7 +519,7 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="EULA" xml:space="preserve">
|
||||
<source>EULA</source>
|
||||
<target>EULA</target>
|
||||
<note>Label indicating that the below text is the EULA, an acronym for End User License Agreement.</note>
|
||||
<note>Navigation title of view that shows the EULA, an acronym for End User License Agreement.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Earn Money" xml:space="preserve">
|
||||
<source>Earn Money</source>
|
||||
@@ -537,9 +536,9 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Encrypted</target>
|
||||
<note>Heading indicating that this application keeps private messaging end-to-end encrypted.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Enter your account key to login:" xml:space="preserve">
|
||||
<source>Enter your account key to login:</source>
|
||||
<target>Enter your account key to login:</target>
|
||||
<trans-unit id="Enter your account key" xml:space="preserve">
|
||||
<source>Enter your account key</source>
|
||||
<target>Enter your account key</target>
|
||||
<note>Prompt for user to enter an account key to login.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error fetching lightning invoice" xml:space="preserve">
|
||||
@@ -572,9 +571,9 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Follow Back</target>
|
||||
<note>Button to follow a user back.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Follow me on nostr" xml:space="preserve">
|
||||
<source>Follow me on nostr</source>
|
||||
<target>Follow me on nostr</target>
|
||||
<trans-unit id="Follow me on Nostr" xml:space="preserve">
|
||||
<source>Follow me on Nostr</source>
|
||||
<target>Follow me on Nostr</target>
|
||||
<note>Text on QR code view to prompt viewer looking at screen to follow the user.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Followers" xml:space="preserve">
|
||||
@@ -620,7 +619,7 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Hide" xml:space="preserve">
|
||||
<source>Hide</source>
|
||||
<target>Hide</target>
|
||||
<note>Button to hide a post from a user who has been muted.</note>
|
||||
<note>Button to hide a note from a user who has been muted.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hide all 🤙's" xml:space="preserve">
|
||||
<source>Hide all 🤙's</source>
|
||||
@@ -630,7 +629,7 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Home" xml:space="preserve">
|
||||
<source>Home</source>
|
||||
<target>Home</target>
|
||||
<note>Navigation bar title for Home view where posts and replies appear from those who the user is following.</note>
|
||||
<note>Navigation bar title for Home view where notes and replies appear from those who the user is following.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Illegal content" xml:space="preserve">
|
||||
<source>Illegal content</source>
|
||||
@@ -652,6 +651,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Invalid Tip Address</target>
|
||||
<note>Title of alerting as invalid tip address.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid Zap" xml:space="preserve">
|
||||
<source>Invalid Zap</source>
|
||||
<target>Invalid Zap</target>
|
||||
<note>Text indicating that a zap event is malformed and could not be displayed.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Invalid key" xml:space="preserve">
|
||||
<source>Invalid key</source>
|
||||
<target>Invalid key</target>
|
||||
@@ -673,11 +677,21 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<note>Navigation title for managing keys.
|
||||
Settings section for managing keys</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Learn more about Nostr" xml:space="preserve">
|
||||
<source>Learn more about Nostr</source>
|
||||
<target>Learn more about Nostr</target>
|
||||
<note>Button that opens up a webpage where the user can learn more about Nostr.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Left Handed" xml:space="preserve">
|
||||
<source>Left Handed</source>
|
||||
<target>Left Handed</target>
|
||||
<note>Moves the post button to the left side of the screen</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Let's get started!" xml:space="preserve">
|
||||
<source>Let's get started!</source>
|
||||
<target>Let's get started!</target>
|
||||
<note>Button to continue to login page.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Let's go!" xml:space="preserve">
|
||||
<source>Let's go!</source>
|
||||
<target>Let's go!</target>
|
||||
@@ -731,8 +745,7 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Login" xml:space="preserve">
|
||||
<source>Login</source>
|
||||
<target>Login</target>
|
||||
<note>Button to log into account.
|
||||
Button to log into an account.</note>
|
||||
<note>Button to navigate to login view.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Logout" xml:space="preserve">
|
||||
<source>Logout</source>
|
||||
@@ -780,14 +793,18 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Mute User" xml:space="preserve">
|
||||
<source>Mute User</source>
|
||||
<target>Mute User</target>
|
||||
<note>Context menu option for muting users.
|
||||
Title of alert for muting a user.</note>
|
||||
<note>Title of alert for muting a user.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Mute conversation" xml:space="preserve">
|
||||
<source>Mute conversation</source>
|
||||
<target>Mute conversation</target>
|
||||
<note>Context menu option for muting a conversation.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Mute user" xml:space="preserve">
|
||||
<source>Mute user</source>
|
||||
<target>Mute user</target>
|
||||
<note>Context menu option for muting users.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Muted" xml:space="preserve">
|
||||
<source>Muted</source>
|
||||
<target>Muted</target>
|
||||
@@ -808,6 +825,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>New encrypted direct message</target>
|
||||
<note>Notification that the user has received a new direct message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New to Nostr?" xml:space="preserve">
|
||||
<source>New to Nostr?</source>
|
||||
<target>New to Nostr?</target>
|
||||
<note>Ask the user if they are new to Nostr</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No" xml:space="preserve">
|
||||
<source>No</source>
|
||||
<target>No</target>
|
||||
@@ -843,11 +865,33 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>None</target>
|
||||
<note>Button text to indicate that the zap type is a private zap.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nostr is a protocol, designed for simplicity, that aims to create a censorship-resistant global social network" xml:space="preserve">
|
||||
<source>Nostr is a protocol, designed for simplicity, that aims to create a censorship-resistant global social network</source>
|
||||
<target>Nostr is a protocol, designed for simplicity, that aims to create a censorship-resistant global social network</target>
|
||||
<note>Description about what is Nostr.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Note contains "nsec1" private key. Are you sure?" xml:space="preserve">
|
||||
<source>Note contains "nsec1" private key. Are you sure?</source>
|
||||
<target>Note contains "nsec1" private key. Are you sure?</target>
|
||||
<note>Alert user that they might be attempting to paste a private key and ask them to confirm.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Note from a user you've muted" xml:space="preserve">
|
||||
<source>Note from a user you've muted</source>
|
||||
<target>Note from a user you've muted</target>
|
||||
<note>Text to indicate that what is being shown is a note from a user who has been muted.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notes" xml:space="preserve">
|
||||
<source>Notes</source>
|
||||
<target>Notes</target>
|
||||
<note>Label for filter for seeing only notes (instead of notes and replies).
|
||||
Label for filter for seeing only your notes (instead of notes and replies).</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Notes & Replies" xml:space="preserve">
|
||||
<source>Notes & Replies</source>
|
||||
<target>Notes & Replies</target>
|
||||
<note>Label for filter for seeing notes and replies (instead of only notes).
|
||||
Label for filter for seeing your notes and replies (instead of only your notes).</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nothing to see here. Check back later!" xml:space="preserve">
|
||||
<source>Nothing to see here. Check back later!</source>
|
||||
<target>Nothing to see here. Check back later!</target>
|
||||
@@ -904,6 +948,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Paid Relay</target>
|
||||
<note>Section header that indicates the relay server requires payment.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Paste" xml:space="preserve">
|
||||
<source>Paste</source>
|
||||
<target>Paste</target>
|
||||
<note>Button to paste a Nostr Wallet Connect string to connect the wallet for use in Damus for zaps.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Pay" xml:space="preserve">
|
||||
<source>Pay</source>
|
||||
<target>Pay</target>
|
||||
@@ -935,23 +984,6 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Post</target>
|
||||
<note>Button to post a note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Post from a user you've muted" xml:space="preserve">
|
||||
<source>Post from a user you've muted</source>
|
||||
<target>Post from a user you've muted</target>
|
||||
<note>Text to indicate that what is being shown is a post from a user who has been muted.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Posts" xml:space="preserve">
|
||||
<source>Posts</source>
|
||||
<target>Posts</target>
|
||||
<note>Label for filter for seeing only posts (instead of posts and replies).
|
||||
Label for filter for seeing only your posts (instead of posts and replies).</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Posts & Replies" xml:space="preserve">
|
||||
<source>Posts & Replies</source>
|
||||
<target>Posts & Replies</target>
|
||||
<note>Label for filter for seeing posts and replies (instead of only posts).
|
||||
Label for filter for seeing your posts and replies (instead of only your posts).</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Private" xml:space="preserve">
|
||||
<source>Private</source>
|
||||
<target>Private</target>
|
||||
@@ -996,12 +1028,7 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<trans-unit id="Public Key" xml:space="preserve">
|
||||
<source>Public Key</source>
|
||||
<target>Public Key</target>
|
||||
<note>Label indicating that the text is a user's public account key.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Public Key?" xml:space="preserve">
|
||||
<source>Public Key?</source>
|
||||
<target>Public Key?</target>
|
||||
<note>Prompt to ask user if the key they entered is a public key.</note>
|
||||
<note>Label to indicate the public key of the account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Public key" xml:space="preserve">
|
||||
<source>Public key</source>
|
||||
@@ -1053,14 +1080,18 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<trans-unit id="Remove Bookmark" xml:space="preserve">
|
||||
<source>Remove Bookmark</source>
|
||||
<target>Remove Bookmark</target>
|
||||
<note>Button text to remove bookmark from a note.
|
||||
Context menu option for removing a note bookmark.</note>
|
||||
<note>Button text to remove bookmark from a note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove all" xml:space="preserve">
|
||||
<source>Remove all</source>
|
||||
<target>Remove all</target>
|
||||
<note>Button label to remove all participants from a note reply.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove bookmark" xml:space="preserve">
|
||||
<source>Remove bookmark</source>
|
||||
<target>Remove bookmark</target>
|
||||
<note>Context menu option for removing a note bookmark.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reply" xml:space="preserve">
|
||||
<source>Reply</source>
|
||||
<target>Reply</target>
|
||||
@@ -1110,7 +1141,7 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<trans-unit id="Reposted" xml:space="preserve">
|
||||
<source>Reposted</source>
|
||||
<target>Reposted</target>
|
||||
<note>Text indicating that the post was reposted (i.e. re-shared).</note>
|
||||
<note>Text indicating that the note was reposted (i.e. re-shared).</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reposted by %@" xml:space="preserve">
|
||||
<source>Reposted by %@</source>
|
||||
@@ -1148,6 +1179,16 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<target>Save Image</target>
|
||||
<note>Context menu option to save an image.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan Code" xml:space="preserve">
|
||||
<source>Scan Code</source>
|
||||
<target>Scan Code</target>
|
||||
<note>Button to switch to scan QR Code page.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan a user's pubkey" xml:space="preserve">
|
||||
<source>Scan a user's pubkey</source>
|
||||
<target>Scan a user's pubkey</target>
|
||||
<note>Text to prompt scanning a QR code of a user's pubkey to open their profile.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Scan the code" xml:space="preserve">
|
||||
<source>Scan the code</source>
|
||||
<target>Scan the code</target>
|
||||
@@ -1163,6 +1204,12 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<target>Search...</target>
|
||||
<note>Placeholder text to prompt entry of search query.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Search/Universe" xml:space="preserve">
|
||||
<source>Search/Universe</source>
|
||||
<target>Search/Universe</target>
|
||||
<note>Navigation title for universe/search settings.
|
||||
Section header for search/universe settings</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Secret Account Login Key" xml:space="preserve">
|
||||
<source>Secret Account Login Key</source>
|
||||
<target>Secret Account Login Key</target>
|
||||
@@ -1207,7 +1254,7 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<trans-unit id="Share" xml:space="preserve">
|
||||
<source>Share</source>
|
||||
<target>Share</target>
|
||||
<note>Button to share a post
|
||||
<note>Button to share a note
|
||||
Button to share an image.
|
||||
Button to share the link to a profile.</note>
|
||||
</trans-unit>
|
||||
@@ -1224,7 +1271,7 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<trans-unit id="Show" xml:space="preserve">
|
||||
<source>Show</source>
|
||||
<target>Show</target>
|
||||
<note>Button to show a post from a user who has been muted.
|
||||
<note>Button to show a note from a user who has been muted.
|
||||
Toggle to show or hide user's secret account login key.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show +" xml:space="preserve">
|
||||
@@ -1232,10 +1279,16 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<target>Show +</target>
|
||||
<note>Button that, when tapped, will show + buttons next to a user's relays.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show less" xml:space="preserve">
|
||||
<source>Show less</source>
|
||||
<target>Show less</target>
|
||||
<note>Button to show less of a long profile description.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show more" xml:space="preserve">
|
||||
<source>Show more</source>
|
||||
<target>Show more</target>
|
||||
<note>Button to show entire note.</note>
|
||||
<note>Button to show entire note.
|
||||
Button to show more of a long profile description.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show only from users you follow" xml:space="preserve">
|
||||
<source>Show only from users you follow</source>
|
||||
@@ -1257,16 +1310,31 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<target>Sign Out</target>
|
||||
<note>Section title for signing out</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sign in" xml:space="preserve">
|
||||
<source>Sign in</source>
|
||||
<target>Sign in</target>
|
||||
<note>Title of view to log into an account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sign out" xml:space="preserve">
|
||||
<source>Sign out</source>
|
||||
<target>Sign out</target>
|
||||
<note>Sidebar menu label to sign out of the account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken" xml:space="preserve">
|
||||
<source>Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken</source>
|
||||
<target>Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken</target>
|
||||
<note>Description about why Nostr is needed.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Software" xml:space="preserve">
|
||||
<source>Software</source>
|
||||
<target>Software</target>
|
||||
<note>Label to display relay software.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Spam" xml:space="preserve">
|
||||
<source>Spam</source>
|
||||
<target>Spam</target>
|
||||
<note>Section header for Universe/Search spam</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Support Damus" xml:space="preserve">
|
||||
<source>Support Damus</source>
|
||||
<target>Support Damus</target>
|
||||
@@ -1297,26 +1365,26 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<target>The address should either begin with LNURL or should look like an email address.</target>
|
||||
<note>Giving the description of the alert message.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="The go-to iOS Nostr client" xml:space="preserve">
|
||||
<source>The go-to iOS Nostr client</source>
|
||||
<target>The go-to iOS Nostr client</target>
|
||||
<note>Quick description of what Damus is</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="They are impersonating someone" xml:space="preserve">
|
||||
<source>They are impersonating someone</source>
|
||||
<target>They are impersonating someone</target>
|
||||
<note>Button for user to report that the account is impersonating someone.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is a paid relay, you must pay for posts to be accepted." xml:space="preserve">
|
||||
<source>This is a paid relay, you must pay for posts to be accepted.</source>
|
||||
<target>This is a paid relay, you must pay for posts to be accepted.</target>
|
||||
<trans-unit id="This is a paid relay, you must pay for notes to be accepted." xml:space="preserve">
|
||||
<source>This is a paid relay, you must pay for notes to be accepted.</source>
|
||||
<target>This is a paid relay, you must pay for notes to be accepted.</target>
|
||||
<note>Footer description that explains that the relay server requires payment to post.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." xml:space="preserve">
|
||||
<source>This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective.</source>
|
||||
<target>This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective.</target>
|
||||
<trans-unit id="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." xml:space="preserve">
|
||||
<source>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.</source>
|
||||
<target>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.</target>
|
||||
<note>Warning that the inputted account key is a public key and the result of what happens because of it.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key." xml:space="preserve">
|
||||
<source>This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key.</source>
|
||||
<target>This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key.</target>
|
||||
<note>Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="This is your account ID, you can give this to your friends so that they can follow you. Tap to copy." xml:space="preserve">
|
||||
<source>This is your account ID, you can give this to your friends so that they can follow you. Tap to copy.</source>
|
||||
<target>This is your account ID, you can give this to your friends so that they can follow you. Tap to copy.</target>
|
||||
@@ -1332,6 +1400,11 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<target>Thread</target>
|
||||
<note>Navigation bar title for note thread.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Top Zap" xml:space="preserve">
|
||||
<source>Top Zap</source>
|
||||
<target>Top Zap</target>
|
||||
<note>Text indicating that this zap is the one with the highest amount of sats.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Translate DMs" xml:space="preserve">
|
||||
<source>Translate DMs</source>
|
||||
<target>Translate DMs</target>
|
||||
@@ -1373,10 +1446,10 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<target>Type %@ to delete</target>
|
||||
<note>Text field prompt asking user to type DELETE in all caps to confirm that they want to proceed with deleting their account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Type your post here..." xml:space="preserve">
|
||||
<source>Type your post here...</source>
|
||||
<target>Type your post here...</target>
|
||||
<note>Text box prompt to ask user to type their post.</note>
|
||||
<trans-unit id="Type your note here..." xml:space="preserve">
|
||||
<source>Type your note here...</source>
|
||||
<target>Type your note here...</target>
|
||||
<note>Text box prompt to ask user to type their note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="URL" xml:space="preserve">
|
||||
<source>URL</source>
|
||||
@@ -1396,7 +1469,7 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<trans-unit id="Universe 🛸" xml:space="preserve">
|
||||
<source>Universe 🛸</source>
|
||||
<target>Universe 🛸</target>
|
||||
<note>Toolbar label for the universal view where posts from all connected relay servers appear.</note>
|
||||
<note>Toolbar label for the universal view where notes from all connected relay servers appear.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Unmute" xml:space="preserve">
|
||||
<source>Unmute</source>
|
||||
@@ -1426,8 +1499,7 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<trans-unit id="Username" xml:space="preserve">
|
||||
<source>Username</source>
|
||||
<target>Username</target>
|
||||
<note>Label for Username section of user profile form.
|
||||
Label to prompt username entry.</note>
|
||||
<note>Label for Username section of user profile form.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Version" xml:space="preserve">
|
||||
<source>Version</source>
|
||||
@@ -1435,6 +1507,16 @@ Button text to indicate that the zap type is a private zap.</note>
|
||||
<note>Label to display relay software version.
|
||||
Section title for displaying the version number of the Damus app.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="View QR Code" xml:space="preserve">
|
||||
<source>View QR Code</source>
|
||||
<target>View QR Code</target>
|
||||
<note>Button to switch to view users QR Code</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="View multiple events per user" xml:space="preserve">
|
||||
<source>View multiple events per user</source>
|
||||
<target>View multiple events per user</target>
|
||||
<note>Setting to only see 1 event per user (npub) in the search/universe</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="WARNING: THIS WILL SIGN AN EVENT THAT DELETES THIS ACCOUNT. YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY. ARE YOU SURE YOU WANT TO CONTINUE?" xml:space="preserve">
|
||||
<source>WARNING:
|
||||
|
||||
@@ -1469,11 +1551,21 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<target>Website</target>
|
||||
<note>Label for Website section of user profile form.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Welcome to Damus" xml:space="preserve">
|
||||
<source>Welcome to Damus</source>
|
||||
<target>Welcome to Damus</target>
|
||||
<note>Welcome text shown on the first screen when user is not logged in.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Welcome to the social network %@ control." xml:space="preserve">
|
||||
<source>Welcome to the social network %@ control.</source>
|
||||
<target>Welcome to the social network %@ control.</target>
|
||||
<note>Welcoming message to the reader. The variable is 'you', the reader.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Welcome to the social network you control" xml:space="preserve">
|
||||
<source>Welcome to the social network you control</source>
|
||||
<target>Welcome to the social network you control</target>
|
||||
<note>Welcome text</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Welcome, %@!" xml:space="preserve">
|
||||
<source>Welcome, %@!</source>
|
||||
<target>Welcome, %@!</target>
|
||||
@@ -1484,6 +1576,16 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<target>What do you want to report?</target>
|
||||
<note>Header text to prompt user what issue they want to report.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="What is Nostr?" xml:space="preserve">
|
||||
<source>What is Nostr?</source>
|
||||
<target>What is Nostr?</target>
|
||||
<note>Heading text for section describing what is Nostr.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Why we need Nostr?" xml:space="preserve">
|
||||
<source>Why we need Nostr?</source>
|
||||
<target>Why we need Nostr?</target>
|
||||
<note>Heading text for section describing why Nostr is needed.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Yes, Overwrite" xml:space="preserve">
|
||||
<source>Yes, Overwrite</source>
|
||||
<target>Yes, Overwrite</target>
|
||||
@@ -1526,6 +1628,16 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<target>Zap Vibration</target>
|
||||
<note>Setting to enable vibration on zap</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Zap attempt from connected wallet failed." xml:space="preserve">
|
||||
<source>Zap attempt from connected wallet failed.</source>
|
||||
<target>Zap attempt from connected wallet failed.</target>
|
||||
<note>Message to display when sending a zap from the user's connected wallet failed.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Zap attempt from connected wallet was canceled." xml:space="preserve">
|
||||
<source>Zap attempt from connected wallet was canceled.</source>
|
||||
<target>Zap attempt from connected wallet was canceled.</target>
|
||||
<note>Message to display when a zap from the user's connected wallet was canceled.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Zap type" xml:space="preserve">
|
||||
<source>Zap type</source>
|
||||
<target>Zap type</target>
|
||||
@@ -1591,23 +1703,23 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<note>Description of private zap type where the zap is sent privately and does not identify the user to the public.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="reacted_tagged_in_1" xml:space="preserve">
|
||||
<source>%@ reacted to a post you were tagged in</source>
|
||||
<target>%@ reacted to a post you were tagged in</target>
|
||||
<note>Notification that a user reacted to a post that the current user was tagged in</note>
|
||||
<source>%@ reacted to a note you were tagged in</source>
|
||||
<target>%@ reacted to a note you were tagged in</target>
|
||||
<note>Notification that a user reacted to a note that the current user was tagged in</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="reacted_tagged_in_2" xml:space="preserve">
|
||||
<source>%@ and %@ reacted to a post you were tagged in</source>
|
||||
<target>%@ and %@ reacted to a post you were tagged in</target>
|
||||
<note>Notification that 2 users reacted to a post that the current user was tagged in</note>
|
||||
<source>%@ and %@ reacted to a note you were tagged in</source>
|
||||
<target>%@ and %@ reacted to a note you were tagged in</target>
|
||||
<note>Notification that 2 users reacted to a note that the current user was tagged in</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="reacted_your_post_1" xml:space="preserve">
|
||||
<source>%@ reacted to your post</source>
|
||||
<target>%@ reacted to your post</target>
|
||||
<note>Notification that a user reacted to the current user's post</note>
|
||||
<trans-unit id="reacted_your_note_1" xml:space="preserve">
|
||||
<source>%@ reacted to your note</source>
|
||||
<target>%@ reacted to your note</target>
|
||||
<note>Notification that a user reacted to the current user's note</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="reacted_your_post_2" xml:space="preserve">
|
||||
<source>%@ and %@ reacted to your post</source>
|
||||
<target>%@ and %@ reacted to your post</target>
|
||||
<trans-unit id="reacted_your_note_2" xml:space="preserve">
|
||||
<source>%@ and %@ reacted to your note</source>
|
||||
<target>%@ and %@ reacted to your note</target>
|
||||
<note>Notification that 2 users reacted to the current user's profile</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="reacted_your_profile_1" xml:space="preserve">
|
||||
@@ -1621,24 +1733,24 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<note>Notification that 2 users reacted to the current user's profile</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="reposted_tagged_in_1" xml:space="preserve">
|
||||
<source>%@ reposted a post you were tagged in</source>
|
||||
<target>%@ reposted a post you were tagged in</target>
|
||||
<note>Notification that a user reposted a post that the current user was tagged in</note>
|
||||
<source>%@ reposted a note you were tagged in</source>
|
||||
<target>%@ reposted a note you were tagged in</target>
|
||||
<note>Notification that a user reposted a note that the current user was tagged in</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="reposted_tagged_in_2" xml:space="preserve">
|
||||
<source>%@ and %@ reposted a post you were tagged in</source>
|
||||
<target>%@ and %@ reposted a post you were tagged in</target>
|
||||
<note>Notification that 2 users reposted a post that the current user was tagged in</note>
|
||||
<source>%@ and %@ reposted a note you were tagged in</source>
|
||||
<target>%@ and %@ reposted a note you were tagged in</target>
|
||||
<note>Notification that 2 users reposted a note that the current user was tagged in</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="reposted_your_post_1" xml:space="preserve">
|
||||
<source>%@ reposted your post</source>
|
||||
<target>%@ reposted your post</target>
|
||||
<note>Notification that a user reposted the current user's post</note>
|
||||
<trans-unit id="reposted_your_note_1" xml:space="preserve">
|
||||
<source>%@ reposted your note</source>
|
||||
<target>%@ reposted your note</target>
|
||||
<note>Notification that a user reposted the current user's note</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="reposted_your_post_2" xml:space="preserve">
|
||||
<source>%@ and %@ reposted your post</source>
|
||||
<target>%@ and %@ reposted your post</target>
|
||||
<note>Notification that 2 users reposted the current user's post</note>
|
||||
<trans-unit id="reposted_your_note_2" xml:space="preserve">
|
||||
<source>%@ and %@ reposted your note</source>
|
||||
<target>%@ and %@ reposted your note</target>
|
||||
<note>Notification that 2 users reposted the current user's note</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="reposted_your_profile_1" xml:space="preserve">
|
||||
<source>%@ reposted your profile</source>
|
||||
@@ -1650,16 +1762,16 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<target>%@ and %@ reposted your profile</target>
|
||||
<note>Notification that 2 users reposted the current user's profile</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="satoshi" xml:space="preserve">
|
||||
<source>satoshi</source>
|
||||
<target>satoshi</target>
|
||||
<note>Example username of Bitcoin creator(s), Satoshi Nakamoto.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="self" xml:space="preserve">
|
||||
<source>self</source>
|
||||
<target>self</target>
|
||||
<note>Part of a larger sentence 'Replying to self' in US English. 'self' indicates that the user is replying to themself and no one else.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="wallet" xml:space="preserve">
|
||||
<source>wallet</source>
|
||||
<target>wallet</target>
|
||||
<note>Sidebar menu label for Wallet view.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="wss://some.relay.com" xml:space="preserve">
|
||||
<source>wss://some.relay.com</source>
|
||||
<target>wss://some.relay.com</target>
|
||||
@@ -1671,45 +1783,40 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<note>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.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="zapped_tagged_in_1" xml:space="preserve">
|
||||
<source>%@ zapped a post you were tagged in</source>
|
||||
<target>%@ zapped a post you were tagged in</target>
|
||||
<note>Notification that a user zapped a post that the current user was tagged in</note>
|
||||
<source>%@ zapped a note you were tagged in</source>
|
||||
<target>%@ zapped a note you were tagged in</target>
|
||||
<note>Notification that a user zapped a note that the current user was tagged in</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="zapped_tagged_in_2" xml:space="preserve">
|
||||
<source>%@ and %@ zapped a post you were tagged in</source>
|
||||
<target>%@ and %@ zapped a post you were tagged in</target>
|
||||
<note>Notification that 2 users zapped a post that the current user was tagged in</note>
|
||||
<source>%@ and %@ zapped a note you were tagged in</source>
|
||||
<target>%@ and %@ zapped a note you were tagged in</target>
|
||||
<note>Notification that 2 users zapped a note that the current user was tagged in</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="zapped_your_post_1" xml:space="preserve">
|
||||
<source>%@ zapped your post</source>
|
||||
<target>%@ zapped your post</target>
|
||||
<note>Notification that a user zapped the current user's post</note>
|
||||
<trans-unit id="zapped_your_note_1" xml:space="preserve">
|
||||
<source>%@ zapped your note</source>
|
||||
<target>%@ zapped your note</target>
|
||||
<note>Notification that a user zapped the current user's note</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="zapped_your_post_2" xml:space="preserve">
|
||||
<source>%@ and %@ zapped your post</source>
|
||||
<target>%@ and %@ zapped your post</target>
|
||||
<note>Notification that 2 users zapped the current user's post</note>
|
||||
<trans-unit id="zapped_your_note_2" xml:space="preserve">
|
||||
<source>%@ and %@ zapped your note</source>
|
||||
<target>%@ and %@ zapped your note</target>
|
||||
<note>Notification that 2 users zapped the current user's note</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="zapped_your_profile_1" xml:space="preserve">
|
||||
<source>%@ zapped your profile</source>
|
||||
<target>%@ zapped your profile</target>
|
||||
<source>%@ zapped you</source>
|
||||
<target>%@ zapped you</target>
|
||||
<note>Notification that a user zapped the current user's profile</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="zapped_your_profile_2" xml:space="preserve">
|
||||
<source>%@ and %@ zapped your profile</source>
|
||||
<target>%@ and %@ zapped your profile</target>
|
||||
<source>%@ and %@ zapped you</source>
|
||||
<target>%@ and %@ zapped you</target>
|
||||
<note>Notification that 2 users zapped the current user's profile</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="⚡️ %@" xml:space="preserve">
|
||||
<source>⚡️ %@</source>
|
||||
<target>⚡️ %@</target>
|
||||
<note>Text indicating the zap amount. i.e. number of satoshis that were tipped to a user</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
<file original="damus/en-US.lproj/Localizable.stringsdict" source-language="en-US" target-language="en-US" datatype="plaintext">
|
||||
<header>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.3" build-num="14E222b"/>
|
||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.3.1" build-num="14E300c"/>
|
||||
</header>
|
||||
<body>
|
||||
<trans-unit id="/collapsed_event_view_other_notes:dict/NOTES:dict/one:dict/:string" xml:space="preserve">
|
||||
@@ -1763,28 +1870,28 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reacted_tagged_in_3:dict/REACTED:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d other reacted to a post you were tagged in</source>
|
||||
<target>%2$@ and %1$d other reacted to a post you were tagged in</target>
|
||||
<source>%2$@ and %1$d other reacted to a note you were tagged in</source>
|
||||
<target>%2$@ and %1$d other reacted to a note you were tagged in</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reacted_tagged_in_3:dict/REACTED:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d others reacted to a post you were tagged in</source>
|
||||
<target>%2$@ and %1$d others reacted to a post you were tagged in</target>
|
||||
<source>%2$@ and %1$d others reacted to a note you were tagged in</source>
|
||||
<target>%2$@ and %1$d others reacted to a note you were tagged in</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reacted_your_post_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
<trans-unit id="/reacted_your_note_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
<source>%#@REACTED@</source>
|
||||
<target>%#@REACTED@</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reacted_your_post_3:dict/REACTED:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d other reacted to your post</source>
|
||||
<target>%2$@ and %1$d other reacted to your post</target>
|
||||
<trans-unit id="/reacted_your_note_3:dict/REACTED:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d other reacted to your note</source>
|
||||
<target>%2$@ and %1$d other reacted to your note</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reacted_your_post_3:dict/REACTED:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d others reacted to your post</source>
|
||||
<target>%2$@ and %1$d others reacted to your post</target>
|
||||
<trans-unit id="/reacted_your_note_3:dict/REACTED:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d others reacted to your note</source>
|
||||
<target>%2$@ and %1$d others reacted to your note</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reacted_your_profile_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
@@ -1853,28 +1960,28 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reposted_tagged_in_3:dict/REPOSTED:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d other reposted a post you were tagged in</source>
|
||||
<target>%2$@ and %1$d other reposted a post you were tagged in</target>
|
||||
<source>%2$@ and %1$d other reposted a note you were tagged in</source>
|
||||
<target>%2$@ and %1$d other reposted a note you were tagged in</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reposted_tagged_in_3:dict/REPOSTED:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d others reposted a post you were tagged in</source>
|
||||
<target>%2$@ and %1$d others reposted a post you were tagged in</target>
|
||||
<source>%2$@ and %1$d others reposted a note you were tagged in</source>
|
||||
<target>%2$@ and %1$d others reposted a note you were tagged in</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reposted_your_post_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
<trans-unit id="/reposted_your_note_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
<source>%#@REPOSTED@</source>
|
||||
<target>%#@REPOSTED@</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reposted_your_post_3:dict/REPOSTED:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d other reposted your post</source>
|
||||
<target>%2$@ and %1$d other reposted your post</target>
|
||||
<trans-unit id="/reposted_your_note_3:dict/REPOSTED:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d other reposted your note</source>
|
||||
<target>%2$@ and %1$d other reposted your note</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reposted_your_post_3:dict/REPOSTED:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d others reposted your post</source>
|
||||
<target>%2$@ and %1$d others reposted your post</target>
|
||||
<trans-unit id="/reposted_your_note_3:dict/REPOSTED:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d others reposted your note</source>
|
||||
<target>%2$@ and %1$d others reposted your note</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/reposted_your_profile_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
@@ -1973,28 +2080,28 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/zapped_tagged_in_3:dict/ZAPPED:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d other zapped a post you were tagged in</source>
|
||||
<target>%2$@ and %1$d other zapped a post you were tagged in</target>
|
||||
<source>%2$@ and %1$d other zapped a note you were tagged in</source>
|
||||
<target>%2$@ and %1$d other zapped a note you were tagged in</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/zapped_tagged_in_3:dict/ZAPPED:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d others zapped a post you were tagged in</source>
|
||||
<target>%2$@ and %1$d others zapped a post you were tagged in</target>
|
||||
<source>%2$@ and %1$d others zapped a note you were tagged in</source>
|
||||
<target>%2$@ and %1$d others zapped a note you were tagged in</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/zapped_your_post_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
<trans-unit id="/zapped_your_note_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
<source>%#@ZAPPED@</source>
|
||||
<target>%#@ZAPPED@</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/zapped_your_post_3:dict/ZAPPED:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d other zapped your post</source>
|
||||
<target>%2$@ and %1$d other zapped your post</target>
|
||||
<trans-unit id="/zapped_your_note_3:dict/ZAPPED:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d other zapped your note</source>
|
||||
<target>%2$@ and %1$d other zapped your note</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/zapped_your_post_3:dict/ZAPPED:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d others zapped your post</source>
|
||||
<target>%2$@ and %1$d others zapped your post</target>
|
||||
<trans-unit id="/zapped_your_note_3:dict/ZAPPED:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d others zapped your note</source>
|
||||
<target>%2$@ and %1$d others zapped your note</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/zapped_your_profile_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
@@ -2003,13 +2110,13 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/zapped_your_profile_3:dict/ZAPPED:dict/one:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d other zapped your profile</source>
|
||||
<target>%2$@ and %1$d other zapped your profile</target>
|
||||
<source>%2$@ and %1$d other zapped you</source>
|
||||
<target>%2$@ and %1$d other zapped you</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/zapped_your_profile_3:dict/ZAPPED:dict/other:dict/:string" xml:space="preserve">
|
||||
<source>%2$@ and %1$d others zapped your profile</source>
|
||||
<target>%2$@ and %1$d others zapped your profile</target>
|
||||
<source>%2$@ and %1$d others zapped you</source>
|
||||
<target>%2$@ and %1$d others zapped you</target>
|
||||
<note/>
|
||||
</trans-unit>
|
||||
<trans-unit id="/zaps_count:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
|
||||
|
||||
Binary file not shown.
@@ -61,12 +61,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other reacted to a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d other reacted to a note you were tagged in</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others reacted to a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d others reacted to a note you were tagged in</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
<key>reacted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTED@</string>
|
||||
@@ -77,9 +77,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other reacted to your post</string>
|
||||
<string>%2$@ and %1$d other reacted to your note</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others reacted to your post</string>
|
||||
<string>%2$@ and %1$d others reacted to your note</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_profile_3</key>
|
||||
@@ -157,12 +157,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other reposted a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d other reposted a note you were tagged in</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others reposted a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d others reposted a note you were tagged in</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
<key>reposted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTED@</string>
|
||||
@@ -173,9 +173,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other reposted your post</string>
|
||||
<string>%2$@ and %1$d other reposted your note</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others reposted your post</string>
|
||||
<string>%2$@ and %1$d others reposted your note</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_profile_3</key>
|
||||
@@ -269,9 +269,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>@</string>
|
||||
<key>one</key>
|
||||
<string>You received %2$@ sat from %3$@: "%4$@"</string>
|
||||
<string>You received %2$@ sat from %3$@: "%4$@"</string>
|
||||
<key>other</key>
|
||||
<string>You received %2$@ sats from %3$@: "%4$@"</string>
|
||||
<string>You received %2$@ sats from %3$@: "%4$@"</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_tagged_in_3</key>
|
||||
@@ -285,12 +285,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other zapped a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d other zapped a note you were tagged in</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others zapped a post you were tagged in</string>
|
||||
<string>%2$@ and %1$d others zapped a note you were tagged in</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
<key>zapped_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPPED@</string>
|
||||
@@ -301,9 +301,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other zapped your post</string>
|
||||
<string>%2$@ and %1$d other zapped your note</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others zapped your post</string>
|
||||
<string>%2$@ and %1$d others zapped your note</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_profile_3</key>
|
||||
@@ -317,9 +317,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ and %1$d other zapped your profile</string>
|
||||
<string>%2$@ and %1$d other zapped you</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ and %1$d others zapped your profile</string>
|
||||
<string>%2$@ and %1$d others zapped you</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
"project" : "damus.xcodeproj",
|
||||
"targetLocale" : "en-US",
|
||||
"toolInfo" : {
|
||||
"toolBuildNumber" : "14E222b",
|
||||
"toolBuildNumber" : "14E300c",
|
||||
"toolID" : "com.apple.dt.xcode",
|
||||
"toolName" : "Xcode",
|
||||
"toolVersion" : "14.3"
|
||||
"toolVersion" : "14.3.1"
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
Binary file not shown.
@@ -67,14 +67,14 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a un post en el que te etiquetaron</string>
|
||||
<string>%2$@ y %1$d más han reaccionado a una nota en la que te etiquetaron</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a un post en el que te etiquetaron</string>
|
||||
<string>%2$@ y %1$d más han reaccionado a una nota en la que te etiquetaron</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a un post en el que te etiquetaron</string>
|
||||
<string>%2$@ y %1$d más han reaccionado a una nota en la que te etiquetaron</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
<key>reacted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTED@</string>
|
||||
@@ -85,11 +85,11 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a tu post</string>
|
||||
<string>%2$@ y %1$d más han reaccionado a tu nota</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a tu post</string>
|
||||
<string>%2$@ y %1$d más han reaccionado a tu nota</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a tu post</string>
|
||||
<string>%2$@ y %1$d más han reaccionado a tu nota</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_profile_3</key>
|
||||
@@ -175,14 +175,14 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ y %1$d más han republicado un post en el que te etiquetaron</string>
|
||||
<string>%2$@ y %1$d más han republicado una nota en la que te etiquetaron</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han republicado un post en el que te etiquetaron</string>
|
||||
<string>%2$@ y %1$d más han republicado una nota en la que te etiquetaron</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han republicado un post en el que te etiquetaron</string>
|
||||
<string>%2$@ y %1$d más han republicado una nota en la que te etiquetaron</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
<key>reposted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTED@</string>
|
||||
@@ -193,11 +193,11 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ y %1$d más han republicado tu post</string>
|
||||
<string>%2$@ y %1$d más han republicado tu nota</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han republicado tu post</string>
|
||||
<string>%2$@ y %1$d más han republicado tu nota</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han republicado tu post</string>
|
||||
<string>%2$@ y %1$d más han republicado tu nota</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_profile_3</key>
|
||||
@@ -319,14 +319,14 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ y %1$d más han zapeado un post en el que te etiquetaron</string>
|
||||
<string>%2$@ y %1$d más han zapeado una nota en la que te etiquetaron</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han zapeado un post en el que te etiquetaron</string>
|
||||
<string>%2$@ y %1$d más han zapeado una nota en la que te etiquetaron</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han zapeado un post en el que te etiquetaron</string>
|
||||
<string>%2$@ y %1$d más han zapeado una nota en la que te etiquetaron</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
<key>zapped_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPPED@</string>
|
||||
@@ -337,11 +337,11 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ y %1$d más han zapeado tu post</string>
|
||||
<string>%2$@ y %1$d más han zapeado tu nota</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han zapeado tu post</string>
|
||||
<string>%2$@ y %1$d más han zapeado tu nota</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han zapeado tu post</string>
|
||||
<string>%2$@ y %1$d más han zapeado tu nota</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_profile_3</key>
|
||||
@@ -355,11 +355,11 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ y %1$d más han zapeado tu perfil</string>
|
||||
<string>%2$@ y %1$d más te han enviado un zap</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han zapeado tu perfil</string>
|
||||
<string>%2$@ y %1$d más te han enviado un zap</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han zapeado tu perfil</string>
|
||||
<string>%2$@ y %1$d más te han enviado un zap</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
|
||||
Binary file not shown.
@@ -67,14 +67,14 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ et %1$d autre ont réagi à une publication dans laquelle vous apparaissez</string>
|
||||
<string>%2$@ et %1$d autre ont réagi à une note dans laquelle vous apparaissez</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ et %1$d autres ont réagi à une publication dans laquelle vous apparaissez</string>
|
||||
<string>%2$@ et %1$d autres ont réagi à une note dans laquelle vous apparaissez</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ et %1$d autres ont réagi à une publication dans laquelle vous apparaissez</string>
|
||||
<string>%2$@ et %1$d autres ont réagi à une note dans laquelle vous apparaissez</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
<key>reacted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTED@</string>
|
||||
@@ -85,11 +85,11 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ et %1$d autre ont réagi à votre publication</string>
|
||||
<string>%2$@ et %1$d autre ont réagi à votre note</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ et %1$d autres ont réagi à votre publication</string>
|
||||
<string>%2$@ et %1$d autres ont réagi à votre note</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ et %1$d autres ont réagi à votre publication</string>
|
||||
<string>%2$@ et %1$d autres ont réagi à votre note</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_profile_3</key>
|
||||
@@ -175,14 +175,14 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ et %1$d autre ont cité une publication dans laquelle vous apparaissez</string>
|
||||
<string>%2$@ et %1$d autre ont republié une note dans laquelle vous apparaissez</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ et %1$d autres ont cité une publication dans laquelle vous apparaissez</string>
|
||||
<string>%2$@ et %1$d autres ont republié une note dans laquelle vous apparaissez</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ et %1$d autres ont republié une publication dans laquelle vous apparaissez</string>
|
||||
<string>%2$@ et %1$d autres ont republié une note dans laquelle vous apparaissez</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
<key>reposted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTED@</string>
|
||||
@@ -193,11 +193,11 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ et %1$d autre ont cité votre publication</string>
|
||||
<string>%2$@ et %1$d autre ont republié votre note</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ et %1$d autres ont cité votre publication</string>
|
||||
<string>%2$@ et %1$d autres ont republié votre note</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ et %1$d autres ont republié votre publication</string>
|
||||
<string>%2$@ et %1$d autres ont republié votre note</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_profile_3</key>
|
||||
@@ -301,11 +301,11 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>@</string>
|
||||
<key>one</key>
|
||||
<string>Vous avez reçu %2$@ sat de %3$@: "%4$@"</string>
|
||||
<string>Vous avez reçu %2$@ sat de %3$@: "%4$@"</string>
|
||||
<key>many</key>
|
||||
<string>Vous avez reçu %2$@ sats de %3$@: "%4$@"</string>
|
||||
<string>Vous avez reçu %2$@ sats de %3$@: "%4$@"</string>
|
||||
<key>other</key>
|
||||
<string>Vous avez reçu %2$@ sats de %3$@: "%4$@"</string>
|
||||
<string>Vous avez reçu %2$@ sats de %3$@: "%4$@"</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_tagged_in_3</key>
|
||||
@@ -319,14 +319,14 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ et %1$d autre ont zappé une publication dans laquelle vous apparaissez</string>
|
||||
<string>%2$@ et %1$d autre ont zappé une note dans laquelle vous apparaissez</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ et %1$d autres ont zappé une publication dans laquelle vous apparaissez</string>
|
||||
<string>%2$@ et %1$d autres ont zappé une note dans laquelle vous apparaissez</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ et %1$d autres ont zappé une publication dans laquelle vous apparaissez</string>
|
||||
<string>%2$@ et %1$d autres ont zappé une note dans laquelle vous apparaissez</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
<key>zapped_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPPED@</string>
|
||||
@@ -337,11 +337,11 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ et %1$d autre ont zappé votre publication</string>
|
||||
<string>%2$@ et %1$d autre ont zappé votre note</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ et %1$d autres ont zappé votre publication</string>
|
||||
<string>%2$@ et %1$d autres ont zappé votre note</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ et %1$d autres ont zappé votre publication</string>
|
||||
<string>%2$@ et %1$d autres ont zappé votre note</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_profile_3</key>
|
||||
@@ -355,11 +355,11 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ et %1$d autre ont zappé votre profile</string>
|
||||
<string>%2$@ et %1$d autre vous ont zappé</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ et %1$d autres ont zappé votre profile</string>
|
||||
<string>%2$@ et %1$d autres vous ont zappé</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ et %1$d autres ont zappé votre profile</string>
|
||||
<string>%2$@ et %1$d autres vous ont zappé</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -58,7 +58,7 @@
|
||||
<string>タグづけされた投稿に%2$@と他%1$d人がリアクションしました</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
<key>reacted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTED@</string>
|
||||
@@ -142,7 +142,7 @@
|
||||
<string>タグづけされた投稿を%2$@と他%1$d人がリポストしました</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
<key>reposted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTED@</string>
|
||||
@@ -237,7 +237,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>@</string>
|
||||
<key>other</key>
|
||||
<string>%3$@ から %2$@ sats を受け取りました: "%4$@"</string>
|
||||
<string>%3$@ から %2$@ sats を受け取りました: "%4$@"</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_tagged_in_3</key>
|
||||
@@ -254,7 +254,7 @@
|
||||
<string>あなたがタグづけされた投稿に%2$@と他%1$d人がZapしました</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
<key>zapped_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPPED@</string>
|
||||
@@ -279,7 +279,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@と他%1$d人があなたのプロフィールにZapしました</string>
|
||||
<string>%2$@と他%1$d人があなたにZapしました</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
|
||||
Binary file not shown.
@@ -61,12 +61,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ en %1$d ander hebben gereageerd op een bericht waarin je getagd bent</string>
|
||||
<string>%2$@ en %1$d ander hebben gereageerd op een notitie waarin je getagd bent</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ en %1$d anderen hebben gereageerd op een bericht waarin je getagd bent</string>
|
||||
<string>%2$@ en %1$d anderen hebben gereageerd op een notitie waarin je getagd bent</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
<key>reacted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTED@</string>
|
||||
@@ -77,9 +77,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ en %1$d ander hebben gereageerd op je bericht</string>
|
||||
<string>%2$@ en %1$d ander hebben gereageerd op je notitie</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ en %1$d anderen hebben gereageerd op je bericht</string>
|
||||
<string>%2$@ en %1$d anderen hebben gereageerd op je notitie</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_profile_3</key>
|
||||
@@ -157,12 +157,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ en %1$d ander hebben een bericht waarin je getagd bent herplaatst</string>
|
||||
<string>%2$@ en %1$d ander hebben een notitie waarin je getagd bent herplaatst</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ en %1$d anderen hebben een bericht waarin je getagd bent herplaatst</string>
|
||||
<string>%2$@ en %1$d anderen hebben een notitie waarin je getagd bent herplaatst</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
<key>reposted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTED@</string>
|
||||
@@ -173,9 +173,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ en %1$d ander hebben je bericht herplaatst</string>
|
||||
<string>%2$@ en %1$d ander hebben je notitie herplaatst</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ en %1$d anderen hebben je bericht herplaatst</string>
|
||||
<string>%2$@ en %1$d anderen hebben je notitie herplaatst</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_profile_3</key>
|
||||
@@ -285,12 +285,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ en %1$d ander hebben een bericht waarin je getagd bent gezapt</string>
|
||||
<string>%2$@ en %1$d ander hebben een notitie waarin je getagd bent gezapt</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ en %1$d anderen hebben een bericht waarin je getagd bent gezapt</string>
|
||||
<string>%2$@ en %1$d anderen hebben een notitie waarin je getagd bent gezapt</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
<key>zapped_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPPED@</string>
|
||||
@@ -301,9 +301,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ en %1$d ander hebben je bericht gezapt</string>
|
||||
<string>%2$@ en %1$d ander hebben je notitie gezapt</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ en %1$d anderen hebben je bericht gezapt</string>
|
||||
<string>%2$@ en %1$d anderen hebben je notitie gezapt</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_profile_3</key>
|
||||
@@ -317,9 +317,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ en %1$d ander hebben je profiel gezapt</string>
|
||||
<string>%2$@ en %1$d ander heeft je gezapt</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ en %1$d anderen hebben je profiel gezapt</string>
|
||||
<string>%2$@ en %1$d anderen hebben je gezapt</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -61,12 +61,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ och %1$d till reagerade på ett inlägg som du var taggad i</string>
|
||||
<string>%2$@ och %1$d andra reagerade på en anteckning som du har taggats i</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ och %1$d andra reagerade på ett inlägg som du var taggad i</string>
|
||||
<string>%2$@ och %1$d andra reagerade på en anteckning som du har taggats i</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
<key>reacted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTED@</string>
|
||||
@@ -77,9 +77,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ och %1$d till reagerade på ditt inlägg</string>
|
||||
<string>%2$@ och %1$d andra reagerade på din anteckning</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ och %1$d andra reagerade på ditt inlägg</string>
|
||||
<string>%2$@ och %1$d andra reagerade på din anteckning</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_profile_3</key>
|
||||
@@ -157,12 +157,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ och %1$d annan delade ett inlägg som du var taggad i</string>
|
||||
<string>%2$@ och %1$d andra lade om en anteckning som du har taggats i</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ och %1$d andra delade ett inlägg som du var taggad i</string>
|
||||
<string>%2$@ och %1$d andra lade om en anteckning som du har taggats i</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
<key>reposted_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTED@</string>
|
||||
@@ -173,9 +173,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ och %1$d annan delade ditt inlägg</string>
|
||||
<string>%2$@ och %1$d andra lade om din anteckning</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ och %1$d andra delade ditt inlägg</string>
|
||||
<string>%2$@ och %1$d andra lade om din anteckning</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_profile_3</key>
|
||||
@@ -269,9 +269,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>@</string>
|
||||
<key>one</key>
|
||||
<string>Du fick %2$@ sat från %3$@: "%4$@"</string>
|
||||
<string>Du fick %2$@ sat från %3$@: "%4$@"</string>
|
||||
<key>other</key>
|
||||
<string>Du fick %2$@ sats från %3$@: "%4$@"</string>
|
||||
<string>Du fick %2$@ sats från %3$@: "%4$@"</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_tagged_in_3</key>
|
||||
@@ -285,12 +285,12 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ och %1$d annan zappade ett inlägg du som var taggad i</string>
|
||||
<string>%2$@ och %1$d andra zappade en anteckning som du har taggats i</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ och %1$d andra zappade ett inlägg som du var taggad i</string>
|
||||
<string>%2$@ och %1$d andra zappade en anteckning som du har taggats i</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
<key>zapped_your_note_3</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPPED@</string>
|
||||
@@ -301,9 +301,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ och %1$d annan zappade ditt inlägg</string>
|
||||
<string>%2$@ och %1$d andra zappade din anteckning</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ och %1$d andra zappade ditt inlägg</string>
|
||||
<string>%2$@ och %1$d andra zappade din anteckning</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_profile_3</key>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user